refactor(gapic-generator): deduplicate sync/async prep_wrapped_messages and transport method wrapping
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 35/100
- Tipo de issue
- Refactorización
- Claridad
- Necesita aclaración
- Estado de actividad
- Activo
- Stack tecnológico
- python
- Área
- build-system, tooling
Línea de trabajo
Start by comparing the synchronous implementation in packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2 with prep_wrapped_messages_async_method in _shared_macros.j2, then review the related PR #18433. Decide among the proposed consolidation approaches and verify that generated sync, AsyncIO gRPC, REST, and AsyncIO REST transports retain their wrapping and retry behavior without the duplicated templates.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Problem Summary
In packages/gapic-generator/gapic/templates/, there is code duplication and structural asymmetry between synchronous and asynchronous RPC preparation and method wrapping across the generated transport classes:
-
_prep_wrapped_messagesDuplication:packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/base.py.j2(lines 239–280) defines_prep_wrapped_messages(self, client_info)for synchronous transports.packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2(lines 322–371) definesprep_wrapped_messages_async_method(api, service)for asynchronous transports.- These two blocks are ~50 lines of Jinja template code that are nearly identical. Both iterate over
service.methods.values()andapi.mixin_api_methods.keys(), mapping every RPC to its retry predicate, default timeout, client info, fully-qualified method name, and streaming flags. The only differences are:self._wrap_methodvsself._wrap_async_methodretries.Retry(...)vsretries.AsyncRetry(...)
-
_wrap_methodvs_wrap_async_methodDuplication:- Both methods are defined side-by-side in
base.py.j2(lines 174–237). - Both methods implement identical compatibility checks and argument filtering across three generations of
google-api-core(client_options,kind,method_name,is_streaming), delegating respectively togapic_v1.method.wrap_methodandgapic_v1.method_async.wrap_method. - Consider moving these to
_compat.pyto reduce the footprint in generated code.
- Both methods are defined side-by-side in
-
Architectural Asymmetry (Base Class vs Shared Macros):
_wrap_async_methodis defined directly on the synchronous base class{{ service.name }}Transportinbase.py.j2, even though synchronous transports never call it.- Conversely, asynchronous
_prep_wrapped_messagesis not inbase.py.j2—it lives as an external Jinja macro in_shared_macros.j2that is injected intogrpc_asyncio.py.j2andrest_asyncio.py.j2to override the base class implementation. - This mixing makes the transport hierarchy harder to reason about and causes maintenance drift when retry parameters, metadata, or tracing attributes are updated.
Deduplication Options to Consider
Option A: Polymorphic Dispatch in Base Transport (Recommended)
Consolidate _prep_wrapped_messages into a single, unified implementation in base.py.j2 by making transport classes declare their retry class and RPC wrapper callable:
# In base.py.j2 ({{ service.name }}Transport):
_is_async: bool = False
_retry_type = retries.Retry
def _wrap_rpc(self, func, *args, **kwargs):
# Unified wrap helper parameterized by sync vs async
wrap_func = gapic_v1.method_async.wrap_method if self._is_async else gapic_v1.method.wrap_method
supports_tracing = _ASYNC_WRAP_METHOD_SUPPORTS_TRACING if self._is_async else _WRAP_METHOD_SUPPORTS_TRACING
if supports_tracing:
kwargs["client_options"] = self._client_options
if self.kind:
kwargs["kind"] = self.kind
return wrap_func(func, *args, **kwargs)
...
- Pros:
_prep_wrapped_messagesonly exists once inbase.py.j2. All transports (gRPC, AsyncIO gRPC, REST, AsyncIO REST) inherit it naturally.- Deletes
prep_wrapped_messages_async_methodfrom_shared_macros.j2entirely (~50 lines removed). - Eliminates duplicate template loops for mixin and service methods.
- Cons: Requires setting transport class-level attributes (
_is_async,_retry_type) on async transports.
Option B: Parameterized Jinja Macro
Unify synchronous and asynchronous message preparation into a single reusable Jinja macro in _shared_macros.j2:
{% macro prep_wrapped_messages(api, service, is_async=False) %}
def _prep_wrapped_messages(self, client_info):
self._wrapped_methods = {
{% for method in service.methods.values() %}
self.{{ method.transport_safe_name|snake_case }}: self.{{ "_wrap_async_method" if is_async else "_wrap_method" }}(
...
{% if method.retry %}
default_retry=retries.{{ "AsyncRetry" if is_async else "Retry" }}(...),
{% endif %}
...
),
{% endfor %}
}
{% endmacro %}
- Pros:
- Keeps generation purely in templates without modifying Python transport class contracts or inheritance semantics.
- Single source of truth for method iteration and configuration.
- Cons: Still renders two copies of the method across generated files; Jinja syntax becomes slightly more branchy.
Option C: Dedicated AsyncTransport Base Class
Introduce an explicit _Base{{ service.name }}AsyncTransport class (similar to _Base{{ service.name }}RestTransport):
-
{{ service.name }}Transport: Sync base class containing sync_prep_wrapped_messagesand_wrap_method. -
_Base{{ service.name }}AsyncTransport: Async base class containing async_prep_wrapped_messagesand_wrap_async_method. -
Pros:
- Clean separation of concerns; async wrappers are no longer baked into sync base classes.
- Idiomatic object-oriented hierarchy.
-
Cons:
- Larger structural change across generated codebases and public type contracts.
- Requires updating multiple transport inheritance graphs.
Related Discussion
- PR #18433:
feat(observability): [WIP] implement universal 4-path OpenTelemetry tracing - Follow-up from reviewer feedback regarding code duplication between sync and async transports.
- Lenguaje dominante
- Python
- Estrellas
- 5.4k
- Forks
- 1.8k
- Merge medio
- 2 d 7 h
- PR fusionados (30 d)
- 173
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de googleapis/google-cloud-python
-
firestore: field path with a quoted name ending in a backslash is misparsedPosiblemente ocupada @sebxt la tomó hace 25 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
googleapis/google-cloud-python#18771 ·
Los mantenedores suelen responder en 1 día
-
api: spanner type: feature request
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
googleapis/google-cloud-python#18584 ·
Los mantenedores suelen responder en 1 día
-
auth: `impersonated_credentials` loses `subject` on copy, so `with_scopes()` and `with_quota_project()` turn off domain wide delegationPosiblemente ocupada @Om-singhaI la tomó hace 55 días. Abiertoauth priority: p2 type: bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
googleapis/google-cloud-python#18428 ·
Los mantenedores suelen responder en 1 día
-
A client called `run_in_transaction` be stuck if Spanner is downPosiblemente ocupada @founderinaction la tomó hace 7 días. Abiertopriority: p2 type: bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
googleapis/google-cloud-python#18375 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
ci: enable --strict flag in presubmit twine_checkQuizá libre de nuevo @ohmayr la tomó hace 44 días y no hay ningún pull request abierto. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 76/100
googleapis/google-cloud-python#18339 · 1 comentario · 1 asignado ·
Los mantenedores suelen responder en 1 día
Todos los issues de googleapis/google-cloud-python
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
cpp-best-practices/project_validator#267 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Project-N-E-K-O/N.E.K.O#3725 ·
Los mantenedores suelen responder en 1 día
-
[Bug] Sending a Telegram message chain twice duplicates mentionsPosiblemente ocupada @123XieC la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
AstrBotDevs/AstrBot#11125 ·
Los mantenedores suelen responder en 1 día
-
backend bug priority: high
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
nooriegarg/ContribIQ#73 ·
Los mantenedores suelen responder en 1 día
-
bug p3 security
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
amd/gaia#8197 · 2 comentarios ·
Los mantenedores suelen responder en 1 día