Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
79f3668
feat(api-core): move request-id auto-population logic to gapic_v1 pub…
hebaalazzeh Jul 16, 2026
62fbcc8
refactor(api-core): rename gapic_v1.method_helpers to gapic_v1.reques…
hebaalazzeh Jul 16, 2026
20adea6
chore: address PR review comments for gapic centralization request ID
hebaalazzeh Jul 16, 2026
f03ac7c
feat(api-core): move request-id auto-population logic to gapic_v1 pub…
hebaalazzeh Jul 16, 2026
2cc27b5
feat: delegate request-id setup to public method_helpers
hebaalazzeh Jul 16, 2026
9c2121c
refactor: use request module instead of method_helpers in gapic-gener…
hebaalazzeh Jul 16, 2026
dbc7307
refactor(api-core): rename gapic_v1.method_helpers to gapic_v1.reques…
hebaalazzeh Jul 16, 2026
5f1b721
chore: address PR review comments for gapic centralization request ID
hebaalazzeh Jul 16, 2026
99cee8e
chore: remove private _setup_request_id and call setup_request_id hel…
hebaalazzeh Jul 16, 2026
4ff1910
test(api-core): update UUID regex in test_request.py
hebaalazzeh Jul 16, 2026
77f97c8
refactor(api-core): rename request.py to requests.py for consistency
hebaalazzeh Jul 17, 2026
7fdf94e
Merge branch 'feat/gapic-centralization-api-core-request-id' into fea…
hebaalazzeh Jul 17, 2026
37ad06f
refactor(generator): update templates and goldens to use requests ins…
hebaalazzeh Jul 17, 2026
b9e2c62
fix(api-core): handle dictionary requests with None value in setup_re…
hebaalazzeh Jul 17, 2026
6bb65bc
merge branch 'feat/gapic-centralization-api-core-request-id' into fea…
hebaalazzeh Jul 17, 2026
0482ae6
fix(api-core): remove redundant request.py and test_request.py, sort …
hebaalazzeh Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@
{% if method_settings is not none %}
{% for auto_populated_field in method_settings.auto_populated_fields %}
{% set is_proto3_optional = method.input.fields[auto_populated_field].proto3_optional %}
{% if is_async %}
self._client._setup_request_id(request, '{{ auto_populated_field }}', {{ is_proto3_optional }})
{% else %}
self._setup_request_id(request, '{{ auto_populated_field }}', {{ is_proto3_optional }})
{% endif %}
setup_request_id(request, '{{ auto_populated_field }}', {{ is_proto3_optional }})
{% endfor %}
{% endif %}{# if method_settings is not none #}
{% endwith %}{# method_settings #}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
{% import "%namespace/%name_%version/%sub/services/%service/_client_macros.j2" as macros %}
{% import "%namespace/%name_%version/%sub/services/%service/_shared_macros.j2" as shared_macros %}

{% set has_auto_populated_fields = namespace(value=false) %}
{% for method in service.methods.values() %}
{% set method_settings = api.all_method_settings.get(method.meta.address.proto) %}
{% if method_settings and method_settings.auto_populated_fields %}
{% set has_auto_populated_fields.value = true %}
{% endif %}
{% endfor %}

import logging as std_logging
from collections import OrderedDict
import re
from typing import Dict, Callable, Mapping, MutableMapping, MutableSequence, Optional, {% if service.any_server_streaming %}AsyncIterable, Awaitable, {% endif %}{% if service.any_client_streaming %}AsyncIterator, {% endif %}Sequence, Tuple, Type, Union
{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|select|list %}
import uuid
{% endif %}
{% if service.any_deprecated %}
import warnings
{% endif %}
Expand All @@ -21,6 +26,9 @@ from {{package_path}} import gapic_version as package_version
from google.api_core.client_options import ClientOptions
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
{% if has_auto_populated_fields.value %}
from google.api_core.gapic_v1.requests import setup_request_id
{% endif %}
from google.api_core import retry_async as retries
from google.auth import credentials as ga_credentials # type: ignore
from google.oauth2 import service_account # type: ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
{% import "%namespace/%name_%version/%sub/services/%service/_client_macros.j2" as macros %}
{% import "%namespace/%name_%version/%sub/services/%service/_shared_macros.j2" as shared_macros %}

{% set has_auto_populated_fields = namespace(value=false) %}
{% for method in service.methods.values() %}
{% set method_settings = api.all_method_settings.get(method.meta.address.proto) %}
{% if method_settings and method_settings.auto_populated_fields %}
{% set has_auto_populated_fields.value = true %}
{% endif %}
{% endfor %}

from collections import OrderedDict
{% if service.any_extended_operations_methods %}
import functools
Expand All @@ -16,9 +24,6 @@ import logging as std_logging
import os
import re
from typing import Dict, Callable, Mapping, MutableMapping, MutableSequence, Optional, {% if service.any_server_streaming %}Iterable, {% endif %}{% if service.any_client_streaming %}Iterator, {% endif %}Sequence, Tuple, Type, Union, cast
{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|select|list %}
import uuid
{% endif %}
import warnings

{% set package_path = api.naming.module_namespace|join('.') + "." + api.naming.versioned_module_name %}
Expand All @@ -30,6 +35,9 @@ from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation
{% endif %}
from google.api_core import gapic_v1
{% if has_auto_populated_fields.value %}
from google.api_core.gapic_v1.requests import setup_request_id
{% endif %}
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials # type: ignore
from google.auth.transport import mtls # type: ignore
Expand Down Expand Up @@ -453,37 +461,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
# NOTE (b/349488459): universe validation is disabled until further notice.
return True

{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|select|list %}
@staticmethod
def _setup_request_id(request, field_name: str, is_proto3_optional: bool):
"""Populate a UUID4 field in the request if it is not already set.

Args:
request (Union[google.protobuf.message.Message, dict]): The request object.
field_name (str): The name of the field to populate.
is_proto3_optional (bool): Whether the field is proto3 optional.
"""
if isinstance(request, dict):
if is_proto3_optional:
if field_name not in request:
request[field_name] = str(uuid.uuid4())
elif not request.get(field_name):
request[field_name] = str(uuid.uuid4())
return

if is_proto3_optional:
try:
# Pure protobuf messages
if not request.HasField(field_name):
setattr(request, field_name, str(uuid.uuid4()))
except (AttributeError, ValueError):
# Proto-plus messages or other objects
if field_name not in request:
setattr(request, field_name, str(uuid.uuid4()))
else:
if not getattr(request, field_name):
setattr(request, field_name, str(uuid.uuid4()))
{% endif %}

def _add_cred_info_for_auth_errors(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@
{% import "tests/unit/gapic/%name_%version/%sub/test_macros.j2" as test_macros %}
{% import "%namespace/%name_%version/%sub/services/%service/_shared_macros.j2" as shared_macros %}

{% set has_auto_populated_fields = namespace(value=false) %}
{% for method in service.methods.values() %}
{% set method_settings = api.all_method_settings.get(method.meta.address.proto) %}
{% if method_settings and method_settings.auto_populated_fields %}
{% set has_auto_populated_fields.value = true %}
{% endif %}
{% endfor %}

import os
import asyncio
{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|select|list %}
{% if has_auto_populated_fields.value %}
import re
{% endif %}
from unittest import mock
Expand Down Expand Up @@ -107,7 +115,7 @@ CRED_INFO_JSON = {
"principal": "service-account@example.com",
}
CRED_INFO_STRING = json.dumps(CRED_INFO_JSON)
{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|select|list %}
{% if has_auto_populated_fields.value %}
_UUID4_RE = re.compile(r"{{ uuid4_re }}")
{% endif %}

Expand Down Expand Up @@ -434,84 +442,6 @@ def test__add_cred_info_for_auth_errors_no_get_cred_info(error_code):
client._add_cred_info_for_auth_errors(error)
assert error.details == []

{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|select|list %}
def test__setup_request_id():
class MockRequest:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def __contains__(self, key):
return hasattr(self, key)

class MockProtoRequest:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def HasField(self, key):
return hasattr(self, key)

# Test with proto3 optional field not in request
request = MockRequest()
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert re.match(r"{{ test_macros.get_uuid4_re() }}", request.request_id)

# Test with proto3 optional field already in request
request = MockRequest(request_id="already_set")
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert request.request_id == "already_set"

# Test with non-proto3 optional field empty
request = MockRequest(request_id="")
{{ service.client_name }}._setup_request_id(request, "request_id", False)
assert re.match(r"{{ test_macros.get_uuid4_re() }}", request.request_id)

# Test with non-proto3 optional field already set
request = MockRequest(request_id="already_set")
{{ service.client_name }}._setup_request_id(request, "request_id", False)
assert request.request_id == "already_set"

# Test with proto3 optional field not in request (MockProtoRequest)
request = MockProtoRequest()
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert re.match(r"{{ test_macros.get_uuid4_re() }}", request.request_id)

# Test with proto3 optional field already in request (MockProtoRequest)
request = MockProtoRequest(request_id="already_set")
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert request.request_id == "already_set"

# Test with ValueError
class MockValueErrorRequest:
def HasField(self, key):
raise ValueError("Mismatched field")
def __contains__(self, key):
return hasattr(self, key)

request = MockValueErrorRequest()
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert re.match(r"{{ test_macros.get_uuid4_re() }}", request.request_id)

# Test with dict and proto3 optional field not in request
request = {}
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert re.match(r"{{ test_macros.get_uuid4_re() }}", request["request_id"])

# Test with dict and proto3 optional field already in request
request = {"request_id": "already_set"}
{{ service.client_name }}._setup_request_id(request, "request_id", True)
assert request["request_id"] == "already_set"

# Test with dict and non-proto3 optional field empty
request = {"request_id": ""}
{{ service.client_name }}._setup_request_id(request, "request_id", False)
assert re.match(r"{{ test_macros.get_uuid4_re() }}", request["request_id"])

# Test with dict and non-proto3 optional field already set
request = {"request_id": "already_set"}
{{ service.client_name }}._setup_request_id(request, "request_id", False)
assert request["request_id"] == "already_set"

{% endif %}
@pytest.mark.parametrize("client_class,transport_name", [
{% if 'grpc' in opts.transport %}
({{ service.client_name }}, "grpc"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core.gapic_v1.requests import setup_request_id
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials # type: ignore
from google.auth.transport import mtls # type: ignore
Expand Down Expand Up @@ -480,26 +481,7 @@ def _setup_request_id(request, field_name: str, is_proto3_optional: bool):
field_name (str): The name of the field to populate.
is_proto3_optional (bool): Whether the field is proto3 optional.
"""
if isinstance(request, dict):
if is_proto3_optional:
if field_name not in request:
request[field_name] = str(uuid.uuid4())
elif not request.get(field_name):
request[field_name] = str(uuid.uuid4())
return

if is_proto3_optional:
try:
# Pure protobuf messages
if not request.HasField(field_name):
setattr(request, field_name, str(uuid.uuid4()))
except (AttributeError, ValueError):
# Proto-plus messages or other objects
if field_name not in request:
setattr(request, field_name, str(uuid.uuid4()))
else:
if not getattr(request, field_name):
setattr(request, field_name, str(uuid.uuid4()))
setup_request_id(request, field_name, is_proto3_optional)

def _add_cred_info_for_auth_errors(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,82 +351,6 @@ def test__add_cred_info_for_auth_errors_no_get_cred_info(error_code):
client._add_cred_info_for_auth_errors(error)
assert error.details == []

def test__setup_request_id():
class MockRequest:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def __contains__(self, key):
return hasattr(self, key)

class MockProtoRequest:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def HasField(self, key):
return hasattr(self, key)

# Test with proto3 optional field not in request
request = MockRequest()
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", request.request_id)

# Test with proto3 optional field already in request
request = MockRequest(request_id="already_set")
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert request.request_id == "already_set"

# Test with non-proto3 optional field empty
request = MockRequest(request_id="")
StorageBatchOperationsClient._setup_request_id(request, "request_id", False)
assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", request.request_id)

# Test with non-proto3 optional field already set
request = MockRequest(request_id="already_set")
StorageBatchOperationsClient._setup_request_id(request, "request_id", False)
assert request.request_id == "already_set"

# Test with proto3 optional field not in request (MockProtoRequest)
request = MockProtoRequest()
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", request.request_id)

# Test with proto3 optional field already in request (MockProtoRequest)
request = MockProtoRequest(request_id="already_set")
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert request.request_id == "already_set"

# Test with ValueError
class MockValueErrorRequest:
def HasField(self, key):
raise ValueError("Mismatched field")
def __contains__(self, key):
return hasattr(self, key)

request = MockValueErrorRequest()
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", request.request_id)

# Test with dict and proto3 optional field not in request
request = {}
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", request["request_id"])

# Test with dict and proto3 optional field already in request
request = {"request_id": "already_set"}
StorageBatchOperationsClient._setup_request_id(request, "request_id", True)
assert request["request_id"] == "already_set"

# Test with dict and non-proto3 optional field empty
request = {"request_id": ""}
StorageBatchOperationsClient._setup_request_id(request, "request_id", False)
assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", request["request_id"])

# Test with dict and non-proto3 optional field already set
request = {"request_id": "already_set"}
StorageBatchOperationsClient._setup_request_id(request, "request_id", False)
assert request["request_id"] == "already_set"

@pytest.mark.parametrize("client_class,transport_name", [
(StorageBatchOperationsClient, "grpc"),
(StorageBatchOperationsAsyncClient, "grpc_asyncio"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
# Older Python versions safely ignore this variable.
__lazy_modules__: Set[str] = {
"google.api_core.gapic_v1.client_info",
"google.api_core.gapic_v1.requests",
"google.api_core.gapic_v1.routing_header",
}
__all__ = ["client_info", "routing_header"]
__all__ = ["client_info", "requests", "routing_header"]

if _has_grpc:
__lazy_modules__.update(
Expand All @@ -41,6 +42,7 @@

from google.api_core.gapic_v1 import ( # noqa: E402
client_info,
requests,
routing_header,
)

Expand Down
Loading
Loading