Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.

Commit 949e811

Browse files
committed
fix: additional logic to mitigate collisions with reserved terms
1 parent 71591e1 commit 949e811

2 files changed

Lines changed: 84 additions & 5 deletions

File tree

proto/message.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ def __init__(
530530
# Underscores may be appended to field names
531531
# that collide with python or proto-plus keywords.
532532
# In case a key only exists with a `_` suffix, coerce the key
533-
# to include the `_` suffix. Is not possible to
533+
# to include the `_` suffix. It's not possible to
534534
# natively define the same field with a trailing underscore in protobuf.
535535
# See related issue
536536
# https://github.com/googleapis/python-api-core/issues/227
@@ -545,7 +545,27 @@ def __init__(
545545
"Unknown field for {}: {}".format(self.__class__.__name__, key)
546546
)
547547

548-
pb_value = marshal.to_proto(pb_type, value)
548+
try:
549+
pb_value = marshal.to_proto(pb_type, value)
550+
except ValueError:
551+
# Underscores may be appended to field names
552+
# that collide with python or proto-plus keywords.
553+
# In case a key only exists with a `_` suffix, coerce the key
554+
# to include the `_` suffix. It's not possible to
555+
# natively define the same field with a trailing underscore in protobuf.
556+
# See related issue
557+
# https://github.com/googleapis/python-api-core/issues/227
558+
if isinstance(value, dict):
559+
keys_to_update = []
560+
for item in value:
561+
if not hasattr(pb_type, item) and hasattr(pb_type, f"{item}_"):
562+
keys_to_update.append(item)
563+
for item in keys_to_update:
564+
value[f"{item}_"] = value[item]
565+
del value[item]
566+
567+
pb_value = marshal.to_proto(pb_type, value)
568+
549569
if pb_value is not None:
550570
params[key] = pb_value
551571

@@ -662,7 +682,24 @@ def __getattr__(self, key):
662682
more details.
663683
"""
664684
try:
665-
pb_type = self._meta.fields[key].pb_type
685+
try:
686+
pb_type = self._meta.fields[key].pb_type
687+
except KeyError:
688+
# Underscores may be appended to field names
689+
# that collide with python or proto-plus keywords.
690+
# In case a key only exists with a `_` suffix, coerce the key
691+
# to include the `_` suffix. It's not possible to
692+
# natively define the same field with a trailing underscore in protobuf.
693+
# See related issue
694+
# https://github.com/googleapis/python-api-core/issues/227
695+
if f"{key}_" in self._meta.fields:
696+
key = f"{key}_"
697+
pb_type = self._meta.fields[key].pb_type
698+
else:
699+
raise KeyError(
700+
"Unknown field for {}: {}".format(self.__class__.__name__, key)
701+
)
702+
666703
pb_value = getattr(self._pb, key)
667704
marshal = self._meta.marshal
668705
return marshal.to_python(pb_type, pb_value, absent=key not in self)
@@ -685,7 +722,24 @@ def __setattr__(self, key, value):
685722
if key[0] == "_":
686723
return super().__setattr__(key, value)
687724
marshal = self._meta.marshal
688-
pb_type = self._meta.fields[key].pb_type
725+
try:
726+
pb_type = self._meta.fields[key].pb_type
727+
except KeyError:
728+
# Underscores may be appended to field names
729+
# that collide with python or proto-plus keywords.
730+
# In case a key only exists with a `_` suffix, coerce the key
731+
# to include the `_` suffix. It's not possible to
732+
# natively define the same field with a trailing underscore in protobuf.
733+
# See related issue
734+
# https://github.com/googleapis/python-api-core/issues/227
735+
if f"{key}_" in self._meta.fields:
736+
key = f"{key}_"
737+
pb_type = self._meta.fields[key].pb_type
738+
else:
739+
raise KeyError(
740+
"Unknown field for {}: {}".format(self.__class__.__name__, key)
741+
)
742+
689743
pb_value = marshal.to_proto(pb_type, value)
690744

691745
# Clear the existing field.

tests/test_fields_mitigate_collision.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# Underscores may be appended to field names
1919
# that collide with python or proto-plus keywords.
2020
# In case a key only exists with a `_` suffix, coerce the key
21-
# to include the `_` suffix. Is not possible to
21+
# to include the `_` suffix. It's not possible to
2222
# natively define the same field with a trailing underscore in protobuf.
2323
# See related issue
2424
# https://github.com/googleapis/python-api-core/issues/227
@@ -27,10 +27,35 @@ class TestMessage(proto.Message):
2727
spam_ = proto.Field(proto.STRING, number=1)
2828
eggs = proto.Field(proto.STRING, number=2)
2929

30+
class TextStream(proto.Message):
31+
text_stream = proto.Field(TestMessage, number=1)
32+
3033
obj = TestMessage(spam_="has_spam")
3134
obj.eggs = "has_eggs"
3235
assert obj.spam_ == "has_spam"
3336

3437
# Test that `spam` is coerced to `spam_`
3538
modified_obj = TestMessage({"spam": "has_spam", "eggs": "has_eggs"})
3639
assert modified_obj.spam_ == "has_spam"
40+
41+
# Test __setattr__ and __getattr___
42+
modified_obj.__setattr__("spam", "no_spam")
43+
modified_obj.__getattr__("spam") == "no_spam"
44+
45+
modified_obj.__setattr__("spam_", "yes_spam")
46+
modified_obj.__getattr__("spam_") == "yes_spam"
47+
48+
# Try nested values
49+
modified_obj = TextStream(text_stream=TestMessage({"spam": "has_spam", "eggs": "has_eggs"}))
50+
assert modified_obj.text_stream.spam_ == "has_spam"
51+
52+
# Test __setattr__ and __getattr___
53+
modified_obj.text_stream.__setattr__("spam", "no_spam")
54+
modified_obj.text_stream.__getattr__("spam") == "no_spam"
55+
56+
modified_obj.text_stream.__setattr__("spam_", "yes_spam")
57+
modified_obj.text_stream.__getattr__("spam_") == "yes_spam"
58+
59+
# Try using dict
60+
modified_obj = TextStream(text_stream={"spam": "has_spam", "eggs": "has_eggs"})
61+
assert modified_obj.text_stream.spam_ == "has_spam"

0 commit comments

Comments
 (0)