@@ -693,8 +693,28 @@ def __repr__(self) -> str:
693693 def __getattribute__ (self , name : str ) -> Any :
694694 """
695695 Lazily initialize default values to avoid infinite recursion for recursive
696- message types
696+ message types.
697+ Raise :class:`AttributeError` on attempts to access unset ``oneof`` fields.
697698 """
699+ try :
700+ group_current = super ().__getattribute__ ("_group_current" )
701+ except AttributeError :
702+ pass
703+ else :
704+ if name not in {"__class__" , "_betterproto" }:
705+ group = self ._betterproto .oneof_group_by_field .get (name )
706+ if group is not None and group_current [group ] != name :
707+ if sys .version_info < (3 , 10 ):
708+ raise AttributeError (
709+ f"{ group !r} is set to { group_current [group ]!r} , not { name !r} "
710+ )
711+ else :
712+ raise AttributeError (
713+ f"{ group !r} is set to { group_current [group ]!r} , not { name !r} " ,
714+ name = name ,
715+ obj = self ,
716+ )
717+
698718 value = super ().__getattribute__ (name )
699719 if value is not PLACEHOLDER :
700720 return value
@@ -761,7 +781,10 @@ def __bytes__(self) -> bytes:
761781 """
762782 output = bytearray ()
763783 for field_name , meta in self ._betterproto .meta_by_field_name .items ():
764- value = getattr (self , field_name )
784+ try :
785+ value = getattr (self , field_name )
786+ except AttributeError :
787+ continue
765788
766789 if value is None :
767790 # Optional items should be skipped. This is used for the Google
@@ -775,9 +798,7 @@ def __bytes__(self) -> bytes:
775798 # Note that proto3 field presence/optional fields are put in a
776799 # synthetic single-item oneof by protoc, which helps us ensure we
777800 # send the value even if the value is the default zero value.
778- selected_in_group = (
779- meta .group and self ._group_current [meta .group ] == field_name
780- )
801+ selected_in_group = bool (meta .group )
781802
782803 # Empty messages can still be sent on the wire if they were
783804 # set (or received empty).
@@ -1016,7 +1037,12 @@ def parse(self: T, data: bytes) -> T:
10161037 parsed .wire_type , meta , field_name , parsed .value
10171038 )
10181039
1019- current = getattr (self , field_name )
1040+ try :
1041+ current = getattr (self , field_name )
1042+ except AttributeError :
1043+ current = self ._get_field_default (field_name )
1044+ setattr (self , field_name , current )
1045+
10201046 if meta .proto_type == TYPE_MAP :
10211047 # Value represents a single key/value pair entry in the map.
10221048 current [value .key ] = value .value
@@ -1077,7 +1103,10 @@ def to_dict(
10771103 defaults = self ._betterproto .default_gen
10781104 for field_name , meta in self ._betterproto .meta_by_field_name .items ():
10791105 field_is_repeated = defaults [field_name ] is list
1080- value = getattr (self , field_name )
1106+ try :
1107+ value = getattr (self , field_name )
1108+ except AttributeError :
1109+ value = self ._get_field_default (field_name )
10811110 cased_name = casing (field_name ).rstrip ("_" ) # type: ignore
10821111 if meta .proto_type == TYPE_MESSAGE :
10831112 if isinstance (value , datetime ):
@@ -1209,7 +1238,7 @@ def from_dict(self: T, value: Mapping[str, Any]) -> T:
12091238
12101239 if value [key ] is not None :
12111240 if meta .proto_type == TYPE_MESSAGE :
1212- v = getattr ( self , field_name )
1241+ v = self . _get_field_default ( field_name )
12131242 cls = self ._betterproto .cls_by_field [field_name ]
12141243 if isinstance (v , list ):
12151244 if cls == datetime :
@@ -1486,7 +1515,6 @@ def _validate_field_groups(cls, values):
14861515 field_name_to_meta = cls ._betterproto_meta .meta_by_field_name # type: ignore
14871516
14881517 for group , field_set in group_to_one_ofs .items ():
1489-
14901518 if len (field_set ) == 1 :
14911519 (field ,) = field_set
14921520 field_name = field .name
0 commit comments