diff --git a/proto/message.py b/proto/message.py index 5135a542..7293fdb5 100644 --- a/proto/message.py +++ b/proto/message.py @@ -527,12 +527,23 @@ def __init__( try: pb_type = self._meta.fields[key].pb_type except KeyError: - if ignore_unknown_fields: - continue - - raise ValueError( - "Unknown field for {}: {}".format(self.__class__.__name__, key) - ) + # Underscores may be appended to field names + # that collide with python or proto-plus keywords. + # In case a key only exists with a `_` suffix, coerce the key + # to include the `_` suffix. Is not possible to + # natively define the same field with a trailing underscore in protobuf. + # See related issue + # https://github.com/googleapis/python-api-core/issues/227 + if f"{key}_" in self._meta.fields: + key = f"{key}_" + pb_type = self._meta.fields[key].pb_type + else: + if ignore_unknown_fields: + continue + + raise ValueError( + "Unknown field for {}: {}".format(self.__class__.__name__, key) + ) pb_value = marshal.to_proto(pb_type, value) if pb_value is not None: diff --git a/tests/test_fields_mitigate_collision.py b/tests/test_fields_mitigate_collision.py new file mode 100644 index 00000000..0dca71df --- /dev/null +++ b/tests/test_fields_mitigate_collision.py @@ -0,0 +1,36 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import proto + + +# Underscores may be appended to field names +# that collide with python or proto-plus keywords. +# In case a key only exists with a `_` suffix, coerce the key +# to include the `_` suffix. Is not possible to +# natively define the same field with a trailing underscore in protobuf. +# See related issue +# https://github.com/googleapis/python-api-core/issues/227 +def test_fields_mitigate_collision(): + class TestMessage(proto.Message): + spam_ = proto.Field(proto.STRING, number=1) + eggs = proto.Field(proto.STRING, number=2) + + obj = TestMessage(spam_="has_spam") + obj.eggs = "has_eggs" + assert obj.spam_ == "has_spam" + + # Test that `spam` is coerced to `spam_` + modified_obj = TestMessage({"spam": "has_spam", "eggs": "has_eggs"}) + assert modified_obj.spam_ == "has_spam"