88import google .protobuf .duration_pb2
99import google .protobuf .empty_pb2
1010import google .protobuf .message
11+ import google .protobuf .struct_pb2
1112import google .protobuf .timestamp_pb2
1213import google .protobuf .wrappers_pb2
1314from google .protobuf import descriptor_pool , message_factory
@@ -44,6 +45,12 @@ def marshal_any(value: Any) -> google.protobuf.any_pb2.Any:
4445 nanos = value .microseconds * 1000
4546 value = google .protobuf .duration_pb2 .Duration (seconds = seconds , nanos = nanos )
4647
48+ if isinstance (value , list ) or isinstance (value , dict ):
49+ try :
50+ value = as_struct_value (value )
51+ except ValueError :
52+ pass # fallthrough
53+
4754 if not isinstance (value , google .protobuf .message .Message ):
4855 value = pickled_pb .Pickled (pickled_value = pickle .dumps (value ))
4956
@@ -64,34 +71,100 @@ def unmarshal_any(any: google.protobuf.any_pb2.Any) -> Any:
6471
6572 if isinstance (proto , pickled_pb .Pickled ):
6673 return pickle .loads (proto .pickled_value )
74+
6775 elif isinstance (proto , google .protobuf .empty_pb2 .Empty ):
6876 return None
77+
6978 elif isinstance (proto , google .protobuf .wrappers_pb2 .BoolValue ):
7079 return proto .value
80+
7181 elif isinstance (proto , google .protobuf .wrappers_pb2 .Int32Value ):
7282 return proto .value
83+
7384 elif isinstance (proto , google .protobuf .wrappers_pb2 .Int64Value ):
7485 return proto .value
86+
7587 elif isinstance (proto , google .protobuf .wrappers_pb2 .UInt32Value ):
7688 return proto .value
89+
7790 elif isinstance (proto , google .protobuf .wrappers_pb2 .UInt64Value ):
7891 return proto .value
92+
7993 elif isinstance (proto , google .protobuf .wrappers_pb2 .FloatValue ):
8094 return proto .value
95+
8196 elif isinstance (proto , google .protobuf .wrappers_pb2 .DoubleValue ):
8297 return proto .value
98+
8399 elif isinstance (proto , google .protobuf .wrappers_pb2 .StringValue ):
84100 return proto .value
101+
85102 elif isinstance (proto , google .protobuf .wrappers_pb2 .BytesValue ):
86103 try :
87104 # Assume it's the legacy container for pickled values.
88105 return pickle .loads (proto .value )
89106 except Exception as e :
90107 # Otherwise, return the literal bytes.
91108 return proto .value
109+
92110 elif isinstance (proto , google .protobuf .timestamp_pb2 .Timestamp ):
93111 return proto .ToDatetime (tzinfo = UTC )
112+
94113 elif isinstance (proto , google .protobuf .duration_pb2 .Duration ):
95114 return proto .ToTimedelta ()
96115
116+ elif isinstance (proto , google .protobuf .struct_pb2 .Value ):
117+ return from_struct_value (proto )
118+
97119 return proto
120+
121+
122+ def as_struct_value (value : Any ) -> google .protobuf .struct_pb2 .Value :
123+ if value is None :
124+ null_value = google .protobuf .struct_pb2 .NullValue .NULL_VALUE
125+ return google .protobuf .struct_pb2 .Value (null_value = null_value )
126+
127+ elif isinstance (value , bool ):
128+ return google .protobuf .struct_pb2 .Value (bool_value = value )
129+
130+ elif isinstance (value , int ) or isinstance (value , float ):
131+ return google .protobuf .struct_pb2 .Value (number_value = float (value ))
132+
133+ elif isinstance (value , str ):
134+ return google .protobuf .struct_pb2 .Value (string_value = value )
135+
136+ elif isinstance (value , list ):
137+ list_value = google .protobuf .struct_pb2 .ListValue (
138+ values = [as_struct_value (v ) for v in value ]
139+ )
140+ return google .protobuf .struct_pb2 .Value (list_value = list_value )
141+
142+ elif isinstance (value , dict ):
143+ for key in value .keys ():
144+ if not isinstance (key , str ):
145+ raise ValueError ("unsupported object key" )
146+
147+ struct_value = google .protobuf .struct_pb2 .Struct (
148+ fields = {k : as_struct_value (v ) for k , v in value .items ()}
149+ )
150+ return google .protobuf .struct_pb2 .Value (struct_value = struct_value )
151+
152+ raise ValueError ("unsupported value" )
153+
154+
155+ def from_struct_value (value : google .protobuf .struct_pb2 .Value ) -> Any :
156+ if value .HasField ("null_value" ):
157+ return None
158+ elif value .HasField ("bool_value" ):
159+ return value .bool_value
160+ elif value .HasField ("number_value" ):
161+ return value .number_value
162+ elif value .HasField ("string_value" ):
163+ return value .string_value
164+ elif value .HasField ("list_value" ):
165+
166+ return [from_struct_value (v ) for v in value .list_value .values ]
167+ elif value .HasField ("struct_value" ):
168+ return {k : from_struct_value (v ) for k , v in value .struct_value .fields .items ()}
169+ else :
170+ raise RuntimeError (f"invalid struct_pb2.Value: { value } " )
0 commit comments