Skip to content

Commit d6632eb

Browse files
committed
fix(generator): add 3-era google-api-core support to async transport and document version checks
- Add _ASYNC_WRAP_METHOD_SUPPORTS_KIND to detect support for the kind parameter in gapic_v1.method_async.wrap_method. - Support all three historical eras of google-api-core in _wrap_async_method: 1. Modern core with OpenTelemetry tracing: inject client_options, kind, method_name, and is_streaming. 2. Intermediate core (>= 2.19.1, PR #688): strip tracing-only arguments, but preserve and inject kind so REST callables are not erroneously wrapped with gRPC error handlers. 3. Ancient core (< 2.19.1): strip both tracing-only arguments and kind to avoid TypeError. - Add comprehensive docstring and inline comments to _wrap_async_method explaining each check and the runtime versions supported. - Update unit tests in test_%service.py.j2 to test both intermediate and ancient core fallback paths. - Regenerate all 8 GAPIC integration goldens.
1 parent 89e0a9c commit d6632eb

35 files changed

Lines changed: 659 additions & 130 deletions

File tree

‎packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ falling back to the local implementation if not present. #}
1313
{# TODO(https://github.com/googleapis/google-cloud-python/issues/17883):
1414
Backfill compatibility functions being removed from the client layer. #}
1515

16+
import contextlib
1617
import os
1718
import json
1819
{% if has_auto_populated_fields %}

‎packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2‎

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,16 @@ DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__
5757

5858
# Check once at module load time whether google-api-core's wrap_methods support
5959
# OpenTelemetry tracing arguments (client_options, method_name, is_streaming, kind)
60-
# to avoid recurring inspect.signature latency during client instantiation.
60+
# or transport kind arguments, to avoid recurring inspect.signature latency during client instantiation.
6161
_WRAP_METHOD_SUPPORTS_TRACING = (
6262
"client_options" in inspect.signature(gapic_v1.method.wrap_method).parameters
6363
)
6464
_ASYNC_WRAP_METHOD_SUPPORTS_TRACING = (
6565
"client_options" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
6666
)
67+
_ASYNC_WRAP_METHOD_SUPPORTS_KIND = (
68+
"kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
69+
)
6770

6871

6972
class {{ service.name }}Transport(abc.ABC):
@@ -194,22 +197,43 @@ class {{ service.name }}Transport(abc.ABC):
194197
"""Wrap an async RPC method with common client-level features.
195198

196199
Applies asynchronous retry, timeout, metadata, and tracing wrappers
197-
to the underlying RPC method callable. Transport attributes (`kind`
198-
and, if supported by the runtime `google-api-core` version, `client_options`)
199-
are injected. Tracing-specific arguments (`client_options`, `method_name`,
200-
`is_streaming`) are stripped when unsupported for backward compatibility
201-
with older `google-api-core` versions.
200+
to the underlying RPC method callable.
201+
202+
This method adapts dynamically across three historical generations of
203+
`google-api-core`:
204+
1. Modern core with OpenTelemetry tracing (PR #18274 / #18433):
205+
Supports `client_options`, `kind`, `method_name`, and `is_streaming`.
206+
2. Intermediate core with async transport discrimination (>= 2.19.1, PR #688):
207+
Supports `kind` to avoid erroneous gRPC error mapping on REST transports,
208+
but does not yet accept tracing arguments (`client_options`, etc.).
209+
3. Ancient core (< 2.19.1):
210+
Accepts neither `client_options` nor `kind`. Both must be stripped to
211+
prevent `TypeError: unexpected keyword argument`.
202212
"""
213+
# Generation 1: Full OpenTelemetry tracing support in modern google-api-core
203214
if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING:
204215
kwargs["client_options"] = self._client_options
205216
if self.kind:
206217
kwargs["kind"] = self.kind
207218
return gapic_v1.method_async.wrap_method(func, *args, **kwargs)
208219

209-
# The fallback below strips tracing-specific arguments when an older version
210-
# of google-api-core is installed (which does not accept client_options, etc.).
211-
for k in ["client_options", "method_name", "is_streaming", "kind"]:
220+
# Fallback for older runtime versions of google-api-core:
221+
# Strip tracing-only arguments (client_options, method_name, is_streaming)
222+
# unsupported in google-api-core < 2.26.0/tracing release.
223+
for k in ["client_options", "method_name", "is_streaming"]:
212224
kwargs.pop(k, None)
225+
226+
# Generation 2 vs Generation 3 fallback check:
227+
# In google-api-core >= 2.19.1, method_async.wrap_method introduced the `kind`
228+
# parameter (commit 8a04ec045c1, PR #688) to distinguish gRPC from REST transports
229+
# and prevent REST callables from being wrapped with gRPC error handlers.
230+
# If supported, inject `kind` from the transport; otherwise (Generation 3,
231+
# google-api-core < 2.19.1), strip `kind` to avoid a TypeError.
232+
if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and self.kind:
233+
kwargs["kind"] = self.kind
234+
else:
235+
kwargs.pop("kind", None)
236+
213237
return gapic_v1.method_async.wrap_method(func, *args, **kwargs)
214238

215239
def _prep_wrapped_messages(self, client_info):

‎packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,10 +1493,26 @@ def test_{{ service.name|snake_case }}_base_transport_wrap_async_method():
14931493
assert mock_wrap.call_args.kwargs.get("client_options") == options
14941494
assert mock_wrap.call_args.kwargs.get("kind") == "grpc_asyncio"
14951495

1496-
# Test older google-api-core without tracing support
1496+
# Test intermediate google-api-core without tracing support but with kind support (>= 2.19.1)
1497+
with mock.patch(
1498+
"{{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join('.') }}.services.{{ service.name|snake_case }}.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_TRACING",
1499+
False,
1500+
), mock.patch(
1501+
"{{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join('.') }}.services.{{ service.name|snake_case }}.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_KIND",
1502+
True,
1503+
):
1504+
mock_wrap.reset_mock()
1505+
transport._wrap_async_method(func, client_options=options, kind="grpc_asyncio")
1506+
assert "client_options" not in mock_wrap.call_args.kwargs
1507+
assert mock_wrap.call_args.kwargs.get("kind") == "grpc_asyncio"
1508+
1509+
# Test ancient google-api-core without tracing support or kind support (< 2.19.1)
14971510
with mock.patch(
14981511
"{{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join('.') }}.services.{{ service.name|snake_case }}.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_TRACING",
14991512
False,
1513+
), mock.patch(
1514+
"{{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join('.') }}.services.{{ service.name|snake_case }}.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_KIND",
1515+
False,
15001516
):
15011517
mock_wrap.reset_mock()
15021518
transport._wrap_async_method(func, client_options=options, kind="grpc_asyncio")

‎packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#
1616
"""A compatibility module for older versions of google-api-core."""
1717

18+
import contextlib
1819
import os
1920
import json
2021

‎packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/base.py‎

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@
3939

4040
# Check once at module load time whether google-api-core's wrap_methods support
4141
# OpenTelemetry tracing arguments (client_options, method_name, is_streaming, kind)
42-
# to avoid recurring inspect.signature latency during client instantiation.
42+
# or transport kind arguments, to avoid recurring inspect.signature latency during client instantiation.
4343
_WRAP_METHOD_SUPPORTS_TRACING = (
4444
"client_options" in inspect.signature(gapic_v1.method.wrap_method).parameters
4545
)
4646
_ASYNC_WRAP_METHOD_SUPPORTS_TRACING = (
4747
"client_options" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
4848
)
49+
_ASYNC_WRAP_METHOD_SUPPORTS_KIND = (
50+
"kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
51+
)
4952

5053

5154
class AssetServiceTransport(abc.ABC):
@@ -171,22 +174,43 @@ def _wrap_async_method(self, func, *args, **kwargs):
171174
"""Wrap an async RPC method with common client-level features.
172175
173176
Applies asynchronous retry, timeout, metadata, and tracing wrappers
174-
to the underlying RPC method callable. Transport attributes (`kind`
175-
and, if supported by the runtime `google-api-core` version, `client_options`)
176-
are injected. Tracing-specific arguments (`client_options`, `method_name`,
177-
`is_streaming`) are stripped when unsupported for backward compatibility
178-
with older `google-api-core` versions.
177+
to the underlying RPC method callable.
178+
179+
This method adapts dynamically across three historical generations of
180+
`google-api-core`:
181+
1. Modern core with OpenTelemetry tracing (PR #18274 / #18433):
182+
Supports `client_options`, `kind`, `method_name`, and `is_streaming`.
183+
2. Intermediate core with async transport discrimination (>= 2.19.1, PR #688):
184+
Supports `kind` to avoid erroneous gRPC error mapping on REST transports,
185+
but does not yet accept tracing arguments (`client_options`, etc.).
186+
3. Ancient core (< 2.19.1):
187+
Accepts neither `client_options` nor `kind`. Both must be stripped to
188+
prevent `TypeError: unexpected keyword argument`.
179189
"""
190+
# Generation 1: Full OpenTelemetry tracing support in modern google-api-core
180191
if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING:
181192
kwargs["client_options"] = self._client_options
182193
if self.kind:
183194
kwargs["kind"] = self.kind
184195
return gapic_v1.method_async.wrap_method(func, *args, **kwargs)
185196

186-
# The fallback below strips tracing-specific arguments when an older version
187-
# of google-api-core is installed (which does not accept client_options, etc.).
188-
for k in ["client_options", "method_name", "is_streaming", "kind"]:
197+
# Fallback for older runtime versions of google-api-core:
198+
# Strip tracing-only arguments (client_options, method_name, is_streaming)
199+
# unsupported in google-api-core < 2.26.0/tracing release.
200+
for k in ["client_options", "method_name", "is_streaming"]:
189201
kwargs.pop(k, None)
202+
203+
# Generation 2 vs Generation 3 fallback check:
204+
# In google-api-core >= 2.19.1, method_async.wrap_method introduced the `kind`
205+
# parameter (commit 8a04ec045c1, PR #688) to distinguish gRPC from REST transports
206+
# and prevent REST callables from being wrapped with gRPC error handlers.
207+
# If supported, inject `kind` from the transport; otherwise (Generation 3,
208+
# google-api-core < 2.19.1), strip `kind` to avoid a TypeError.
209+
if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and self.kind:
210+
kwargs["kind"] = self.kind
211+
else:
212+
kwargs.pop("kind", None)
213+
190214
return gapic_v1.method_async.wrap_method(func, *args, **kwargs)
191215

192216
def _prep_wrapped_messages(self, client_info):

‎packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17582,10 +17582,26 @@ def test_asset_service_base_transport_wrap_async_method():
1758217582
assert mock_wrap.call_args.kwargs.get("client_options") == options
1758317583
assert mock_wrap.call_args.kwargs.get("kind") == "grpc_asyncio"
1758417584

17585-
# Test older google-api-core without tracing support
17585+
# Test intermediate google-api-core without tracing support but with kind support (>= 2.19.1)
17586+
with mock.patch(
17587+
"google.cloud.asset_v1.services.asset_service.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_TRACING",
17588+
False,
17589+
), mock.patch(
17590+
"google.cloud.asset_v1.services.asset_service.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_KIND",
17591+
True,
17592+
):
17593+
mock_wrap.reset_mock()
17594+
transport._wrap_async_method(func, client_options=options, kind="grpc_asyncio")
17595+
assert "client_options" not in mock_wrap.call_args.kwargs
17596+
assert mock_wrap.call_args.kwargs.get("kind") == "grpc_asyncio"
17597+
17598+
# Test ancient google-api-core without tracing support or kind support (< 2.19.1)
1758617599
with mock.patch(
1758717600
"google.cloud.asset_v1.services.asset_service.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_TRACING",
1758817601
False,
17602+
), mock.patch(
17603+
"google.cloud.asset_v1.services.asset_service.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_KIND",
17604+
False,
1758917605
):
1759017606
mock_wrap.reset_mock()
1759117607
transport._wrap_async_method(func, client_options=options, kind="grpc_asyncio")

‎packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#
1616
"""A compatibility module for older versions of google-api-core."""
1717

18+
import contextlib
1819
import os
1920
import json
2021

‎packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/transports/base.py‎

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@
3636

3737
# Check once at module load time whether google-api-core's wrap_methods support
3838
# OpenTelemetry tracing arguments (client_options, method_name, is_streaming, kind)
39-
# to avoid recurring inspect.signature latency during client instantiation.
39+
# or transport kind arguments, to avoid recurring inspect.signature latency during client instantiation.
4040
_WRAP_METHOD_SUPPORTS_TRACING = (
4141
"client_options" in inspect.signature(gapic_v1.method.wrap_method).parameters
4242
)
4343
_ASYNC_WRAP_METHOD_SUPPORTS_TRACING = (
4444
"client_options" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
4545
)
46+
_ASYNC_WRAP_METHOD_SUPPORTS_KIND = (
47+
"kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters
48+
)
4649

4750

4851
class IAMCredentialsTransport(abc.ABC):
@@ -168,22 +171,43 @@ def _wrap_async_method(self, func, *args, **kwargs):
168171
"""Wrap an async RPC method with common client-level features.
169172
170173
Applies asynchronous retry, timeout, metadata, and tracing wrappers
171-
to the underlying RPC method callable. Transport attributes (`kind`
172-
and, if supported by the runtime `google-api-core` version, `client_options`)
173-
are injected. Tracing-specific arguments (`client_options`, `method_name`,
174-
`is_streaming`) are stripped when unsupported for backward compatibility
175-
with older `google-api-core` versions.
174+
to the underlying RPC method callable.
175+
176+
This method adapts dynamically across three historical generations of
177+
`google-api-core`:
178+
1. Modern core with OpenTelemetry tracing (PR #18274 / #18433):
179+
Supports `client_options`, `kind`, `method_name`, and `is_streaming`.
180+
2. Intermediate core with async transport discrimination (>= 2.19.1, PR #688):
181+
Supports `kind` to avoid erroneous gRPC error mapping on REST transports,
182+
but does not yet accept tracing arguments (`client_options`, etc.).
183+
3. Ancient core (< 2.19.1):
184+
Accepts neither `client_options` nor `kind`. Both must be stripped to
185+
prevent `TypeError: unexpected keyword argument`.
176186
"""
187+
# Generation 1: Full OpenTelemetry tracing support in modern google-api-core
177188
if _ASYNC_WRAP_METHOD_SUPPORTS_TRACING:
178189
kwargs["client_options"] = self._client_options
179190
if self.kind:
180191
kwargs["kind"] = self.kind
181192
return gapic_v1.method_async.wrap_method(func, *args, **kwargs)
182193

183-
# The fallback below strips tracing-specific arguments when an older version
184-
# of google-api-core is installed (which does not accept client_options, etc.).
185-
for k in ["client_options", "method_name", "is_streaming", "kind"]:
194+
# Fallback for older runtime versions of google-api-core:
195+
# Strip tracing-only arguments (client_options, method_name, is_streaming)
196+
# unsupported in google-api-core < 2.26.0/tracing release.
197+
for k in ["client_options", "method_name", "is_streaming"]:
186198
kwargs.pop(k, None)
199+
200+
# Generation 2 vs Generation 3 fallback check:
201+
# In google-api-core >= 2.19.1, method_async.wrap_method introduced the `kind`
202+
# parameter (commit 8a04ec045c1, PR #688) to distinguish gRPC from REST transports
203+
# and prevent REST callables from being wrapped with gRPC error handlers.
204+
# If supported, inject `kind` from the transport; otherwise (Generation 3,
205+
# google-api-core < 2.19.1), strip `kind` to avoid a TypeError.
206+
if _ASYNC_WRAP_METHOD_SUPPORTS_KIND and self.kind:
207+
kwargs["kind"] = self.kind
208+
else:
209+
kwargs.pop("kind", None)
210+
187211
return gapic_v1.method_async.wrap_method(func, *args, **kwargs)
188212

189213
def _prep_wrapped_messages(self, client_info):

‎packages/gapic-generator/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3996,10 +3996,26 @@ def test_iam_credentials_base_transport_wrap_async_method():
39963996
assert mock_wrap.call_args.kwargs.get("client_options") == options
39973997
assert mock_wrap.call_args.kwargs.get("kind") == "grpc_asyncio"
39983998

3999-
# Test older google-api-core without tracing support
3999+
# Test intermediate google-api-core without tracing support but with kind support (>= 2.19.1)
4000+
with mock.patch(
4001+
"google.iam.credentials_v1.services.iam_credentials.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_TRACING",
4002+
False,
4003+
), mock.patch(
4004+
"google.iam.credentials_v1.services.iam_credentials.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_KIND",
4005+
True,
4006+
):
4007+
mock_wrap.reset_mock()
4008+
transport._wrap_async_method(func, client_options=options, kind="grpc_asyncio")
4009+
assert "client_options" not in mock_wrap.call_args.kwargs
4010+
assert mock_wrap.call_args.kwargs.get("kind") == "grpc_asyncio"
4011+
4012+
# Test ancient google-api-core without tracing support or kind support (< 2.19.1)
40004013
with mock.patch(
40014014
"google.iam.credentials_v1.services.iam_credentials.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_TRACING",
40024015
False,
4016+
), mock.patch(
4017+
"google.iam.credentials_v1.services.iam_credentials.transports.base._ASYNC_WRAP_METHOD_SUPPORTS_KIND",
4018+
False,
40034019
):
40044020
mock_wrap.reset_mock()
40054021
transport._wrap_async_method(func, client_options=options, kind="grpc_asyncio")

‎packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#
1616
"""A compatibility module for older versions of google-api-core."""
1717

18+
import contextlib
1819
import os
1920
import json
2021

0 commit comments

Comments
 (0)