2020
2121from google .protobuf import descriptor_pb2
2222from google .protobuf import message
23- from google .protobuf .json_format import MessageToJson , Parse
23+ from google .protobuf .json_format import MessageToDict , MessageToJson , Parse
2424
2525from proto import _file_info
2626from proto import _package_info
@@ -347,39 +347,65 @@ def to_json(cls, instance, *, use_integers_for_enums=True) -> str:
347347 including_default_value_fields = True ,
348348 )
349349
350- def from_json (cls , payload ) -> "Message" :
350+ def from_json (cls , payload , * , ignore_unknown_fields = False ) -> "Message" :
351351 """Given a json string representing an instance,
352352 parse it into a message.
353353
354354 Args:
355355 paylod: A json string representing a message.
356+ ignore_unknown_fields (Optional(bool)): If True, do not raise errors
357+ for unknown fields.
356358
357359 Returns:
358360 ~.Message: An instance of the message class against which this
359361 method was called.
360362 """
361363 instance = cls ()
362- Parse (payload , instance ._pb )
364+ Parse (payload , instance ._pb , ignore_unknown_fields = ignore_unknown_fields )
363365 return instance
364366
367+ def to_dict (cls , instance , * , use_integers_for_enums = True ) -> "Message" :
368+ """Given a message instance, return its representation as a python dict.
369+
370+ Args:
371+ instance: An instance of this message type, or something
372+ compatible (accepted by the type's constructor).
373+ use_integers_for_enums (Optional(bool)): An option that determines whether enum
374+ values should be represented by strings (False) or integers (True).
375+ Default is True.
376+
377+ Returns:
378+ dict: A representation of the protocol buffer using pythonic data structures.
379+ Messages and map fields are represented as dicts,
380+ repeated fields are represented as lists.
381+ """
382+ return MessageToDict (
383+ cls .pb (instance ),
384+ including_default_value_fields = True ,
385+ preserving_proto_field_name = True ,
386+ use_integers_for_enums = use_integers_for_enums ,
387+ )
388+
365389
366390class Message (metaclass = MessageMeta ):
367391 """The abstract base class for a message.
368392
369393 Args:
370394 mapping (Union[dict, ~.Message]): A dictionary or message to be
371395 used to determine the values for this message.
396+ ignore_unknown_fields (Optional(bool)): If True, do not raise errors for
397+ unknown fields. Only applied if `mapping` is a mapping type or there
398+ are keyword parameters.
372399 kwargs (dict): Keys and values corresponding to the fields of the
373400 message.
374401 """
375402
376- def __init__ (self , mapping = None , ** kwargs ):
403+ def __init__ (self , mapping = None , * , ignore_unknown_fields = False , * *kwargs ):
377404 # We accept several things for `mapping`:
378405 # * An instance of this class.
379406 # * An instance of the underlying protobuf descriptor class.
380407 # * A dict
381408 # * Nothing (keyword arguments only).
382-
383409 if mapping is None :
384410 if not kwargs :
385411 # Special fast path for empty construction.
@@ -405,24 +431,33 @@ def __init__(self, mapping=None, **kwargs):
405431 # Just use the above logic on mapping's underlying pb.
406432 self .__init__ (mapping = mapping ._pb , ** kwargs )
407433 return
408- elif not isinstance (mapping , collections .abc .Mapping ):
434+ elif isinstance (mapping , collections .abc .Mapping ):
435+ # Can't have side effects on mapping.
436+ mapping = copy .copy (mapping )
437+ # kwargs entries take priority for duplicate keys.
438+ mapping .update (kwargs )
439+ else :
409440 # Sanity check: Did we get something not a map? Error if so.
410441 raise TypeError (
411442 "Invalid constructor input for %s: %r"
412443 % (self .__class__ .__name__ , mapping ,)
413444 )
414- else :
415- # Can't have side effects on mapping.
416- mapping = copy .copy (mapping )
417- # kwargs entries take priority for duplicate keys.
418- mapping .update (kwargs )
419445
420446 params = {}
421447 # Update the mapping to address any values that need to be
422448 # coerced.
423449 marshal = self ._meta .marshal
424450 for key , value in mapping .items ():
425- pb_type = self ._meta .fields [key ].pb_type
451+ try :
452+ pb_type = self ._meta .fields [key ].pb_type
453+ except KeyError :
454+ if ignore_unknown_fields :
455+ continue
456+
457+ raise ValueError (
458+ "Unknown field for {}: {}" .format (self .__class__ .__name__ , key )
459+ )
460+
426461 pb_value = marshal .to_proto (pb_type , value )
427462 if pb_value is not None :
428463 params [key ] = pb_value
0 commit comments