diff --git a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/client.py.j2 b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/client.py.j2 index 8f2ae2f41b..b313548972 100644 --- a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/client.py.j2 +++ b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/client.py.j2 @@ -443,7 +443,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. - rpc = self._transport._wrapped_methods[self._transport.{{ method.name|snake_case}}] + rpc = self._transport._wrapped_methods[self._transport.{{ method.transport_safe_name|snake_case}}] {% if method.field_headers %} # Certain fields should be provided within the metadata header; diff --git a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/base.py.j2 b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/base.py.j2 index 7d0ab6ef5e..ea01ac991b 100644 --- a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/base.py.j2 +++ b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/base.py.j2 @@ -120,8 +120,8 @@ class {{ service.name }}Transport(abc.ABC): # Precompute the wrapped methods. self._wrapped_methods = { {% for method in service.methods.values() %} - self.{{ method.name|snake_case }}: gapic_v1.method.wrap_method( - self.{{ method.name|snake_case }}, + self.{{ method.transport_safe_name|snake_case }}: gapic_v1.method.wrap_method( + self.{{ method.transport_safe_name|snake_case }}, {% if method.retry %} default_retry=retries.Retry( {% if method.retry.initial_backoff %}initial={{ method.retry.initial_backoff }},{% endif %} @@ -160,7 +160,7 @@ class {{ service.name }}Transport(abc.ABC): {% for method in service.methods.values() %} @property - def {{ method.name|snake_case }}(self) -> Callable[ + def {{ method.transport_safe_name|snake_case }}(self) -> Callable[ [{{ method.input.ident }}], Union[ {{ method.output.ident }}, diff --git a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/grpc.py.j2 b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/grpc.py.j2 index 0aca3a55f0..cabc67e443 100644 --- a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/grpc.py.j2 +++ b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/grpc.py.j2 @@ -249,7 +249,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): {% for method in service.methods.values() %} @property - def {{ method.name|snake_case }}(self) -> Callable[ + def {{ method.transport_safe_name|snake_case }}(self) -> Callable[ [{{ method.input.ident }}], {{ method.output.ident }}]: r"""Return a callable for the{{ ' ' }} @@ -269,13 +269,13 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): # the request. # gRPC handles serialization and deserialization, so we just need # to pass in the functions for each. - if '{{ method.name|snake_case }}' not in self._stubs: - self._stubs['{{ method.name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}( + if '{{ method.transport_safe_name|snake_case }}' not in self._stubs: + self._stubs['{{ method.transport_safe_name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}( '/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}', request_serializer={{ method.input.ident }}.{% if method.input.ident.python_import.module.endswith('_pb2') %}SerializeToString{% else %}serialize{% endif %}, response_deserializer={{ method.output.ident }}.{% if method.output.ident.python_import.module.endswith('_pb2') %}FromString{% else %}deserialize{% endif %}, ) - return self._stubs['{{ method.name|snake_case }}'] + return self._stubs['{{ method.transport_safe_name|snake_case }}'] {% endfor %} {% if opts.add_iam_methods %} diff --git a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/rest.py.j2 b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/rest.py.j2 index eb34c706f2..9f00f84ebe 100644 --- a/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/rest.py.j2 +++ b/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/rest.py.j2 @@ -396,12 +396,12 @@ class {{service.name}}RestTransport({{service.name}}Transport): {% for method in service.methods.values()|sort(attribute="name") %} @property - def {{method.name | snake_case}}(self) -> Callable[ + def {{method.transport_safe_name | snake_case}}(self) -> Callable[ [{{method.input.ident}}], {{method.output.ident}}]: - stub = self._STUBS.get("{{method.name | snake_case}}") + stub = self._STUBS.get("{{method.transport_safe_name | snake_case}}") if not stub: - stub = self._STUBS["{{method.name | snake_case}}"] = self._{{method.name}}(self._session, self._host, self._interceptor) + stub = self._STUBS["{{method.transport_safe_name | snake_case}}"] = self._{{method.name}}(self._session, self._host, self._interceptor) # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. # In C++ this would require a dynamic_cast diff --git a/gapic/ads-templates/setup.py.j2 b/gapic/ads-templates/setup.py.j2 index 4827ca35ee..9d96e1809e 100644 --- a/gapic/ads-templates/setup.py.j2 +++ b/gapic/ads-templates/setup.py.j2 @@ -19,7 +19,7 @@ setuptools.setup( install_requires=( {# TODO(dovs): remove when 1.x deprecation is complete #} {% if 'rest' in opts.transport %} - 'google-api-core[grpc] >= 2.1.0, < 3.0.0dev', + 'google-api-core[grpc] >= 2.4.0, < 3.0.0dev', {% else %} 'google-api-core[grpc] >= 1.28.0, < 3.0.0dev', {% endif %} diff --git a/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index d615df153b..3592c4a70e 100644 --- a/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -494,7 +494,7 @@ def test_{{ method_name }}(request_type, transport: str = 'grpc'): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -571,7 +571,7 @@ def test_{{ method_name }}_empty_call(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: client.{{ method_name }}() call.assert_called() @@ -600,7 +600,7 @@ def test_{{ method_name }}_field_headers(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: {% if method.void %} call.return_value = None @@ -638,7 +638,7 @@ def test_{{ method_name }}_from_dict_foreign(): ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -668,7 +668,7 @@ def test_{{ method_name }}_flattened(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -746,7 +746,7 @@ def test_{{ method_name }}_pager(transport_name: str = "grpc"): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Set the response to a series of pages. call.side_effect = ( @@ -808,7 +808,7 @@ def test_{{ method_name }}_pages(transport_name: str = "grpc"): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Set the response to a series of pages. {% if method.paged_result_field.map%} @@ -1184,7 +1184,7 @@ def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ide def test_{{ method_name }}_rest_unset_required_fields(): transport = transports.{{ service.rest_transport_name }}(credentials=ga_credentials.AnonymousCredentials) - unset_fields = transport.{{ method.name|snake_case }}._get_unset_required_fields({}) + unset_fields = transport.{{ method.transport_safe_name|snake_case }}._get_unset_required_fields({}) assert set(unset_fields) == (set(({% for param in method.query_params|sort %}"{{ param|camel_case }}", {% endfor %})) & set(({% for param in method.input.required_fields %}"{{param.name|camel_case}}", {% endfor %}))) @@ -1645,7 +1645,7 @@ def test_{{ service.name|snake_case }}_base_transport(): # raise NotImplementedError. methods = ( {% for method in service.methods.values() %} - '{{ method.name|snake_case }}', + '{{ method.transport_safe_name|snake_case }}', {% endfor %} {% if opts.add_iam_methods %} 'set_iam_policy', diff --git a/gapic/schema/wrappers.py b/gapic/schema/wrappers.py index aa379f4787..b9741dacc9 100644 --- a/gapic/schema/wrappers.py +++ b/gapic/schema/wrappers.py @@ -948,6 +948,21 @@ class Method: def __getattr__(self, name): return getattr(self.method_pb, name) + @property + def transport_safe_name(self) -> str: + # These names conflict with other methods in the transport. + # We don't want to disambiguate the names at the client level + # because the disambiguated name is less convenient and user friendly. + # + # Note: this should really be a class variable, + # but python 3.6 can't handle that. + TRANSPORT_UNSAFE_NAMES = { + "CreateChannel", + "GrpcChannel", + "OperationsClient", + } + return f"{self.name}_" if self.name in TRANSPORT_UNSAFE_NAMES else self.name + @property def is_operation_polling_method(self): return self.output.is_extended_operation and self.options.Extensions[ex_ops_pb2.operation_polling_method] diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 index a6e76a595f..348f3bf32f 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 @@ -92,7 +92,7 @@ class {{ service.async_client_name }}: @classmethod def get_mtls_endpoint_and_cert_source(cls, client_options: Optional[ClientOptions] = None): - """Return the API endpoint and client cert source for mutual TLS. + """Return the API endpoint and client cert source for mutual TLS. The client cert source is determined in the following order: (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the @@ -100,7 +100,7 @@ class {{ service.async_client_name }}: (2) if `client_options.client_cert_source` is provided, use the provided one; if the default client cert source exists, use the default one; otherwise the client cert source is None. - + The API endpoint is determined in the following order: (1) if `client_options.api_endpoint` if provided, use the provided one. (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the @@ -118,7 +118,7 @@ class {{ service.async_client_name }}: Returns: Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the client cert source to use. - + Raises: google.auth.exceptions.MutualTLSChannelError: If any errors happen. """ @@ -302,7 +302,7 @@ class {{ service.async_client_name }}: # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = gapic_v1.method_async.wrap_method( - self._client._transport.{{ method.name|snake_case }}, + self._client._transport.{{ method.transport_safe_name|snake_case }}, {% if method.retry %} default_retry=retries.Retry( {% if method.retry.initial_backoff %}initial={{ method.retry.initial_backoff }},{% endif %} diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index 46c4b6cf29..3180125d82 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -477,7 +477,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. - rpc = self._transport._wrapped_methods[self._transport.{{ method.name|snake_case}}] + rpc = self._transport._wrapped_methods[self._transport.{{ method.transport_safe_name|snake_case}}] {% if method.explicit_routing %} header_params = {} diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 index 3b803ebb15..bc37e7602f 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 @@ -120,8 +120,8 @@ class {{ service.name }}Transport(abc.ABC): # Precompute the wrapped methods. self._wrapped_methods = { {% for method in service.methods.values() %} - self.{{ method.name|snake_case }}: gapic_v1.method.wrap_method( - self.{{ method.name|snake_case }}, + self.{{ method.transport_safe_name|snake_case }}: gapic_v1.method.wrap_method( + self.{{ method.transport_safe_name|snake_case }}, {% if method.retry %} default_retry=retries.Retry( {% if method.retry.initial_backoff %}initial={{ method.retry.initial_backoff }},{% endif %} @@ -160,7 +160,7 @@ class {{ service.name }}Transport(abc.ABC): {% for method in service.methods.values() %} @property - def {{ method.name|snake_case }}(self) -> Callable[ + def {{ method.transport_safe_name|snake_case }}(self) -> Callable[ [{{ method.input.ident }}], Union[ {{ method.output.ident }}, diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 index 0aca3a55f0..cabc67e443 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 @@ -249,7 +249,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): {% for method in service.methods.values() %} @property - def {{ method.name|snake_case }}(self) -> Callable[ + def {{ method.transport_safe_name|snake_case }}(self) -> Callable[ [{{ method.input.ident }}], {{ method.output.ident }}]: r"""Return a callable for the{{ ' ' }} @@ -269,13 +269,13 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): # the request. # gRPC handles serialization and deserialization, so we just need # to pass in the functions for each. - if '{{ method.name|snake_case }}' not in self._stubs: - self._stubs['{{ method.name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}( + if '{{ method.transport_safe_name|snake_case }}' not in self._stubs: + self._stubs['{{ method.transport_safe_name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}( '/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}', request_serializer={{ method.input.ident }}.{% if method.input.ident.python_import.module.endswith('_pb2') %}SerializeToString{% else %}serialize{% endif %}, response_deserializer={{ method.output.ident }}.{% if method.output.ident.python_import.module.endswith('_pb2') %}FromString{% else %}deserialize{% endif %}, ) - return self._stubs['{{ method.name|snake_case }}'] + return self._stubs['{{ method.transport_safe_name|snake_case }}'] {% endfor %} {% if opts.add_iam_methods %} diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 index a6271fe752..9c9e1e164c 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 @@ -250,7 +250,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): {% for method in service.methods.values() %} @property - def {{ method.name|snake_case }}(self) -> Callable[ + def {{ method.transport_safe_name|snake_case }}(self) -> Callable[ [{{ method.input.ident }}], Awaitable[{{ method.output.ident }}]]: r"""Return a callable for the{{ ' ' }} @@ -270,13 +270,13 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): # the request. # gRPC handles serialization and deserialization, so we just need # to pass in the functions for each. - if '{{ method.name|snake_case }}' not in self._stubs: - self._stubs['{{ method.name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}( + if '{{ method.transport_safe_name|snake_case }}' not in self._stubs: + self._stubs['{{ method.transport_safe_name|snake_case }}'] = self.grpc_channel.{{ method.grpc_stub_type }}( '/{{ '.'.join(method.meta.address.package) }}.{{ service.name }}/{{ method.name }}', request_serializer={{ method.input.ident }}.{% if method.input.ident.python_import.module.endswith('_pb2') %}SerializeToString{% else %}serialize{% endif %}, response_deserializer={{ method.output.ident }}.{% if method.output.ident.python_import.module.endswith('_pb2') %}FromString{% else %}deserialize{% endif %}, ) - return self._stubs['{{ method.name|snake_case }}'] + return self._stubs['{{ method.transport_safe_name|snake_case }}'] {% endfor %} {% if opts.add_iam_methods %} diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 index eb34c706f2..dc9c583579 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 @@ -396,12 +396,12 @@ class {{service.name}}RestTransport({{service.name}}Transport): {% for method in service.methods.values()|sort(attribute="name") %} @property - def {{method.name | snake_case}}(self) -> Callable[ + def {{method.transport_safe_name|snake_case}}(self) -> Callable[ [{{method.input.ident}}], {{method.output.ident}}]: - stub = self._STUBS.get("{{method.name | snake_case}}") + stub = self._STUBS.get("{{method.transport_safe_name|snake_case}}") if not stub: - stub = self._STUBS["{{method.name | snake_case}}"] = self._{{method.name}}(self._session, self._host, self._interceptor) + stub = self._STUBS["{{method.transport_safe_name|snake_case}}"] = self._{{method.name}}(self._session, self._host, self._interceptor) # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. # In C++ this would require a dynamic_cast diff --git a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 7bde90ac43..77a125d222 100644 --- a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -576,7 +576,7 @@ def test_{{ method_name }}(request_type, transport: str = 'grpc'): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -653,7 +653,7 @@ def test_{{ method_name }}_empty_call(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: client.{{ method_name }}() call.assert_called() @@ -682,7 +682,7 @@ async def test_{{ method_name }}_async(transport: str = 'grpc_asyncio', request_ # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -769,7 +769,7 @@ def test_{{ method.name|snake_case }}_routing_parameters(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: {% if method.void %} call.return_value = None @@ -810,7 +810,7 @@ def test_{{ method_name }}_field_headers(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: {% if method.void %} call.return_value = None @@ -855,7 +855,7 @@ async def test_{{ method_name }}_field_headers_async(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: {% if method.void %} call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) @@ -892,7 +892,7 @@ def test_{{ method_name }}_from_dict_foreign(): ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -922,7 +922,7 @@ def test_{{ method_name }}_flattened(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -995,7 +995,7 @@ async def test_{{ method_name }}_flattened_async(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Designate an appropriate return value for the call. {% if method.void %} @@ -1091,7 +1091,7 @@ def test_{{ method_name }}_pager(transport_name: str = "grpc"): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Set the response to a series of pages. call.side_effect = ( @@ -1151,7 +1151,7 @@ def test_{{ method_name }}_pages(transport_name: str = "grpc"): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__') as call: # Set the response to a series of pages. {% if method.paged_result_field.map %} @@ -1224,7 +1224,7 @@ async def test_{{ method_name }}_async_pager(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__', new_callable=mock.AsyncMock) as call: # Set the response to a series of pages. call.side_effect = ( @@ -1314,7 +1314,7 @@ async def test_{{ method_name }}_async_pages(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client.transport.{{ method.name|snake_case }}), + type(client.transport.{{ method.transport_safe_name|snake_case }}), '__call__', new_callable=mock.AsyncMock) as call: # Set the response to a series of pages. call.side_effect = ( @@ -1621,7 +1621,7 @@ def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ide def test_{{ method_name }}_rest_unset_required_fields(): transport = transports.{{ service.rest_transport_name }}(credentials=ga_credentials.AnonymousCredentials) - unset_fields = transport.{{ method.name|snake_case }}._get_unset_required_fields({}) + unset_fields = transport.{{ method.transport_safe_name|snake_case }}._get_unset_required_fields({}) assert set(unset_fields) == (set(({% for param in method.query_params|sort %}"{{ param|camel_case }}", {% endfor %})) & set(({% for param in method.input.required_fields %}"{{ param.name|camel_case }}", {% endfor %}))) {% endif %}{# required_fields #} @@ -2114,7 +2114,7 @@ def test_{{ service.name|snake_case }}_base_transport(): # raise NotImplementedError. methods = ( {% for method in service.methods.values() %} - '{{ method.name|snake_case }}', + '{{ method.transport_safe_name|snake_case }}', {% endfor %} {% if opts.add_iam_methods %} 'set_iam_policy', diff --git a/noxfile.py b/noxfile.py index 718469855c..3e1b9638c6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -130,9 +130,8 @@ def __call__(self, frag): return "".join(outputs) -# TODO(dovs): ads templates @nox.session(python=ALL_PYTHON) -def fragment(session): +def fragment(session, use_ads_templates=False): session.install( "coverage", "pytest", @@ -144,47 +143,25 @@ def fragment(session): ) session.install("-e", ".") + frag_files = [Path(f) for f in session.posargs] if session.posargs else FRAGMENT_FILES + if os.environ.get("PARALLEL_FRAGMENT_TESTS", "false").lower() == "true": with ThreadPoolExecutor() as p: - all_outs = p.map(FragTester(session, False), FRAGMENT_FILES) + all_outs = p.map(FragTester(session, use_ads_templates), frag_files) output = "".join(all_outs) session.log(output) else: - tester = FragTester(session, False) - for frag in FRAGMENT_FILES: + tester = FragTester(session, use_ads_templates) + for frag in frag_files: session.log(tester(frag)) @nox.session(python=ALL_PYTHON[1:]) def fragment_alternative_templates(session): - session.install( - "coverage", - "pytest", - "pytest-cov", - "pytest-xdist", - "asyncmock", - "pytest-asyncio", - "grpcio-tools", - ) - session.install("-e", ".") - - if os.environ.get("PARALLEL_FRAGMENT_TESTS", "false").lower() == "true": - with ThreadPoolExecutor() as p: - all_outs = p.map(FragTester(session, True), FRAGMENT_FILES) - - output = "".join(all_outs) - session.log(output) - else: - tester = FragTester(session, True) - for frag in FRAGMENT_FILES: - session.log(tester(frag)) + fragment(session, use_ads_templates=True) -# TODO(yon-mg): -add compute context manager that includes rest transport -# -add compute unit tests -# (to test against temporarily while rest transport is incomplete) -# (to be removed once all features are complete) @contextmanager def showcase_library( session, templates="DEFAULT", other_opts: typing.Iterable[str] = () diff --git a/tests/fragments/google/longrunning/operations.proto b/tests/fragments/google/longrunning/operations.proto new file mode 100644 index 0000000000..299eefb2e5 --- /dev/null +++ b/tests/fragments/google/longrunning/operations.proto @@ -0,0 +1,247 @@ +// Copyright 2019 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 +// +// http://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. + +syntax = "proto3"; + +package google.longrunning; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "google/rpc/status.proto"; +import "google/protobuf/descriptor.proto"; + +option cc_enable_arenas = true; +option csharp_namespace = "Google.LongRunning"; +option go_package = "google.golang.org/genproto/googleapis/longrunning;longrunning"; +option java_multiple_files = true; +option java_outer_classname = "OperationsProto"; +option java_package = "com.google.longrunning"; +option php_namespace = "Google\\LongRunning"; + +extend google.protobuf.MethodOptions { + // Additional information regarding long-running operations. + // In particular, this specifies the types that are returned from + // long-running operations. + // + // Required for methods that return `google.longrunning.Operation`; invalid + // otherwise. + google.longrunning.OperationInfo operation_info = 1049; +} + +// Manages long-running operations with an API service. +// +// When an API method normally takes long time to complete, it can be designed +// to return [Operation][google.longrunning.Operation] to the client, and the client can use this +// interface to receive the real response asynchronously by polling the +// operation resource, or pass the operation resource to another API (such as +// Google Cloud Pub/Sub API) to receive the response. Any API service that +// returns long-running operations should implement the `Operations` interface +// so developers can have a consistent client experience. +service Operations { + option (google.api.default_host) = "longrunning.googleapis.com"; + + // Lists operations that match the specified filter in the request. If the + // server doesn't support this method, it returns `UNIMPLEMENTED`. + // + // NOTE: the `name` binding allows API services to override the binding + // to use different resource name schemes, such as `users/*/operations`. To + // override the binding, API services can add a binding such as + // `"/v1/{name=users/*}/operations"` to their service configuration. + // For backwards compatibility, the default name includes the operations + // collection id, however overriding users must ensure the name binding + // is the parent resource, without the operations collection id. + rpc ListOperations(ListOperationsRequest) returns (ListOperationsResponse) { + option (google.api.http) = { + get: "/v1/{name=operations}" + }; + option (google.api.method_signature) = "name,filter"; + } + + // Gets the latest state of a long-running operation. Clients can use this + // method to poll the operation result at intervals as recommended by the API + // service. + rpc GetOperation(GetOperationRequest) returns (Operation) { + option (google.api.http) = { + get: "/v1/{name=operations/**}" + }; + option (google.api.method_signature) = "name"; + } + + // Deletes a long-running operation. This method indicates that the client is + // no longer interested in the operation result. It does not cancel the + // operation. If the server doesn't support this method, it returns + // `google.rpc.Code.UNIMPLEMENTED`. + rpc DeleteOperation(DeleteOperationRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1/{name=operations/**}" + }; + option (google.api.method_signature) = "name"; + } + + // Starts asynchronous cancellation on a long-running operation. The server + // makes a best effort to cancel the operation, but success is not + // guaranteed. If the server doesn't support this method, it returns + // `google.rpc.Code.UNIMPLEMENTED`. Clients can use + // [Operations.GetOperation][google.longrunning.Operations.GetOperation] or + // other methods to check whether the cancellation succeeded or whether the + // operation completed despite cancellation. On successful cancellation, + // the operation is not deleted; instead, it becomes an operation with + // an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, + // corresponding to `Code.CANCELLED`. + rpc CancelOperation(CancelOperationRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1/{name=operations/**}:cancel" + body: "*" + }; + option (google.api.method_signature) = "name"; + } + + // Waits for the specified long-running operation until it is done or reaches + // at most a specified timeout, returning the latest state. If the operation + // is already done, the latest state is immediately returned. If the timeout + // specified is greater than the default HTTP/RPC timeout, the HTTP/RPC + // timeout is used. If the server does not support this method, it returns + // `google.rpc.Code.UNIMPLEMENTED`. + // Note that this method is on a best-effort basis. It may return the latest + // state before the specified timeout (including immediately), meaning even an + // immediate response is no guarantee that the operation is done. + rpc WaitOperation(WaitOperationRequest) returns (Operation) { + } +} + +// This resource represents a long-running operation that is the result of a +// network API call. +message Operation { + // The server-assigned name, which is only unique within the same service that + // originally returns it. If you use the default HTTP mapping, the + // `name` should be a resource name ending with `operations/{unique_id}`. + string name = 1; + + // Service-specific metadata associated with the operation. It typically + // contains progress information and common metadata such as create time. + // Some services might not provide such metadata. Any method that returns a + // long-running operation should document the metadata type, if any. + google.protobuf.Any metadata = 2; + + // If the value is `false`, it means the operation is still in progress. + // If `true`, the operation is completed, and either `error` or `response` is + // available. + bool done = 3; + + // The operation result, which can be either an `error` or a valid `response`. + // If `done` == `false`, neither `error` nor `response` is set. + // If `done` == `true`, exactly one of `error` or `response` is set. + oneof result { + // The error result of the operation in case of failure or cancellation. + google.rpc.Status error = 4; + + // The normal response of the operation in case of success. If the original + // method returns no data on success, such as `Delete`, the response is + // `google.protobuf.Empty`. If the original method is standard + // `Get`/`Create`/`Update`, the response should be the resource. For other + // methods, the response should have the type `XxxResponse`, where `Xxx` + // is the original method name. For example, if the original method name + // is `TakeSnapshot()`, the inferred response type is + // `TakeSnapshotResponse`. + google.protobuf.Any response = 5; + } +} + +// The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation]. +message GetOperationRequest { + // The name of the operation resource. + string name = 1; +} + +// The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. +message ListOperationsRequest { + // The name of the operation's parent resource. + string name = 4; + + // The standard list filter. + string filter = 1; + + // The standard list page size. + int32 page_size = 2; + + // The standard list page token. + string page_token = 3; +} + +// The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. +message ListOperationsResponse { + // A list of operations that matches the specified filter in the request. + repeated Operation operations = 1; + + // The standard List next-page token. + string next_page_token = 2; +} + +// The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation]. +message CancelOperationRequest { + // The name of the operation resource to be cancelled. + string name = 1; +} + +// The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation]. +message DeleteOperationRequest { + // The name of the operation resource to be deleted. + string name = 1; +} + +// The request message for [Operations.WaitOperation][google.longrunning.Operations.WaitOperation]. +message WaitOperationRequest { + // The name of the operation resource to wait on. + string name = 1; + + // The maximum duration to wait before timing out. If left blank, the wait + // will be at most the time permitted by the underlying HTTP/RPC protocol. + // If RPC context deadline is also specified, the shorter one will be used. + google.protobuf.Duration timeout = 2; +} + +// A message representing the message types used by a long-running operation. +// +// Example: +// +// rpc LongRunningRecognize(LongRunningRecognizeRequest) +// returns (google.longrunning.Operation) { +// option (google.longrunning.operation_info) = { +// response_type: "LongRunningRecognizeResponse" +// metadata_type: "LongRunningRecognizeMetadata" +// }; +// } +message OperationInfo { + // Required. The message name of the primary return type for this + // long-running operation. + // This type will be used to deserialize the LRO's response. + // + // If the response is in a different package from the rpc, a fully-qualified + // message name must be used (e.g. `google.protobuf.Struct`). + // + // Note: Altering this value constitutes a breaking change. + string response_type = 1; + + // Required. The message name of the metadata type for this long-running + // operation. + // + // If the response is in a different package from the rpc, a fully-qualified + // message name must be used (e.g. `google.protobuf.Struct`). + // + // Note: Altering this value constitutes a breaking change. + string metadata_type = 2; +} diff --git a/tests/fragments/google/protobuf/any.proto b/tests/fragments/google/protobuf/any.proto new file mode 100644 index 0000000000..6ed8a23cf5 --- /dev/null +++ b/tests/fragments/google/protobuf/any.proto @@ -0,0 +1,158 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/tests/fragments/google/rpc/status.proto b/tests/fragments/google/rpc/status.proto new file mode 100644 index 0000000000..3b1f7a932f --- /dev/null +++ b/tests/fragments/google/rpc/status.proto @@ -0,0 +1,47 @@ +// Copyright 2020 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 +// +// http://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. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; +option java_multiple_files = true; +option java_outer_classname = "StatusProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The `Status` type defines a logical error model that is suitable for +// different programming environments, including REST APIs and RPC APIs. It is +// used by [gRPC](https://github.com/grpc). Each `Status` message contains +// three pieces of data: error code, error message, and error details. +// +// You can find out more about this error model and how to work with it in the +// [API Design Guide](https://cloud.google.com/apis/design/errors). +message Status { + // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + int32 code = 1; + + // A developer-facing error message, which should be in English. Any + // user-facing error message should be localized and sent in the + // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + string message = 2; + + // A list of messages that carry the error details. There is a common set of + // message types for APIs to use. + repeated google.protobuf.Any details = 3; +} diff --git a/tests/fragments/test_reserved_method_names.proto b/tests/fragments/test_reserved_method_names.proto new file mode 100644 index 0000000000..d8f23494fe --- /dev/null +++ b/tests/fragments/test_reserved_method_names.proto @@ -0,0 +1,82 @@ +// Copyright (C) 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 +// +// http://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. + +syntax = "proto3"; + +package google.fragment; + +import "google/api/client.proto"; +import "google/api/annotations.proto"; +import "google/longrunning/operations.proto"; + +service MyService { + option (google.api.default_host) = "my.example.com"; + + rpc CreateChannel(CreateChannelRequest) returns (CreateChannelResponse) { + option (google.api.http) = { + body: "*" + post: "/channels/v1/channel/{channel}" + }; + }; + + rpc GrpcChannel(GrpcChannelRequest) returns (GrpcChannelResponse) { + option (google.api.http) = { + body: "*" + post: "/channels/v1/grpc_channel/{grpc_channel}" + }; + }; + + rpc OperationsClient(OperationsClientRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + body: "*" + post: "/clients/v1/operations_clients/{operations_client}" + }; + option (google.longrunning.operation_info) = { + response_type: "google.fragment.OperationsClientResponse" + metadata_type: "google.fragment.OperationsMetadata" + }; + }; + +} + +message CreateChannelRequest { + string channel = 1; + string info = 2; +} + +message CreateChannelResponse { + string info = 1; +} + +message GrpcChannelRequest { + string grpc_channel = 1; + string info = 2; +} + +message GrpcChannelResponse { + string info = 1; +} + +message OperationsClientRequest { + string operations_client = 1; + string info = 2; +} + +message OperationsClientResponse { + string info = 1; +} + +message OperationsMetadata { + string data = 1; +} \ No newline at end of file diff --git a/tests/unit/schema/wrappers/test_method.py b/tests/unit/schema/wrappers/test_method.py index 1caf1cce36..2aba8aa44c 100644 --- a/tests/unit/schema/wrappers/test_method.py +++ b/tests/unit/schema/wrappers/test_method.py @@ -862,3 +862,21 @@ def test_is_operation_polling_method(): ) assert not invalid_method.is_operation_polling_method + + +def test_transport_safe_name(): + unsafe_methods = { + name: make_method(name=name) + for name in ["CreateChannel", "GrpcChannel", "OperationsClient"] + } + + safe_methods = { + name: make_method(name=name) + for name in ["Call", "Put", "Hold", "Raise"] + } + + for name, method in safe_methods.items(): + assert method.transport_safe_name == name + + for name, method in unsafe_methods.items(): + assert method.transport_safe_name == f"{name}_"