@@ -527,34 +527,72 @@ def __init__(
527527 # coerced.
528528 marshal = self ._meta .marshal
529529 for key , value in mapping .items ():
530+ (key , pb_type ) = self ._get_pb_type_from_key (key )
531+ if pb_type is None :
532+ if ignore_unknown_fields :
533+ continue
534+
535+ raise ValueError (
536+ "Unknown field for {}: {}" .format (self .__class__ .__name__ , key )
537+ )
538+
530539 try :
531- pb_type = self . _meta . fields [ key ]. pb_type
532- except KeyError :
540+ pb_value = marshal . to_proto ( pb_type , value )
541+ except ValueError :
533542 # Underscores may be appended to field names
534543 # that collide with python or proto-plus keywords.
535544 # In case a key only exists with a `_` suffix, coerce the key
536- # to include the `_` suffix. Is not possible to
545+ # to include the `_` suffix. It's not possible to
537546 # natively define the same field with a trailing underscore in protobuf.
538547 # See related issue
539548 # https://github.com/googleapis/python-api-core/issues/227
540- if f"{ key } _" in self ._meta .fields :
541- key = f"{ key } _"
542- pb_type = self ._meta .fields [key ].pb_type
543- else :
544- if ignore_unknown_fields :
545- continue
546-
547- raise ValueError (
548- "Unknown field for {}: {}" .format (self .__class__ .__name__ , key )
549- )
550-
551- pb_value = marshal .to_proto (pb_type , value )
549+ if isinstance (value , dict ):
550+ keys_to_update = [
551+ item
552+ for item in value
553+ if not hasattr (pb_type , item ) and hasattr (pb_type , f"{ item } _" )
554+ ]
555+ for item in keys_to_update :
556+ value [f"{ item } _" ] = value .pop (item )
557+
558+ pb_value = marshal .to_proto (pb_type , value )
559+
552560 if pb_value is not None :
553561 params [key ] = pb_value
554562
555563 # Create the internal protocol buffer.
556564 super ().__setattr__ ("_pb" , self ._meta .pb (** params ))
557565
566+ def _get_pb_type_from_key (self , key ):
567+ """Given a key, return the corresponding pb_type.
568+
569+ Args:
570+ key(str): The name of the field.
571+
572+ Returns:
573+ A tuple containing a key and pb_type. The pb_type will be
574+ the composite type of the field, or the primitive type if a primitive.
575+ If no corresponding field exists, return None.
576+ """
577+
578+ pb_type = None
579+
580+ try :
581+ pb_type = self ._meta .fields [key ].pb_type
582+ except KeyError :
583+ # Underscores may be appended to field names
584+ # that collide with python or proto-plus keywords.
585+ # In case a key only exists with a `_` suffix, coerce the key
586+ # to include the `_` suffix. It's not possible to
587+ # natively define the same field with a trailing underscore in protobuf.
588+ # See related issue
589+ # https://github.com/googleapis/python-api-core/issues/227
590+ if f"{ key } _" in self ._meta .fields :
591+ key = f"{ key } _"
592+ pb_type = self ._meta .fields [key ].pb_type
593+
594+ return (key , pb_type )
595+
558596 def __dir__ (self ):
559597 desc = type (self ).pb ().DESCRIPTOR
560598 names = {f_name for f_name in self ._meta .fields .keys ()}
@@ -664,13 +702,14 @@ def __getattr__(self, key):
664702 their Python equivalents. See the ``marshal`` module for
665703 more details.
666704 """
667- try :
668- pb_type = self ._meta .fields [key ].pb_type
669- pb_value = getattr (self ._pb , key )
670- marshal = self ._meta .marshal
671- return marshal .to_python (pb_type , pb_value , absent = key not in self )
672- except KeyError as ex :
673- raise AttributeError (str (ex ))
705+ (key , pb_type ) = self ._get_pb_type_from_key (key )
706+ if pb_type is None :
707+ raise AttributeError (
708+ "Unknown field for {}: {}" .format (self .__class__ .__name__ , key )
709+ )
710+ pb_value = getattr (self ._pb , key )
711+ marshal = self ._meta .marshal
712+ return marshal .to_python (pb_type , pb_value , absent = key not in self )
674713
675714 def __ne__ (self , other ):
676715 """Return True if the messages are unequal, False otherwise."""
@@ -688,7 +727,12 @@ def __setattr__(self, key, value):
688727 if key [0 ] == "_" :
689728 return super ().__setattr__ (key , value )
690729 marshal = self ._meta .marshal
691- pb_type = self ._meta .fields [key ].pb_type
730+ (key , pb_type ) = self ._get_pb_type_from_key (key )
731+ if pb_type is None :
732+ raise AttributeError (
733+ "Unknown field for {}: {}" .format (self .__class__ .__name__ , key )
734+ )
735+
692736 pb_value = marshal .to_proto (pb_type , value )
693737
694738 # Clear the existing field.
0 commit comments