diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 index fe12fbdcc411..9c93aaea9c1d 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 @@ -179,20 +179,34 @@ class {{ service.name }}Transport(abc.ABC): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -204,20 +218,34 @@ class {{ service.name }}Transport(abc.ABC): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 index f54cf0d0c739..c6ea02ef3317 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2 @@ -426,6 +426,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport): credentials=transport._credentials, client_info=transport._client_info, client_cert_source_for_mtls=transport._client_cert_source_for_mtls, + client_options=getattr(transport, "_client_options", None), ) return transport._rest_transport.{{ method.transport_safe_name|snake_case }}(*args, **kwargs) self._stubs['{{ method.transport_safe_name|snake_case }}'] = _RestStub() diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 index ba5aebc31a4d..894fdd9de946 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2 @@ -435,6 +435,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport): host=transport._host, credentials=transport._credentials, client_info=transport._client_info, + client_options=getattr(transport, "_client_options", None), ) return transport._rest_transport.{{ method.transport_safe_name|snake_case }}(*args, **kwargs) self._stubs['{{ method.transport_safe_name|snake_case }}'] = _AsyncRestStub() diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 53accce6375d..5b3c39c061ec 100644 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -1817,6 +1817,7 @@ async def test_{{ service.name|snake_case }}_{{ method.name|snake_case }}_grpc_a host=transport._host, credentials=transport._credentials, client_info=transport._client_info, + client_options=getattr(transport, "_client_options", None), ) assert mock_rest_transport.{{ method.transport_safe_name|snake_case }}.call_count == 2 await transport.close() @@ -2475,4 +2476,200 @@ def test_api_key_credentials(client_class, transport_class): api_audience=None, ) + +def test_{{ service.name|snake_case }}_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="{{ opts.transport[0] }}" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.{{ service.name }}{{ opts.transport[0].capitalize() }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "{{ opts.transport[0] }}" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.{{ service.name }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_{{ service.name|snake_case }}_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.{{ service.name }}{{ opts.transport[0].capitalize() }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.{{ service.name }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "{{ opts.transport[0] }}" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.{{ service.name }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +{% if 'grpc' in opts.transport %} +def test_{{ service.name|snake_case }}_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.{{ service.grpc_asyncio_transport_name }}( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.{{ service.name }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_{{ service.name|snake_case }}_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.{{ service.grpc_asyncio_transport_name }}( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.{{ service.name }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.{{ service.name }}Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs +{% endif %} + {% endblock %} diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/base.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/base.py index 8840dbfc0fd7..515c173224af 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/base.py @@ -150,20 +150,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -175,20 +189,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py b/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py index ea507dee5887..b19ad4ebf79f 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py @@ -18410,3 +18410,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_asset_service_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.AssetServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.AssetServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_asset_service_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.AssetServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.AssetServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.AssetServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_asset_service_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.AssetServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.AssetServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_asset_service_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.AssetServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.AssetServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.AssetServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/base.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/base.py index 5d577415bdad..9ad2c974a10c 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/base.py @@ -147,20 +147,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -172,20 +186,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py b/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py index d374eae044ac..4b13be93cc7c 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py @@ -4456,3 +4456,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_iam_credentials_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.IAMCredentialsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.IAMCredentialsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_iam_credentials_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.IAMCredentialsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.IAMCredentialsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.IAMCredentialsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_iam_credentials_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.IAMCredentialsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.IAMCredentialsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_iam_credentials_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.IAMCredentialsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.IAMCredentialsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.IAMCredentialsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/base.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/base.py index 9aac532d8376..963fc7ddb134 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/base.py @@ -162,20 +162,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -187,20 +201,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py b/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py index 937e3558b3c9..52b232686c64 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py @@ -33513,3 +33513,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_eventarc_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.EventarcGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.EventarcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_eventarc_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.EventarcGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.EventarcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.EventarcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_eventarc_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.EventarcGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.EventarcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_eventarc_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.EventarcGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.EventarcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.EventarcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/base.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/base.py index 5928c3dd2280..4a5dbf0b6ca1 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/base.py @@ -153,20 +153,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -178,20 +192,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/base.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/base.py index 2547e10f105b..4f1882897e01 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/base.py @@ -153,20 +153,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -178,20 +192,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py index 64f32a0a6022..98e6b8826af6 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py @@ -153,20 +153,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -178,20 +192,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py index 6a0372b7a0e7..afb0002126cf 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -14007,3 +14007,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_config_service_v2_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.ConfigServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_config_service_v2_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.ConfigServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_config_service_v2_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.ConfigServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_config_service_v2_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.ConfigServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 20b3b64dd51e..4f3443ffa649 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -4447,3 +4447,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_logging_service_v2_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.LoggingServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_logging_service_v2_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.LoggingServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_logging_service_v2_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.LoggingServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_logging_service_v2_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.LoggingServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 94dd963c9ac2..a54c2753e0e5 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -4247,3 +4247,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_metrics_service_v2_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.MetricsServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_metrics_service_v2_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.MetricsServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_metrics_service_v2_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.MetricsServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_metrics_service_v2_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.MetricsServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/base.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/base.py index 5928c3dd2280..4a5dbf0b6ca1 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/base.py @@ -153,20 +153,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -178,20 +192,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/base.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/base.py index 2547e10f105b..4f1882897e01 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/base.py @@ -153,20 +153,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -178,20 +192,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py index 64f32a0a6022..98e6b8826af6 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py @@ -153,20 +153,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -178,20 +192,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py index c958e82e22f1..bc4c58a995f5 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -14007,3 +14007,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_config_service_v2_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.ConfigServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_config_service_v2_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.ConfigServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_config_service_v2_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.ConfigServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_config_service_v2_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.ConfigServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ConfigServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 20b3b64dd51e..4f3443ffa649 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -4447,3 +4447,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_logging_service_v2_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.LoggingServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_logging_service_v2_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.LoggingServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_logging_service_v2_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.LoggingServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_logging_service_v2_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.LoggingServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.LoggingServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index b613eed4163a..590d022a4056 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -4247,3 +4247,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_metrics_service_v2_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.MetricsServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_metrics_service_v2_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.MetricsServiceV2GrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_metrics_service_v2_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.MetricsServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_metrics_service_v2_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.MetricsServiceV2GrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MetricsServiceV2Transport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/base.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/base.py index a4f11b9bf2b0..7164fc0bfe5f 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/base.py @@ -150,20 +150,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -175,20 +189,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py b/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py index 262e2c3f072b..2dcd23f856aa 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -13301,3 +13301,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_cloud_redis_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.CloudRedisGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_cloud_redis_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.CloudRedisGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_cloud_redis_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.CloudRedisGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_cloud_redis_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.CloudRedisGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/base.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/base.py index 327984cec569..da37256c19e4 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/base.py @@ -150,20 +150,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -175,20 +189,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py index f45bec4b8e3c..7237130575ff 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -8521,3 +8521,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_cloud_redis_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.CloudRedisGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_cloud_redis_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.CloudRedisGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_cloud_redis_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.CloudRedisGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_cloud_redis_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.CloudRedisGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.CloudRedisTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/echo/transports/base.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/echo/transports/base.py index 684e191c3512..f5e34a9f9aa7 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/echo/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/echo/transports/base.py @@ -151,20 +151,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -176,20 +190,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/identity/transports/base.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/identity/transports/base.py index 5bf22ac55612..8f44d529f6dd 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/identity/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/identity/transports/base.py @@ -151,20 +151,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -176,20 +190,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/messaging/transports/base.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/messaging/transports/base.py index 4078f037702f..c783a24ea331 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/messaging/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/messaging/transports/base.py @@ -152,20 +152,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -177,20 +191,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/base.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/base.py index b1688f42550a..b9ca386076b2 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/base.py @@ -152,20 +152,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -177,20 +191,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc.py index 164281da93ae..66a696e695f0 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc.py @@ -398,6 +398,7 @@ def __call__(self, *args, **kwargs): credentials=transport._credentials, client_info=transport._client_info, client_cert_source_for_mtls=transport._client_cert_source_for_mtls, + client_options=getattr(transport, "_client_options", None), ) return transport._rest_transport.upload_media(*args, **kwargs) self._stubs['upload_media'] = _RestStub() diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc_asyncio.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc_asyncio.py index db2b68ba76be..d43839f84e68 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc_asyncio.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/resumable_upload_service/transports/grpc_asyncio.py @@ -412,6 +412,7 @@ def __call__(self, *args, **kwargs): host=transport._host, credentials=transport._credentials, client_info=transport._client_info, + client_options=getattr(transport, "_client_options", None), ) return transport._rest_transport.upload_media(*args, **kwargs) self._stubs['upload_media'] = _AsyncRestStub() diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/sequence_service/transports/base.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/sequence_service/transports/base.py index a82c31ac851c..f6b79c09fa5f 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/sequence_service/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/sequence_service/transports/base.py @@ -152,20 +152,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -177,20 +191,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/testing/transports/base.py b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/testing/transports/base.py index 1743258a589b..f0fc08e3ef55 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/testing/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/google/showcase_v1beta1/services/testing/transports/base.py @@ -151,20 +151,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -176,20 +190,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_echo.py b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_echo.py index 2838d603e7fc..ab41a74e8afa 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_echo.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_echo.py @@ -9780,3 +9780,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_echo_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.EchoGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.EchoTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_echo_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.EchoGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.EchoTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.EchoTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_echo_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.EchoGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.EchoTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_echo_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.EchoGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.EchoTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.EchoTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_identity.py b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_identity.py index a9d0b5f347a7..1e96a08dcbdc 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_identity.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_identity.py @@ -6887,3 +6887,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_identity_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.IdentityGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.IdentityTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_identity_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.IdentityGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.IdentityTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.IdentityTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_identity_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.IdentityGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.IdentityTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_identity_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.IdentityGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.IdentityTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.IdentityTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_messaging.py b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_messaging.py index b2a984e65b9d..4d366542f410 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_messaging.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_messaging.py @@ -11787,3 +11787,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_messaging_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.MessagingGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MessagingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_messaging_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.MessagingGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MessagingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.MessagingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_messaging_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.MessagingGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MessagingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_messaging_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.MessagingGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MessagingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.MessagingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_resumable_upload_service.py b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_resumable_upload_service.py index 278da48d6481..cdf9f108e7bb 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_resumable_upload_service.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_resumable_upload_service.py @@ -2396,6 +2396,7 @@ async def test_resumable_upload_service_upload_media_grpc_asyncio_rest_stub(): host=transport._host, credentials=transport._credentials, client_info=transport._client_info, + client_options=getattr(transport, "_client_options", None), ) assert mock_rest_transport.upload_media.call_count == 2 await transport.close() @@ -4479,3 +4480,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_resumable_upload_service_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.ResumableUploadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ResumableUploadServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_resumable_upload_service_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.ResumableUploadServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ResumableUploadServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.ResumableUploadServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_resumable_upload_service_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.ResumableUploadServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ResumableUploadServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_resumable_upload_service_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.ResumableUploadServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ResumableUploadServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.ResumableUploadServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_sequence_service.py b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_sequence_service.py index 2876b35164be..78383ff9ec55 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_sequence_service.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_sequence_service.py @@ -7569,3 +7569,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_sequence_service_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.SequenceServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.SequenceServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_sequence_service_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.SequenceServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.SequenceServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.SequenceServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_sequence_service_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.SequenceServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.SequenceServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_sequence_service_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.SequenceServiceGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.SequenceServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.SequenceServiceTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_testing.py b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_testing.py index a2fe4054dcd9..336faed128c4 100755 --- a/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_testing.py +++ b/packages/gapic-generator/tests/integration/goldens/showcase/tests/unit/gapic/showcase_v1beta1/test_testing.py @@ -7758,3 +7758,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_testing_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.TestingGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.TestingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_testing_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.TestingGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.TestingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.TestingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_testing_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.TestingGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.TestingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_testing_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.TestingGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.TestingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.TestingTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/base.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/base.py index bee249c160a9..5cf929454825 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/base.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/transports/base.py @@ -152,20 +152,34 @@ def _wrap_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method.wrap_method(func, *args, **kwargs) def _wrap_async_method(self, func, *args, **kwargs): """Wrap an async RPC method with common client-level features. @@ -177,20 +191,34 @@ def _wrap_async_method(self, func, *args, **kwargs): stripped for backward compatibility with older `google-api-core` versions. """ - if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: # pragma: NO COVER + if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING: kwargs["client_options"] = self._client_options try: kind = self.kind - except NotImplementedError: # pragma: NO COVER + except NotImplementedError: kind = None - if kind: # pragma: NO COVER + if kind: kwargs["kind"] = kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) - # The fallback below strips tracing-specific arguments when an older version - # of google-api-core is installed (which does not accept client_options, etc.). - for k in ["client_options", "method_name", "is_streaming", "kind"]: # pragma: NO COVER - kwargs.pop(k, None) # pragma: NO COVER - return gapic_v1.method_async.wrap_method(func, *args, **kwargs) # pragma: NO COVER + # Fallback for older runtime versions of google-api-core: + # - Era 1 (< 2.29.0): Neither tracing args nor `kind` are supported. + # - Era 2 (>= 2.29.0, < 2.36.0): `kind` is supported (prevents REST from + # falling back to gRPC wrapping), but tracing args are not. + # (These fallbacks can be removed once google-api-core < 2.36.0 is no longer supported.) + for k in ["client_options", "method_name", "is_streaming"]: + kwargs.pop(k, None) + + try: + kind = self.kind + except NotImplementedError: + kind = None + + if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and kind: + kwargs["kind"] = kind + else: + kwargs.pop("kind", None) + + return gapic_v1.method_async.wrap_method(func, *args, **kwargs) def _prep_wrapped_messages(self, client_info): """Precompute and cache wrapped methods for RPC dispatch.""" diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py index 10bb5ecf13f2..ac4ac6794b9f 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test_storage_batch_operations.py @@ -8457,3 +8457,197 @@ def test_api_key_credentials(client_class, transport_class): always_use_jwt_access=True, api_audience=None, ) + + +def test_storage_batch_operations_base_transport_wrap_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc" + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + transport = transports.StorageBatchOperationsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.StorageBatchOperationsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_storage_batch_operations_base_transport_wrap_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method"): + transport = transports.StorageBatchOperationsGrpcTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.StorageBatchOperationsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method, "wrap_method") as mock_wrap: + base_transport = transports.StorageBatchOperationsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + +def test_storage_batch_operations_base_transport_wrap_async_method_modern_api_core(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", True) + # 1. Concrete transport: passes client_options=None and kind="grpc_asyncio" + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + transport = transports.StorageBatchOperationsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + transport._wrap_async_method(func) + assert mock_wrap.call_args.kwargs["kind"] == "grpc_asyncio" + assert "client_options" in mock_wrap.call_args.kwargs + + # 2. Base transport: kind raises NotImplementedError and is cleanly omitted + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.StorageBatchOperationsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method(func) + assert "kind" not in mock_wrap.call_args.kwargs + assert "client_options" in mock_wrap.call_args.kwargs + + +def test_storage_batch_operations_base_transport_wrap_async_method_older_api_core_fallbacks(monkeypatch): + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_TRACING", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method"): + transport = transports.StorageBatchOperationsGrpcAsyncIOTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + + # Era 1: Ancient google-api-core (< 2.29.0: can be removed once google-api-core < 2.29.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", False) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.StorageBatchOperationsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + # Era 2: Intermediate google-api-core (>= 2.29.0, < 2.36.0: can be removed once google-api-core < 2.36.0 is no longer supported) + monkeypatch.setattr(transports.base, "_ASYNC_WRAP_METHOD_SUPPORTS_KIND", True) + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + func = mock.MagicMock() + transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert kwargs.get("kind") == "grpc_asyncio" + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs + + with mock.patch.object(transports.base.gapic_v1.method_async, "wrap_method") as mock_wrap: + base_transport = transports.StorageBatchOperationsTransport( + credentials=ga_credentials.AnonymousCredentials() + ) + func = mock.MagicMock() + base_transport._wrap_async_method( + func, + client_options="fake_options", + method_name="fake_method", + is_streaming=False, + ) + kwargs = mock_wrap.call_args.kwargs + assert "kind" not in kwargs + assert "client_options" not in kwargs + assert "method_name" not in kwargs + assert "is_streaming" not in kwargs diff --git a/packages/google-api-core/google/api_core/gapic_v1/method.py b/packages/google-api-core/google/api_core/gapic_v1/method.py index b12a0e126a60..a6e70fe7c6d8 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/method.py +++ b/packages/google-api-core/google/api_core/gapic_v1/method.py @@ -41,6 +41,10 @@ class _MethodDefault(enum.Enum): """Sentinel value indicating that a retry, timeout, or compression argument was unspecified, so the default should be used.""" +_TRANSPORT_KIND_GRPC = "grpc" +_TRANSPORT_KIND_REST = "rest" +_DEFAULT_TRANSPORT_KIND = _TRANSPORT_KIND_GRPC + def _is_not_none_or_false(value): return value is not None and value is not False @@ -122,7 +126,7 @@ def _extract_rpc_identity( return method_str, service, method -def _extract_status_code(exc: Optional[Exception]) -> str: +def _extract_status_code(exc: Optional[BaseException]) -> str: """Extract canonical status code name string from an exception. Status code name strings are resolved by inspecting the following locations: @@ -132,7 +136,7 @@ def _extract_status_code(exc: Optional[Exception]) -> str: * Fallback: Defaults to the exception class name for standard Python errors. Args: - exc (Optional[Exception]): The exception to extract the status code name from. + exc (Optional[BaseException]): The exception to extract the status code name from. Returns: str: The canonical status code name (e.g. "NOT_FOUND", "UNAVAILABLE") or class name. @@ -166,7 +170,7 @@ def _extract_status_code(exc: Optional[Exception]) -> str: return target.__class__.__name__ -def _extract_error_attributes(exc: Optional[Exception]) -> dict[str, Any]: +def _extract_error_attributes(exc: Optional[BaseException]) -> dict[str, Any]: """Extract gcp.errors.* and error.type attributes from an exception. Error details and ErrorInfo structures are resolved by inspecting the following locations: @@ -176,7 +180,7 @@ def _extract_error_attributes(exc: Optional[Exception]) -> dict[str, Any]: * Unified attribute extraction: Extracts domain, reason, and metadata from ErrorInfo or exception attributes. Args: - exc (Optional[Exception]): An exception (such as GoogleAPICallError or grpc.RpcError) or ErrorInfo object. + exc (Optional[BaseException]): An exception (such as GoogleAPICallError or grpc.RpcError) or ErrorInfo object. Returns: dict[str, Any]: Extracted error attributes (e.g. gcp.errors.domain, error.type, gcp.errors.metadata.*). @@ -228,9 +232,10 @@ def _extract_error_attributes(exc: Optional[Exception]) -> dict[str, Any]: # 5. Extract human-readable error description for cross-language PRD parity message = getattr(target_exc, "message", None) - if not message and hasattr(target_exc, "details") and callable(target_exc.details): - message = target_exc.details() - if not message and isinstance(target_exc, Exception): + if not message and hasattr(target_exc, "details"): + details = target_exc.details + message = details() if callable(details) else details + if not message and isinstance(target_exc, BaseException): message = str(target_exc) if message: attrs["status.message"] = str(message) @@ -271,6 +276,8 @@ class _GapicCallable(object): Allowed values for OpenTelemetry method tracing are "grpc" and "rest". """ + _SUPPORTED_TRACING_KINDS = (_TRANSPORT_KIND_GRPC, _TRANSPORT_KIND_REST) + def __init__( self, target, @@ -282,7 +289,7 @@ def __init__( method_name=None, is_streaming=False, client_info=None, - kind="grpc", + kind=_DEFAULT_TRANSPORT_KIND, ): self._target = target self._retry = retry @@ -301,11 +308,11 @@ def __init__( self._default_metadata = self._static_metadata # Configure the OpenTelemetry span factory once at initialization. - # For now, method tracing is gated to non-streaming gRPC calls where an explicit method_name is provided. + # For now, method tracing is gated to non-streaming calls where an explicit method_name is provided. self._start_span_fn = None if ( not is_streaming - and kind in ("grpc", "rest") + and kind in self._SUPPORTED_TRACING_KINDS and method_name is not None and _observability.is_otel_capabilities_enabled(client_options) ): @@ -323,9 +330,8 @@ def __init__( tracer = trace.get_tracer("google.api_core") span_name, _, _ = _extract_rpc_identity(method_name) - is_rest = kind in ("rest", "rest_asyncio") span_attributes = { - "rpc.system.name": "http" if is_rest else "grpc", + "rpc.system.name": "http" if kind.startswith("rest") else "grpc", "rpc.method": span_name, } self._start_span_fn = functools.partial( @@ -338,11 +344,7 @@ def __init__( # Gracefully disable tracing if OpenTelemetry or custom provider fails self._start_span_fn = None - def __call__( - self, *args, timeout=DEFAULT, retry=DEFAULT, compression=DEFAULT, **kwargs - ): - """Invoke the low-level RPC with retry, timeout, compression, and metadata.""" - + def _prepare_call(self, timeout, retry, compression, kwargs): if retry is DEFAULT: retry = self._retry @@ -376,22 +378,26 @@ def __call__( if compression is not None: kwargs["compression"] = compression + return wrapped_func + + @contextlib.contextmanager + def _trace_span(self): span_cm = contextlib.nullcontext() if self._start_span_fn is not None: try: span_cm = self._start_span_fn() - except ( - Exception - ): # Fail-open: proceed without span if tracing initialization fails + except Exception: + # Fail-open: proceed without span if tracing initialization fails span_cm = contextlib.nullcontext() with span_cm as span: try: - result = wrapped_func(*args, **kwargs) + yield if span is not None and hasattr(span, "set_attribute"): span.set_attribute("rpc.response.status_code", "OK") - return result - except Exception as exc: + except (KeyboardInterrupt, SystemExit, GeneratorExit): + raise + except BaseException as exc: if span is not None and hasattr(span, "set_attribute"): span.set_attribute( "rpc.response.status_code", _extract_status_code(exc) @@ -400,6 +406,14 @@ def __call__( span.set_attribute(k, v) raise + def __call__( + self, *args, timeout=DEFAULT, retry=DEFAULT, compression=DEFAULT, **kwargs + ): + """Invoke the low-level RPC with retry, timeout, compression, and metadata.""" + wrapped_func = self._prepare_call(timeout, retry, compression, kwargs) + with self._trace_span(): + return wrapped_func(*args, **kwargs) + def wrap_method( func, @@ -412,7 +426,7 @@ def wrap_method( client_options=None, method_name=None, is_streaming=False, - kind="grpc", + kind=_DEFAULT_TRANSPORT_KIND, ): """Wrap an RPC method with common behavior. diff --git a/packages/google-api-core/google/api_core/gapic_v1/method_async.py b/packages/google-api-core/google/api_core/gapic_v1/method_async.py index e550b4228745..2402f9f3ff95 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/method_async.py +++ b/packages/google-api-core/google/api_core/gapic_v1/method_async.py @@ -17,192 +17,40 @@ compression, pagination, and long-running operations to methods. """ -import asyncio -import contextlib import functools import inspect -from google.api_core import _observability, grpc_helpers_async +from google.api_core import grpc_helpers_async from google.api_core.gapic_v1 import client_info -from google.api_core.gapic_v1.client_info import METRICS_METADATA_KEY - -# Retain _GapicCallable import for backward compatibility with external packages from google.api_core.gapic_v1.method import ( # noqa: F401 DEFAULT, USE_DEFAULT_METADATA, - _apply_decorators, - _deduplicate_metadata_tokens, - _extract_error_attributes, - _extract_metrics_header, - _extract_rpc_identity, - _extract_status_code, _GapicCallable, ) -from google.api_core.timeout import TimeToDeadlineTimeout - -_DEFAULT_ASYNC_TRANSPORT_KIND = "grpc_asyncio" - - -class _AsyncGapicCallable(object): - """Async callable object that wraps an async RPC method with retry, timeout, metadata, and tracing. - - Args: - target (Callable): The low-level async RPC method. - retry (Optional[google.api_core.retry_async.AsyncRetry]): The default retry for the - callable. If ``None``, this callable will not retry by default. - timeout (Optional[Union[google.api_core.timeout.Timeout, float]]): The default timeout for the - callable. If ``None``, this callable will not specify a timeout argument to the - low-level RPC method. - compression (Optional[grpc.Compression]): The default compression for the callable. - If ``None``, this callable will not specify a compression argument to the low-level - RPC method. - metadata (Optional[Sequence[Tuple[str, str]]]): Additional metadata that is - provided to the RPC method on every invocation. This is merged with - any metadata specified during invocation. If ``None``, no - additional metadata will be passed to the RPC method. - client_options (Optional[google.api_core.client_options.ClientOptions]): - Client options used to configure client-level behavior, such as - custom OpenTelemetry tracer providers. Defaults to None. - method_name (Optional[str]): The optional explicit full RPC method name - (e.g. "/google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion"). - is_streaming (bool): Whether the RPC method is streaming. Defaults to False. - Note: Streaming methods do not currently generate Tier 3 observability spans. - client_info (Optional[google.api_core.gapic_v1.client_info.ClientInfo]): - Client information used for metadata headers. Defaults to None. - kind (str): The transport kind for the RPC method. Defaults to "grpc_asyncio". - Allowed values for OpenTelemetry method tracing are "grpc_asyncio" and "rest_asyncio". - """ - def __init__( - self, - target, - retry, - timeout, - compression, - metadata=None, - client_options=None, - method_name=None, - is_streaming=False, - client_info=None, - kind=_DEFAULT_ASYNC_TRANSPORT_KIND, - ): - self._target = target - self._retry = retry - self._timeout = timeout - self._compression = compression - - # Pre-extract the x-goog-api-client header from the initialized metadata. - self._x_goog_api_client, remaining = _extract_metrics_header(metadata) - self._static_metadata = tuple(remaining) - if self._x_goog_api_client: - self._default_metadata = ( - (METRICS_METADATA_KEY, self._x_goog_api_client), - *self._static_metadata, - ) - else: - self._default_metadata = self._static_metadata +_TRANSPORT_KIND_GRPC_ASYNC = "grpc_asyncio" +_TRANSPORT_KIND_REST_ASYNC = "rest_asyncio" +_DEFAULT_ASYNC_TRANSPORT_KIND = _TRANSPORT_KIND_GRPC_ASYNC - # Configure the OpenTelemetry span factory once at initialization. - self._start_span_fn = None - if ( - not is_streaming - and kind in ("grpc_asyncio", "rest_asyncio") - and method_name is not None - and _observability.is_otel_capabilities_enabled(client_options) - ): - try: - from opentelemetry import trace - tracer_provider = None - if isinstance(client_options, dict): - tracer_provider = client_options.get("tracer_provider") - elif client_options is not None: - tracer_provider = getattr(client_options, "tracer_provider", None) - if tracer_provider is not None: - tracer = tracer_provider.get_tracer("google.api_core") - else: - tracer = trace.get_tracer("google.api_core") +class _AsyncGapicCallable(_GapicCallable): + """Async callable object that wraps an async RPC method with retry, timeout, metadata, and tracing.""" - span_name, _, _ = _extract_rpc_identity(method_name) - is_rest = kind in ("rest", "rest_asyncio") - span_attributes = { - "rpc.system.name": "http" if is_rest else "grpc", - "rpc.method": span_name, - } - self._start_span_fn = functools.partial( - tracer.start_as_current_span, - span_name, - kind=trace.SpanKind.CLIENT, - attributes=span_attributes, - ) - except (ImportError, AttributeError, TypeError): - # Gracefully disable tracing if OpenTelemetry or custom provider fails - self._start_span_fn = None + _SUPPORTED_TRACING_KINDS = ( + _TRANSPORT_KIND_GRPC_ASYNC, + _TRANSPORT_KIND_REST_ASYNC, + ) async def __call__( self, *args, timeout=DEFAULT, retry=DEFAULT, compression=DEFAULT, **kwargs ): """Invoke the low-level async RPC with retry, timeout, compression, and metadata.""" - if retry is DEFAULT: - retry = self._retry - - if timeout is DEFAULT: - timeout = self._timeout - - if compression is DEFAULT: - compression = self._compression - - if isinstance(timeout, (int, float)): - timeout = TimeToDeadlineTimeout(timeout=timeout) - - # Apply all applicable decorators. - wrapped_func = _apply_decorators(self._target, [retry, timeout]) - - if user_metadata := kwargs.get("metadata"): - # Add the user agent metadata to the call. - final_metadata = list(self._static_metadata) - user_x_goog, remaining = _extract_metrics_header(user_metadata) - - merged_header = _deduplicate_metadata_tokens( - self._x_goog_api_client, user_x_goog - ) - if merged_header: - final_metadata.append((METRICS_METADATA_KEY, merged_header)) - final_metadata.extend(remaining) - kwargs["metadata"] = final_metadata - elif self._default_metadata: - kwargs["metadata"] = self._default_metadata - - if compression is not None: - kwargs["compression"] = compression - - span_cm = contextlib.nullcontext() - if self._start_span_fn is not None: - try: - span_cm = self._start_span_fn() - except ( - Exception - ): # Fail-open: proceed without span if tracing initialization fails - span_cm = contextlib.nullcontext() - - with span_cm as span: - try: - res = wrapped_func(*args, **kwargs) - if inspect.isawaitable(res): - result = await res - else: - result = res - if span is not None and hasattr(span, "set_attribute"): - span.set_attribute("rpc.response.status_code", "OK") - return result - except (Exception, asyncio.CancelledError) as exc: - if span is not None and hasattr(span, "set_attribute"): - span.set_attribute( - "rpc.response.status_code", _extract_status_code(exc) - ) - for k, v in _extract_error_attributes(exc).items(): - span.set_attribute(k, v) - raise + wrapped_func = self._prepare_call(timeout, retry, compression, kwargs) + with self._trace_span(): + res = wrapped_func(*args, **kwargs) + if inspect.isawaitable(res): + return await res + return res def wrap_method( @@ -219,6 +67,33 @@ def wrap_method( ): """Wrap an async RPC method with common behavior. + Args: + func (Callable): The low-level async RPC method. + default_retry (Optional[google.api_core.retry_async.AsyncRetry]): The default + retry strategy. If ``None``, the method will not retry by default. + default_timeout (Optional[Union[google.api_core.timeout.Timeout, float]]): The + default timeout strategy. Can also be specified as an int or float. If + ``None``, the method will not have a timeout specified by default. + default_compression (Optional[grpc.Compression]): The default + grpc.Compression. If ``None``, the method will not have + compression specified by default. + client_info (Optional[google.api_core.gapic_v1.client_info.ClientInfo]): + Client information used to create a user-agent string that's + passed as gRPC metadata to the method. If unspecified, then + a sane default will be used. If ``None``, then no user agent + metadata will be provided to the RPC method. + kind (str): The transport kind for the RPC method. Defaults to "grpc_asyncio". + Allowed values for OpenTelemetry method tracing are "grpc_asyncio" and "rest_asyncio". + client_options + (Optional[google.api_core.client_options.ClientOptions]): + Client options used to configure client-level behavior, such as + custom OpenTelemetry tracer providers. Defaults to None. + method_name (Optional[str]): Optional explicit full RPC method name + (e.g. "/google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion"). + Used to identify the RPC for observability. + is_streaming (bool): Whether the RPC method is streaming. Defaults to False. + Streaming methods are currently gated and do not generate Tier 3 spans. + Returns: Callable: A new callable that takes optional ``retry``, ``timeout``, and ``compression`` arguments and applies the common error mapping, diff --git a/packages/google-api-core/tests/asyncio/gapic/test_method_async.py b/packages/google-api-core/tests/asyncio/gapic/test_method_async.py index 517f475a76fd..d51ae6ea2a49 100644 --- a/packages/google-api-core/tests/asyncio/gapic/test_method_async.py +++ b/packages/google-api-core/tests/asyncio/gapic/test_method_async.py @@ -350,11 +350,14 @@ async def test_wrap_method_async_otel_tracing_skips_span( """Proves that under various gating conditions, no async Tier 3 span is created.""" mock_target = mock.AsyncMock(return_value="success") mock_trace = mock.Mock() + from google.api_core import _observability with ( - mock.patch( - "google.api_core._observability.is_otel_capabilities_enabled", + mock.patch.object( + _observability, + "is_otel_capabilities_enabled", return_value=capabilities_enabled, + autospec=True, ), mock.patch.dict( "sys.modules", @@ -371,8 +374,18 @@ async def test_wrap_method_async_otel_tracing_skips_span( mock_trace.get_tracer.assert_not_called() +@pytest.mark.parametrize( + "kind,expected_rpc_system", + [ + ("grpc_asyncio", "grpc"), + ("rest_asyncio", "http"), + ], + ids=["grpc_asyncio", "rest_asyncio"], +) @pytest.mark.asyncio -async def test_wrap_method_async_otel_tracing_enabled_success(mock_otel): +async def test_wrap_method_async_otel_tracing_enabled_success( + mock_otel, kind, expected_rpc_system +): """Proves that when OpenTelemetry tracing is enabled and method_name is passed, a T3 client span is started and awaited.""" mock_target = mock.AsyncMock(return_value="async_success") @@ -380,7 +393,7 @@ async def test_wrap_method_async_otel_tracing_enabled_success(mock_otel): mock_target, default_timeout=60, method_name="/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="grpc_asyncio", + kind=kind, ) result = await wrapped() @@ -389,32 +402,7 @@ async def test_wrap_method_async_otel_tracing_enabled_success(mock_otel): "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", kind="CLIENT", attributes={ - "rpc.system.name": "grpc", - "rpc.method": "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - }, - ) - mock_otel.span.set_attribute.assert_called_with("rpc.response.status_code", "OK") - - -@pytest.mark.asyncio -async def test_wrap_method_async_otel_tracing_enabled_rest_asyncio(mock_otel): - """Proves that when kind is 'rest_asyncio', a T3 client span is started.""" - mock_target = mock.AsyncMock(return_value="rest_success") - - wrapped = gapic_v1.method_async.wrap_method( - mock_target, - default_timeout=60, - method_name="/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="rest_asyncio", - ) - result = await wrapped() - - assert result == "rest_success" - mock_otel.tracer.start_as_current_span.assert_called_once_with( - "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="CLIENT", - attributes={ - "rpc.system.name": "http", + "rpc.system.name": expected_rpc_system, "rpc.method": "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", }, ) @@ -447,37 +435,31 @@ async def delayed_target(*args, **kwargs): @pytest.mark.asyncio -async def test_wrap_method_async_otel_tracing_custom_client_options(mock_otel): +@pytest.mark.parametrize( + "options_builder", + [ + pytest.param( + lambda p: client_options_lib.ClientOptions(tracer_provider=p), + id="client_options_object", + ), + pytest.param( + lambda p: {"tracer_provider": p}, + id="client_options_dict", + ), + ], +) +async def test_wrap_method_async_otel_tracing_client_options( + mock_otel, options_builder +): """Proves that providing client_options with a custom tracer_provider uses that provider.""" mock_target = mock.AsyncMock(return_value="success") mock_provider = mock.Mock() mock_provider.get_tracer.return_value = mock_otel.tracer - client_options = client_options_lib.ClientOptions(tracer_provider=mock_provider) - wrapped = gapic_v1.method_async.wrap_method( mock_target, - client_options=client_options, - method_name="/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - ) - result = await wrapped() - - assert result == "success" - mock_provider.get_tracer.assert_called_once_with("google.api_core") - - -@pytest.mark.asyncio -async def test_wrap_method_async_otel_tracing_dict_client_options(mock_otel): - """Proves that providing a dict with tracer_provider uses that provider.""" - mock_target = mock.AsyncMock(return_value="success") - - mock_provider = mock.Mock() - mock_provider.get_tracer.return_value = mock_otel.tracer - - wrapped = gapic_v1.method_async.wrap_method( - mock_target, - client_options={"tracer_provider": mock_provider}, + client_options=options_builder(mock_provider), method_name="/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", ) result = await wrapped() @@ -505,6 +487,31 @@ async def test_wrap_method_async_otel_tracing_enabled_error(mock_otel): ) +@pytest.mark.asyncio +@pytest.mark.parametrize("interrupt_exc", [KeyboardInterrupt, SystemExit]) +async def test_wrap_method_async_otel_tracing_interrupt_bypasses_error_attributes( + mock_otel, interrupt_exc +): + """Proves that process-level interrupts in async calls are re-raised without polluting span error attributes.""" + mock_target = mock.AsyncMock(side_effect=interrupt_exc()) + + wrapped = gapic_v1.method_async.wrap_method( + mock_target, + method_name="/google.cloud.secretmanager.v1.SecretManagerService/GetSecret", + ) + + with pytest.raises(interrupt_exc): + await wrapped() + + for call in mock_otel.span.set_attribute.call_args_list: + attr_name = call[0][0] + assert attr_name not in ( + "rpc.response.status_code", + "error.type", + "status.message", + ) + + @pytest.mark.asyncio async def test_wrap_method_async_otel_tracing_records_gcp_error_attributes(mock_otel): """Proves that GCP error attributes (domain, reason, metadata) are recorded on the span.""" diff --git a/packages/google-api-core/tests/unit/gapic/test_method.py b/packages/google-api-core/tests/unit/gapic/test_method.py index 18a20d8e1879..a979a948338e 100644 --- a/packages/google-api-core/tests/unit/gapic/test_method.py +++ b/packages/google-api-core/tests/unit/gapic/test_method.py @@ -539,6 +539,30 @@ def test_wrap_method_otel_tracing_enabled_error(mock_otel): ) +@pytest.mark.parametrize("interrupt_exc", [KeyboardInterrupt, SystemExit]) +def test_wrap_method_otel_tracing_interrupt_bypasses_error_attributes( + mock_otel, interrupt_exc +): + """Proves that process-level interrupts are re-raised without polluting span error attributes.""" + mock_target = mock.Mock(side_effect=interrupt_exc()) + + wrapped = google.api_core.gapic_v1.method.wrap_method( + mock_target, + method_name="/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", + ) + with pytest.raises(interrupt_exc): + wrapped() + + # The span should NOT record error attributes for process-level interruptions + for call in mock_otel.span.set_attribute.call_args_list: + attr_name = call[0][0] + assert attr_name not in ( + "rpc.response.status_code", + "error.type", + "status.message", + ) + + @pytest.mark.parametrize( "exc,expected_status", [ @@ -911,6 +935,20 @@ def test_extract_error_attributes_variations(): "error.type": "ValueError", } + # 11. status.message extraction from BaseException subclasses (e.g. asyncio.CancelledError) + import asyncio + + exc_cancelled_with_msg = asyncio.CancelledError("async task cancelled") + assert _extract_error_attributes(exc_cancelled_with_msg) == { + "error.type": "CancelledError", + "status.message": "async task cancelled", + } + + exc_cancelled_empty = asyncio.CancelledError() + assert _extract_error_attributes(exc_cancelled_empty) == { + "error.type": "CancelledError", + } + def test_wrap_method_otel_tracing_partial_span_capabilities(mock_otel): """Proves handling when span has or lacks set_attribute."""