diff --git a/google/api_core/path_template.py b/google/api_core/path_template.py index b8ebb2af..2b90a710 100644 --- a/google/api_core/path_template.py +++ b/google/api_core/path_template.py @@ -298,6 +298,26 @@ def transcode(http_options, message=None, **request_kwargs): # Assign body and query params body = http_option.get("body") + # gapic-generator-python appends an underscore to field names + # that collide with python keywords. See related issue + # https://github.com/googleapis/python-api-core/issues/227 + # Make sure that when the fields are REST encoded, the + # Python-specific trailing underscore is removed before going on the wire. + # `leftovers` can either be a dict or protobuf message. + # When `leftovers` is a dict, the `_` suffix in each key + # is stripped away as it is not possible to natively define a field + # with a trailing underscore in protobuf. + # When `leftovers` is a protobuf message, we need to use an underscore + # suffix when accessing the field in the protobuf message when the + # field has an underscore suffix. + field_suffix = "" + + if isinstance(leftovers, dict): + leftovers = {key.rstrip("_"): val for key, val in leftovers.items()} + elif body: + if hasattr(leftovers, body + "_"): + field_suffix = "_" + if body: if body == "*": request["body"] = leftovers @@ -308,8 +328,8 @@ def transcode(http_options, message=None, **request_kwargs): else: try: if message: - request["body"] = getattr(leftovers, body) - delete_field(leftovers, body) + request["body"] = getattr(leftovers, f"{body}{field_suffix}") + delete_field(leftovers, f"{body}{field_suffix}") else: request["body"] = leftovers.pop(body) except (KeyError, AttributeError): diff --git a/tests/unit/test_path_template.py b/tests/unit/test_path_template.py index 808b36f3..148e2936 100644 --- a/tests/unit/test_path_template.py +++ b/tests/unit/test_path_template.py @@ -17,10 +17,22 @@ import mock import pytest +import proto + from google.api import auth_pb2 from google.api_core import path_template +class Breakpoint(proto.Message): + name = proto.Field(proto.STRING, number=1) + + +class SomeMessage(proto.Message): + breakpoint_ = proto.Field(Breakpoint, number=1) + debuggee_id = proto.Field(proto.STRING, number=2) + stacktrace_ = proto.Field(proto.STRING, number=3) + + @pytest.mark.parametrize( "tmpl, args, kwargs, expected_result", [ @@ -413,6 +425,27 @@ def test_transcode_with_wildcard( {"data": {"id": 1, "info": "some info"}, "foo": "bar"}, ["post", "/v1/no/template", {"id": 1, "info": "some info"}, {"foo": "bar"}], ], + # Single field body with trailing underscore + [ + [["post", "/v1/no/template", "data"]], + None, + {"data": {"id": 1, "info": "some info"}, "foo_": "bar"}, + ["post", "/v1/no/template", {"id": 1, "info": "some info"}, {"foo": "bar"}], + ], + # Single field body with reserved keyword, using message where field name has trailing underscore + [ + [["post", "/v1/no/template", "breakpoint"]], + SomeMessage( + breakpoint_=Breakpoint(name="foo"), debuggee_id="bar", stacktrace_="baz" + )._pb, + {}, + [ + "post", + "/v1/no/template", + Breakpoint(name="foo")._pb, + SomeMessage(debuggee_id="bar", stacktrace_="baz")._pb, + ], + ], [ [["post", "/v1/no/template", "oauth"]], auth_pb2.AuthenticationRule(