From 79f3668453554e7db059578a4276097080a78259 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 16:17:52 +0000 Subject: [PATCH 01/40] feat(api-core): move request-id auto-population logic to gapic_v1 public helpers Introduces method_helpers module containing setup_request_id helper. Exposes the module as public in gapic_v1. --- .../google/api_core/gapic_v1/__init__.py | 4 +- .../api_core/gapic_v1/method_helpers.py | 59 +++++++++ .../tests/unit/gapic/test_method_helpers.py | 118 ++++++++++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 packages/google-api-core/google/api_core/gapic_v1/method_helpers.py create mode 100644 packages/google-api-core/tests/unit/gapic/test_method_helpers.py diff --git a/packages/google-api-core/google/api_core/gapic_v1/__init__.py b/packages/google-api-core/google/api_core/gapic_v1/__init__.py index 48a27ec21d24..b112d6fc5cc0 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/__init__.py +++ b/packages/google-api-core/google/api_core/gapic_v1/__init__.py @@ -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.method_helpers", "google.api_core.gapic_v1.routing_header", } -__all__ = ["client_info", "routing_header"] +__all__ = ["client_info", "method_helpers", "routing_header"] if _has_grpc: __lazy_modules__.update( @@ -41,6 +42,7 @@ from google.api_core.gapic_v1 import ( # noqa: E402 client_info, + method_helpers, routing_header, ) diff --git a/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py b/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py new file mode 100644 index 000000000000..a79d23bf6b19 --- /dev/null +++ b/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Helpers for method requests.""" + +import uuid +from typing import Union +import google.protobuf.message + + +def setup_request_id( + request: Union[google.protobuf.message.Message, dict, None], + field_name: str, + is_proto3_optional: bool, +) -> None: + """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 request is None: + return + + 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 not getattr(request, field_name, None): + setattr(request, field_name, str(uuid.uuid4())) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, str(uuid.uuid4())) diff --git a/packages/google-api-core/tests/unit/gapic/test_method_helpers.py b/packages/google-api-core/tests/unit/gapic/test_method_helpers.py new file mode 100644 index 000000000000..61189a72a965 --- /dev/null +++ b/packages/google-api-core/tests/unit/gapic/test_method_helpers.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from google.api_core.gapic_v1.method_helpers import setup_request_id + + +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() + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with proto3 optional field already in request + request = MockRequest(request_id="already_set") + setup_request_id(request, "request_id", True) + assert request.request_id == "already_set" + + # Test with non-proto3 optional field empty + request = MockRequest(request_id="") + setup_request_id(request, "request_id", False) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with non-proto3 optional field already set + request = MockRequest(request_id="already_set") + setup_request_id(request, "request_id", False) + assert request.request_id == "already_set" + + # Test with proto3 optional field not in request (MockProtoRequest) + request = MockProtoRequest() + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with proto3 optional field already in request (MockProtoRequest) + request = MockProtoRequest(request_id="already_set") + 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() + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with dict and proto3 optional field not in request + request = {} + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request["request_id"], + ) + + # Test with dict and proto3 optional field already in request + request = {"request_id": "already_set"} + 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": ""} + setup_request_id(request, "request_id", False) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request["request_id"], + ) + + # Test with dict and non-proto3 optional field already set + request = {"request_id": "already_set"} + setup_request_id(request, "request_id", False) + assert request["request_id"] == "already_set" + + # Test with None request (should handle gracefully without raising exception) + setup_request_id(None, "request_id", True) From 62fbcc87261077bb2dd6e04508b0a694c2f62f56 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 18:14:03 +0000 Subject: [PATCH 02/40] refactor(api-core): rename gapic_v1.method_helpers to gapic_v1.request and improve docstring --- .../google-api-core/google/api_core/gapic_v1/__init__.py | 6 +++--- .../api_core/gapic_v1/{method_helpers.py => request.py} | 8 +++++++- .../gapic/{test_method_helpers.py => test_request.py} | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) rename packages/google-api-core/google/api_core/gapic_v1/{method_helpers.py => request.py} (89%) rename packages/google-api-core/tests/unit/gapic/{test_method_helpers.py => test_request.py} (98%) diff --git a/packages/google-api-core/google/api_core/gapic_v1/__init__.py b/packages/google-api-core/google/api_core/gapic_v1/__init__.py index b112d6fc5cc0..010ff6ed3ef7 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/__init__.py +++ b/packages/google-api-core/google/api_core/gapic_v1/__init__.py @@ -25,10 +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.method_helpers", + "google.api_core.gapic_v1.request", "google.api_core.gapic_v1.routing_header", } -__all__ = ["client_info", "method_helpers", "routing_header"] +__all__ = ["client_info", "request", "routing_header"] if _has_grpc: __lazy_modules__.update( @@ -42,7 +42,7 @@ from google.api_core.gapic_v1 import ( # noqa: E402 client_info, - method_helpers, + request, routing_header, ) diff --git a/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py b/packages/google-api-core/google/api_core/gapic_v1/request.py similarity index 89% rename from packages/google-api-core/google/api_core/gapic_v1/method_helpers.py rename to packages/google-api-core/google/api_core/gapic_v1/request.py index a79d23bf6b19..75672aeb7853 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py +++ b/packages/google-api-core/google/api_core/gapic_v1/request.py @@ -14,10 +14,16 @@ # limitations under the License. # -"""Helpers for method requests.""" +"""Helpers for preparing and structuring API requests. + +This module provides utilities to preprocess request parameters and objects +before invoking API methods, such as automatically generating request IDs +if they are not already set. +""" import uuid from typing import Union + import google.protobuf.message diff --git a/packages/google-api-core/tests/unit/gapic/test_method_helpers.py b/packages/google-api-core/tests/unit/gapic/test_request.py similarity index 98% rename from packages/google-api-core/tests/unit/gapic/test_method_helpers.py rename to packages/google-api-core/tests/unit/gapic/test_request.py index 61189a72a965..5cdb66ce05e4 100644 --- a/packages/google-api-core/tests/unit/gapic/test_method_helpers.py +++ b/packages/google-api-core/tests/unit/gapic/test_request.py @@ -14,7 +14,8 @@ # limitations under the License. import re -from google.api_core.gapic_v1.method_helpers import setup_request_id + +from google.api_core.gapic_v1.request import setup_request_id def test_setup_request_id(): From 20adea6b498f00f84def5e9f8378938cb5d7c2cd Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 21:15:24 +0000 Subject: [PATCH 03/40] chore: address PR review comments for gapic centralization request ID --- .../google/api_core/gapic_v1/request.py | 7 + .../tests/unit/gapic/test_request.py | 187 +++++++++--------- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/packages/google-api-core/google/api_core/gapic_v1/request.py b/packages/google-api-core/google/api_core/gapic_v1/request.py index 75672aeb7853..90c135d00f8c 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/request.py +++ b/packages/google-api-core/google/api_core/gapic_v1/request.py @@ -34,6 +34,13 @@ def setup_request_id( ) -> None: """Populate a UUID4 field in the request if it is not already set. + This helper is used to ensure request idempotency by automatically + generating a unique identifier (such as `request_id`) for requests + that support it. If a request is retried, the same identifier can be + sent on subsequent retries, allowing the server to recognize the retried + request and prevent duplicate processing (e.g., creating duplicate + resources). + Args: request (Union[google.protobuf.message.Message, dict]): The request object. diff --git a/packages/google-api-core/tests/unit/gapic/test_request.py b/packages/google-api-core/tests/unit/gapic/test_request.py index 5cdb66ce05e4..8fc4d2f110de 100644 --- a/packages/google-api-core/tests/unit/gapic/test_request.py +++ b/packages/google-api-core/tests/unit/gapic/test_request.py @@ -15,105 +15,98 @@ import re -from google.api_core.gapic_v1.request import setup_request_id - - -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() - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) - - # Test with proto3 optional field already in request - request = MockRequest(request_id="already_set") - setup_request_id(request, "request_id", True) - assert request.request_id == "already_set" - - # Test with non-proto3 optional field empty - request = MockRequest(request_id="") - setup_request_id(request, "request_id", False) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) +import pytest - # Test with non-proto3 optional field already set - request = MockRequest(request_id="already_set") - setup_request_id(request, "request_id", False) - assert request.request_id == "already_set" - - # Test with proto3 optional field not in request (MockProtoRequest) - request = MockProtoRequest() - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) - - # Test with proto3 optional field already in request (MockProtoRequest) - request = MockProtoRequest(request_id="already_set") - 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() - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) +from google.api_core.gapic_v1.request import setup_request_id - # Test with dict and proto3 optional field not in request - request = {} - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request["request_id"], - ) - # Test with dict and proto3 optional field already in request - request = {"request_id": "already_set"} - 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": ""} - setup_request_id(request, "request_id", False) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request["request_id"], +# --- Mock Request Helper Classes --- + + +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) + + +class MockValueErrorRequest: + def HasField(self, key): + raise ValueError("Mismatched field") + + def __contains__(self, key): + return hasattr(self, key) + + +# --- Parameterized Test --- + +UUID_REGEX = r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}" + + +@pytest.mark.parametrize( + "request_obj, is_proto3_optional, expected", + [ + # MockRequest cases + (MockRequest(), True, "uuid"), + (MockRequest(request_id="already_set"), True, "already_set"), + (MockRequest(request_id=""), False, "uuid"), + (MockRequest(request_id="already_set"), False, "already_set"), + # MockProtoRequest cases + (MockProtoRequest(), True, "uuid"), + (MockProtoRequest(request_id="already_set"), True, "already_set"), + # ValueError case + (MockValueErrorRequest(), True, "uuid"), + # Dict cases + ({}, True, "uuid"), + ({"request_id": "already_set"}, True, "already_set"), + ({"request_id": ""}, False, "uuid"), + ({"request_id": "already_set"}, False, "already_set"), + # None case + (None, True, "none"), + ], + ids=[ + "proto3_optional_not_in_request", + "proto3_optional_already_in_request", + "non_proto3_optional_empty", + "non_proto3_optional_already_set", + "proto3_optional_not_in_request_proto", + "proto3_optional_already_in_request_proto", + "value_error_fallback", + "dict_proto3_optional_not_in_request", + "dict_proto3_optional_already_in_request", + "dict_non_proto3_optional_empty", + "dict_non_proto3_optional_already_set", + "none_request", + ], +) +def test_setup_request_id(request_obj, is_proto3_optional, expected): + # Act + setup_request_id(request_obj, "request_id", is_proto3_optional) + + # Assert + if expected == "none": + assert request_obj is None + return + + # Extract the resulting value depending on container type + value = ( + request_obj["request_id"] + if isinstance(request_obj, dict) + else request_obj.request_id ) - # Test with dict and non-proto3 optional field already set - request = {"request_id": "already_set"} - setup_request_id(request, "request_id", False) - assert request["request_id"] == "already_set" - - # Test with None request (should handle gracefully without raising exception) - setup_request_id(None, "request_id", True) + if expected == "uuid": + assert re.match(UUID_REGEX, value) + else: + assert value == expected From f03ac7c519da0d0504a0334ec09d32f1e7a8328e Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 16:17:52 +0000 Subject: [PATCH 04/40] feat(api-core): move request-id auto-population logic to gapic_v1 public helpers Introduces method_helpers module containing setup_request_id helper. Exposes the module as public in gapic_v1. --- .../google/api_core/gapic_v1/__init__.py | 4 +- .../api_core/gapic_v1/method_helpers.py | 59 +++++++++ .../tests/unit/gapic/test_method_helpers.py | 118 ++++++++++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 packages/google-api-core/google/api_core/gapic_v1/method_helpers.py create mode 100644 packages/google-api-core/tests/unit/gapic/test_method_helpers.py diff --git a/packages/google-api-core/google/api_core/gapic_v1/__init__.py b/packages/google-api-core/google/api_core/gapic_v1/__init__.py index 48a27ec21d24..b112d6fc5cc0 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/__init__.py +++ b/packages/google-api-core/google/api_core/gapic_v1/__init__.py @@ -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.method_helpers", "google.api_core.gapic_v1.routing_header", } -__all__ = ["client_info", "routing_header"] +__all__ = ["client_info", "method_helpers", "routing_header"] if _has_grpc: __lazy_modules__.update( @@ -41,6 +42,7 @@ from google.api_core.gapic_v1 import ( # noqa: E402 client_info, + method_helpers, routing_header, ) diff --git a/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py b/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py new file mode 100644 index 000000000000..a79d23bf6b19 --- /dev/null +++ b/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Helpers for method requests.""" + +import uuid +from typing import Union +import google.protobuf.message + + +def setup_request_id( + request: Union[google.protobuf.message.Message, dict, None], + field_name: str, + is_proto3_optional: bool, +) -> None: + """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 request is None: + return + + 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 not getattr(request, field_name, None): + setattr(request, field_name, str(uuid.uuid4())) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, str(uuid.uuid4())) diff --git a/packages/google-api-core/tests/unit/gapic/test_method_helpers.py b/packages/google-api-core/tests/unit/gapic/test_method_helpers.py new file mode 100644 index 000000000000..61189a72a965 --- /dev/null +++ b/packages/google-api-core/tests/unit/gapic/test_method_helpers.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +from google.api_core.gapic_v1.method_helpers import setup_request_id + + +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() + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with proto3 optional field already in request + request = MockRequest(request_id="already_set") + setup_request_id(request, "request_id", True) + assert request.request_id == "already_set" + + # Test with non-proto3 optional field empty + request = MockRequest(request_id="") + setup_request_id(request, "request_id", False) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with non-proto3 optional field already set + request = MockRequest(request_id="already_set") + setup_request_id(request, "request_id", False) + assert request.request_id == "already_set" + + # Test with proto3 optional field not in request (MockProtoRequest) + request = MockProtoRequest() + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with proto3 optional field already in request (MockProtoRequest) + request = MockProtoRequest(request_id="already_set") + 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() + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request.request_id, + ) + + # Test with dict and proto3 optional field not in request + request = {} + setup_request_id(request, "request_id", True) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request["request_id"], + ) + + # Test with dict and proto3 optional field already in request + request = {"request_id": "already_set"} + 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": ""} + setup_request_id(request, "request_id", False) + assert re.match( + r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", + request["request_id"], + ) + + # Test with dict and non-proto3 optional field already set + request = {"request_id": "already_set"} + setup_request_id(request, "request_id", False) + assert request["request_id"] == "already_set" + + # Test with None request (should handle gracefully without raising exception) + setup_request_id(None, "request_id", True) From 2cc27b5b2803acfe090a31810fecbac8d2bb01fa Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 16:20:12 +0000 Subject: [PATCH 05/40] feat: delegate request-id setup to public method_helpers Updates generator templates and goldens to import public method_helpers from google-api-core gapic_v1 and call setup_request_id helper. Removes duplicate setup_request_id test logic from generated client unit tests. --- .../%sub/services/%service/async_client.py.j2 | 3 - .../%sub/services/%service/client.py.j2 | 37 +++----- .../%name_%version/%sub/test_%service.py.j2 | 90 +++---------------- .../storage_batch_operations/client.py | 22 +---- .../test_storage_batch_operations.py | 76 ---------------- 5 files changed, 25 insertions(+), 203 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 index 5fe416902b86..84c6cdd2bd3e 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 @@ -8,9 +8,6 @@ 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 %} diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index e2e3edb24967..530361055a32 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -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 @@ -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 %} @@ -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 import method_helpers +{% 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 @@ -453,7 +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 %} + {% if has_auto_populated_fields.value %} @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. @@ -463,26 +471,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): 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())) + method_helpers.setup_request_id(request, field_name, is_proto3_optional) {% endif %} def _add_cred_info_for_auth_errors( 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 c68b390b7c84..aa7c48173f5c 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 @@ -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 @@ -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 %} @@ -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"), diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index 5f79cf8e016a..3afb80ac1d10 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -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 import method_helpers 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 @@ -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())) + method_helpers.setup_request_id(request, field_name, is_proto3_optional) def _add_cred_info_for_auth_errors( self, 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 5c53e97f8d12..3697bd9a4839 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 @@ -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"), From 9c2121cb78d74db4b418a6cfb5522a84cf275d8b Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 18:19:45 +0000 Subject: [PATCH 06/40] refactor: use request module instead of method_helpers in gapic-generator --- .../%name_%version/%sub/services/%service/client.py.j2 | 4 ++-- .../services/storage_batch_operations/client.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index 530361055a32..d54885bdcf98 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -36,7 +36,7 @@ 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 import method_helpers +from google.api_core.gapic_v1 import request {% endif %} from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore @@ -471,7 +471,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): field_name (str): The name of the field to populate. is_proto3_optional (bool): Whether the field is proto3 optional. """ - method_helpers.setup_request_id(request, field_name, is_proto3_optional) + request.setup_request_id(request, field_name, is_proto3_optional) {% endif %} def _add_cred_info_for_auth_errors( diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index 3afb80ac1d10..95ad1da16ea1 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -28,7 +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 import method_helpers +from google.api_core.gapic_v1 import request 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 @@ -481,7 +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. """ - method_helpers.setup_request_id(request, field_name, is_proto3_optional) + request.setup_request_id(request, field_name, is_proto3_optional) def _add_cred_info_for_auth_errors( self, From dbc73075618f9417c9435b2d0f4dcb4208318911 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 18:14:03 +0000 Subject: [PATCH 07/40] refactor(api-core): rename gapic_v1.method_helpers to gapic_v1.request and improve docstring --- .../google-api-core/google/api_core/gapic_v1/__init__.py | 6 +++--- .../api_core/gapic_v1/{method_helpers.py => request.py} | 8 +++++++- .../gapic/{test_method_helpers.py => test_request.py} | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) rename packages/google-api-core/google/api_core/gapic_v1/{method_helpers.py => request.py} (89%) rename packages/google-api-core/tests/unit/gapic/{test_method_helpers.py => test_request.py} (98%) diff --git a/packages/google-api-core/google/api_core/gapic_v1/__init__.py b/packages/google-api-core/google/api_core/gapic_v1/__init__.py index b112d6fc5cc0..010ff6ed3ef7 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/__init__.py +++ b/packages/google-api-core/google/api_core/gapic_v1/__init__.py @@ -25,10 +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.method_helpers", + "google.api_core.gapic_v1.request", "google.api_core.gapic_v1.routing_header", } -__all__ = ["client_info", "method_helpers", "routing_header"] +__all__ = ["client_info", "request", "routing_header"] if _has_grpc: __lazy_modules__.update( @@ -42,7 +42,7 @@ from google.api_core.gapic_v1 import ( # noqa: E402 client_info, - method_helpers, + request, routing_header, ) diff --git a/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py b/packages/google-api-core/google/api_core/gapic_v1/request.py similarity index 89% rename from packages/google-api-core/google/api_core/gapic_v1/method_helpers.py rename to packages/google-api-core/google/api_core/gapic_v1/request.py index a79d23bf6b19..75672aeb7853 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/method_helpers.py +++ b/packages/google-api-core/google/api_core/gapic_v1/request.py @@ -14,10 +14,16 @@ # limitations under the License. # -"""Helpers for method requests.""" +"""Helpers for preparing and structuring API requests. + +This module provides utilities to preprocess request parameters and objects +before invoking API methods, such as automatically generating request IDs +if they are not already set. +""" import uuid from typing import Union + import google.protobuf.message diff --git a/packages/google-api-core/tests/unit/gapic/test_method_helpers.py b/packages/google-api-core/tests/unit/gapic/test_request.py similarity index 98% rename from packages/google-api-core/tests/unit/gapic/test_method_helpers.py rename to packages/google-api-core/tests/unit/gapic/test_request.py index 61189a72a965..5cdb66ce05e4 100644 --- a/packages/google-api-core/tests/unit/gapic/test_method_helpers.py +++ b/packages/google-api-core/tests/unit/gapic/test_request.py @@ -14,7 +14,8 @@ # limitations under the License. import re -from google.api_core.gapic_v1.method_helpers import setup_request_id + +from google.api_core.gapic_v1.request import setup_request_id def test_setup_request_id(): From 5f1b7214efc097f86d61970ffa7c286b174ee7c5 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 21:15:24 +0000 Subject: [PATCH 08/40] chore: address PR review comments for gapic centralization request ID --- .../google/api_core/gapic_v1/request.py | 7 + .../tests/unit/gapic/test_request.py | 187 +++++++++--------- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/packages/google-api-core/google/api_core/gapic_v1/request.py b/packages/google-api-core/google/api_core/gapic_v1/request.py index 75672aeb7853..90c135d00f8c 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/request.py +++ b/packages/google-api-core/google/api_core/gapic_v1/request.py @@ -34,6 +34,13 @@ def setup_request_id( ) -> None: """Populate a UUID4 field in the request if it is not already set. + This helper is used to ensure request idempotency by automatically + generating a unique identifier (such as `request_id`) for requests + that support it. If a request is retried, the same identifier can be + sent on subsequent retries, allowing the server to recognize the retried + request and prevent duplicate processing (e.g., creating duplicate + resources). + Args: request (Union[google.protobuf.message.Message, dict]): The request object. diff --git a/packages/google-api-core/tests/unit/gapic/test_request.py b/packages/google-api-core/tests/unit/gapic/test_request.py index 5cdb66ce05e4..8fc4d2f110de 100644 --- a/packages/google-api-core/tests/unit/gapic/test_request.py +++ b/packages/google-api-core/tests/unit/gapic/test_request.py @@ -15,105 +15,98 @@ import re -from google.api_core.gapic_v1.request import setup_request_id - - -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() - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) - - # Test with proto3 optional field already in request - request = MockRequest(request_id="already_set") - setup_request_id(request, "request_id", True) - assert request.request_id == "already_set" - - # Test with non-proto3 optional field empty - request = MockRequest(request_id="") - setup_request_id(request, "request_id", False) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) +import pytest - # Test with non-proto3 optional field already set - request = MockRequest(request_id="already_set") - setup_request_id(request, "request_id", False) - assert request.request_id == "already_set" - - # Test with proto3 optional field not in request (MockProtoRequest) - request = MockProtoRequest() - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) - - # Test with proto3 optional field already in request (MockProtoRequest) - request = MockProtoRequest(request_id="already_set") - 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() - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request.request_id, - ) +from google.api_core.gapic_v1.request import setup_request_id - # Test with dict and proto3 optional field not in request - request = {} - setup_request_id(request, "request_id", True) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request["request_id"], - ) - # Test with dict and proto3 optional field already in request - request = {"request_id": "already_set"} - 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": ""} - setup_request_id(request, "request_id", False) - assert re.match( - r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", - request["request_id"], +# --- Mock Request Helper Classes --- + + +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) + + +class MockValueErrorRequest: + def HasField(self, key): + raise ValueError("Mismatched field") + + def __contains__(self, key): + return hasattr(self, key) + + +# --- Parameterized Test --- + +UUID_REGEX = r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}" + + +@pytest.mark.parametrize( + "request_obj, is_proto3_optional, expected", + [ + # MockRequest cases + (MockRequest(), True, "uuid"), + (MockRequest(request_id="already_set"), True, "already_set"), + (MockRequest(request_id=""), False, "uuid"), + (MockRequest(request_id="already_set"), False, "already_set"), + # MockProtoRequest cases + (MockProtoRequest(), True, "uuid"), + (MockProtoRequest(request_id="already_set"), True, "already_set"), + # ValueError case + (MockValueErrorRequest(), True, "uuid"), + # Dict cases + ({}, True, "uuid"), + ({"request_id": "already_set"}, True, "already_set"), + ({"request_id": ""}, False, "uuid"), + ({"request_id": "already_set"}, False, "already_set"), + # None case + (None, True, "none"), + ], + ids=[ + "proto3_optional_not_in_request", + "proto3_optional_already_in_request", + "non_proto3_optional_empty", + "non_proto3_optional_already_set", + "proto3_optional_not_in_request_proto", + "proto3_optional_already_in_request_proto", + "value_error_fallback", + "dict_proto3_optional_not_in_request", + "dict_proto3_optional_already_in_request", + "dict_non_proto3_optional_empty", + "dict_non_proto3_optional_already_set", + "none_request", + ], +) +def test_setup_request_id(request_obj, is_proto3_optional, expected): + # Act + setup_request_id(request_obj, "request_id", is_proto3_optional) + + # Assert + if expected == "none": + assert request_obj is None + return + + # Extract the resulting value depending on container type + value = ( + request_obj["request_id"] + if isinstance(request_obj, dict) + else request_obj.request_id ) - # Test with dict and non-proto3 optional field already set - request = {"request_id": "already_set"} - setup_request_id(request, "request_id", False) - assert request["request_id"] == "already_set" - - # Test with None request (should handle gracefully without raising exception) - setup_request_id(None, "request_id", True) + if expected == "uuid": + assert re.match(UUID_REGEX, value) + else: + assert value == expected From 99cee8ea0545215dd8c1d3aa75a5fcdd99aab6dd Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 21:22:45 +0000 Subject: [PATCH 09/40] chore: remove private _setup_request_id and call setup_request_id helper directly --- .../%sub/services/%service/_shared_macros.j2 | 6 +----- .../%sub/services/%service/async_client.py.j2 | 11 +++++++++++ .../%sub/services/%service/client.py.j2 | 13 +------------ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 index 755e4530e7ba..5f1b236f1780 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 @@ -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 #} diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 index 84c6cdd2bd3e..19a39aa53f3c 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 @@ -4,6 +4,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 %} + import logging as std_logging from collections import OrderedDict import re @@ -18,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.request 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 diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index d54885bdcf98..a29896142286 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -36,7 +36,7 @@ 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 import request +from google.api_core.gapic_v1.request import setup_request_id {% endif %} from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore @@ -461,18 +461,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta): # NOTE (b/349488459): universe validation is disabled until further notice. return True - {% if has_auto_populated_fields.value %} - @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. - """ - request.setup_request_id(request, field_name, is_proto3_optional) - {% endif %} def _add_cred_info_for_auth_errors( self, From 4ff1910b7e648a6683edac8aa85f7398b7bd3e02 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Thu, 16 Jul 2026 21:35:56 +0000 Subject: [PATCH 10/40] test(api-core): update UUID regex in test_request.py --- packages/google-api-core/tests/unit/gapic/test_request.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/google-api-core/tests/unit/gapic/test_request.py b/packages/google-api-core/tests/unit/gapic/test_request.py index 8fc4d2f110de..14ec9bb5dd24 100644 --- a/packages/google-api-core/tests/unit/gapic/test_request.py +++ b/packages/google-api-core/tests/unit/gapic/test_request.py @@ -51,7 +51,9 @@ def __contains__(self, key): # --- Parameterized Test --- -UUID_REGEX = r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}" +UUID_REGEX = ( + r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}" +) @pytest.mark.parametrize( From 77f97c8044e4a13e397fec541d80e937333ceb8f Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Fri, 17 Jul 2026 14:01:55 +0000 Subject: [PATCH 11/40] refactor(api-core): rename request.py to requests.py for consistency --- .../google-api-core/google/api_core/gapic_v1/__init__.py | 6 +++--- .../google/api_core/gapic_v1/{request.py => requests.py} | 0 .../tests/unit/gapic/{test_request.py => test_requests.py} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename packages/google-api-core/google/api_core/gapic_v1/{request.py => requests.py} (100%) rename packages/google-api-core/tests/unit/gapic/{test_request.py => test_requests.py} (98%) diff --git a/packages/google-api-core/google/api_core/gapic_v1/__init__.py b/packages/google-api-core/google/api_core/gapic_v1/__init__.py index 010ff6ed3ef7..78937c032670 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/__init__.py +++ b/packages/google-api-core/google/api_core/gapic_v1/__init__.py @@ -25,10 +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.request", + "google.api_core.gapic_v1.requests", "google.api_core.gapic_v1.routing_header", } -__all__ = ["client_info", "request", "routing_header"] +__all__ = ["client_info", "requests", "routing_header"] if _has_grpc: __lazy_modules__.update( @@ -42,7 +42,7 @@ from google.api_core.gapic_v1 import ( # noqa: E402 client_info, - request, + requests, routing_header, ) diff --git a/packages/google-api-core/google/api_core/gapic_v1/request.py b/packages/google-api-core/google/api_core/gapic_v1/requests.py similarity index 100% rename from packages/google-api-core/google/api_core/gapic_v1/request.py rename to packages/google-api-core/google/api_core/gapic_v1/requests.py diff --git a/packages/google-api-core/tests/unit/gapic/test_request.py b/packages/google-api-core/tests/unit/gapic/test_requests.py similarity index 98% rename from packages/google-api-core/tests/unit/gapic/test_request.py rename to packages/google-api-core/tests/unit/gapic/test_requests.py index 8fc4d2f110de..603241ba4e3e 100644 --- a/packages/google-api-core/tests/unit/gapic/test_request.py +++ b/packages/google-api-core/tests/unit/gapic/test_requests.py @@ -17,7 +17,7 @@ import pytest -from google.api_core.gapic_v1.request import setup_request_id +from google.api_core.gapic_v1.requests import setup_request_id # --- Mock Request Helper Classes --- From 37ad06f3818609a6cb85279f78aff75a03715ecf Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Fri, 17 Jul 2026 14:03:04 +0000 Subject: [PATCH 12/40] refactor(generator): update templates and goldens to use requests instead of request module for setup_request_id --- .../%name_%version/%sub/services/%service/async_client.py.j2 | 2 +- .../%name_%version/%sub/services/%service/client.py.j2 | 2 +- .../services/storage_batch_operations/client.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 index 19a39aa53f3c..2dc1f7350258 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 @@ -27,7 +27,7 @@ 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.request import setup_request_id +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 diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index a29896142286..fff3a08b0013 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -36,7 +36,7 @@ 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.request import setup_request_id +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 diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index 95ad1da16ea1..27c7188f4a3e 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -28,7 +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 import request +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 @@ -481,7 +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. """ - request.setup_request_id(request, field_name, is_proto3_optional) + setup_request_id(request, field_name, is_proto3_optional) def _add_cred_info_for_auth_errors( self, From b9e2c6200a8c4c5636e767295af644f120b52297 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Fri, 17 Jul 2026 14:07:40 +0000 Subject: [PATCH 13/40] fix(api-core): handle dictionary requests with None value in setup_request_id --- packages/google-api-core/google/api_core/gapic_v1/requests.py | 2 +- packages/google-api-core/tests/unit/gapic/test_requests.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-api-core/google/api_core/gapic_v1/requests.py b/packages/google-api-core/google/api_core/gapic_v1/requests.py index 90c135d00f8c..8ce97c9cffa7 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/requests.py +++ b/packages/google-api-core/google/api_core/gapic_v1/requests.py @@ -52,7 +52,7 @@ def setup_request_id( if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: + if field_name not in request or request[field_name] is None: request[field_name] = str(uuid.uuid4()) elif not request.get(field_name): request[field_name] = str(uuid.uuid4()) diff --git a/packages/google-api-core/tests/unit/gapic/test_requests.py b/packages/google-api-core/tests/unit/gapic/test_requests.py index 603241ba4e3e..1e921955d043 100644 --- a/packages/google-api-core/tests/unit/gapic/test_requests.py +++ b/packages/google-api-core/tests/unit/gapic/test_requests.py @@ -69,8 +69,10 @@ def __contains__(self, key): (MockValueErrorRequest(), True, "uuid"), # Dict cases ({}, True, "uuid"), + ({"request_id": None}, True, "uuid"), ({"request_id": "already_set"}, True, "already_set"), ({"request_id": ""}, False, "uuid"), + ({"request_id": None}, False, "uuid"), ({"request_id": "already_set"}, False, "already_set"), # None case (None, True, "none"), @@ -84,8 +86,10 @@ def __contains__(self, key): "proto3_optional_already_in_request_proto", "value_error_fallback", "dict_proto3_optional_not_in_request", + "dict_proto3_optional_value_none", "dict_proto3_optional_already_in_request", "dict_non_proto3_optional_empty", + "dict_non_proto3_optional_value_none", "dict_non_proto3_optional_already_set", "none_request", ], From 0482ae6ede02ec94732ee1e7c99fd53c0099c7bb Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Fri, 17 Jul 2026 14:13:46 +0000 Subject: [PATCH 14/40] fix(api-core): remove redundant request.py and test_request.py, sort imports in requests.py --- .../google/api_core/gapic_v1/request.py | 72 ----------- .../google/api_core/gapic_v1/requests.py | 2 +- .../tests/unit/gapic/test_request.py | 114 ------------------ 3 files changed, 1 insertion(+), 187 deletions(-) delete mode 100644 packages/google-api-core/google/api_core/gapic_v1/request.py delete mode 100644 packages/google-api-core/tests/unit/gapic/test_request.py diff --git a/packages/google-api-core/google/api_core/gapic_v1/request.py b/packages/google-api-core/google/api_core/gapic_v1/request.py deleted file mode 100644 index 90c135d00f8c..000000000000 --- a/packages/google-api-core/google/api_core/gapic_v1/request.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Helpers for preparing and structuring API requests. - -This module provides utilities to preprocess request parameters and objects -before invoking API methods, such as automatically generating request IDs -if they are not already set. -""" - -import uuid -from typing import Union - -import google.protobuf.message - - -def setup_request_id( - request: Union[google.protobuf.message.Message, dict, None], - field_name: str, - is_proto3_optional: bool, -) -> None: - """Populate a UUID4 field in the request if it is not already set. - - This helper is used to ensure request idempotency by automatically - generating a unique identifier (such as `request_id`) for requests - that support it. If a request is retried, the same identifier can be - sent on subsequent retries, allowing the server to recognize the retried - request and prevent duplicate processing (e.g., creating duplicate - resources). - - 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 request is None: - return - - 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 not getattr(request, field_name, None): - setattr(request, field_name, str(uuid.uuid4())) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, str(uuid.uuid4())) diff --git a/packages/google-api-core/google/api_core/gapic_v1/requests.py b/packages/google-api-core/google/api_core/gapic_v1/requests.py index 8ce97c9cffa7..f440ac69126c 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/requests.py +++ b/packages/google-api-core/google/api_core/gapic_v1/requests.py @@ -21,8 +21,8 @@ if they are not already set. """ -import uuid from typing import Union +import uuid import google.protobuf.message diff --git a/packages/google-api-core/tests/unit/gapic/test_request.py b/packages/google-api-core/tests/unit/gapic/test_request.py deleted file mode 100644 index 14ec9bb5dd24..000000000000 --- a/packages/google-api-core/tests/unit/gapic/test_request.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re - -import pytest - -from google.api_core.gapic_v1.request import setup_request_id - - -# --- Mock Request Helper Classes --- - - -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) - - -class MockValueErrorRequest: - def HasField(self, key): - raise ValueError("Mismatched field") - - def __contains__(self, key): - return hasattr(self, key) - - -# --- Parameterized Test --- - -UUID_REGEX = ( - r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}" -) - - -@pytest.mark.parametrize( - "request_obj, is_proto3_optional, expected", - [ - # MockRequest cases - (MockRequest(), True, "uuid"), - (MockRequest(request_id="already_set"), True, "already_set"), - (MockRequest(request_id=""), False, "uuid"), - (MockRequest(request_id="already_set"), False, "already_set"), - # MockProtoRequest cases - (MockProtoRequest(), True, "uuid"), - (MockProtoRequest(request_id="already_set"), True, "already_set"), - # ValueError case - (MockValueErrorRequest(), True, "uuid"), - # Dict cases - ({}, True, "uuid"), - ({"request_id": "already_set"}, True, "already_set"), - ({"request_id": ""}, False, "uuid"), - ({"request_id": "already_set"}, False, "already_set"), - # None case - (None, True, "none"), - ], - ids=[ - "proto3_optional_not_in_request", - "proto3_optional_already_in_request", - "non_proto3_optional_empty", - "non_proto3_optional_already_set", - "proto3_optional_not_in_request_proto", - "proto3_optional_already_in_request_proto", - "value_error_fallback", - "dict_proto3_optional_not_in_request", - "dict_proto3_optional_already_in_request", - "dict_non_proto3_optional_empty", - "dict_non_proto3_optional_already_set", - "none_request", - ], -) -def test_setup_request_id(request_obj, is_proto3_optional, expected): - # Act - setup_request_id(request_obj, "request_id", is_proto3_optional) - - # Assert - if expected == "none": - assert request_obj is None - return - - # Extract the resulting value depending on container type - value = ( - request_obj["request_id"] - if isinstance(request_obj, dict) - else request_obj.request_id - ) - - if expected == "uuid": - assert re.match(UUID_REGEX, value) - else: - assert value == expected From 2001c790ed2fd17a0ad7c17e82a1890ebf76057e Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Mon, 20 Jul 2026 19:44:22 +0000 Subject: [PATCH 15/40] feat(generator): add _compat.py fallback for setup_request_id --- .../gapic/generator/generator.py | 2 +- .../%name_%version/%sub/_compat.py.j2 | 36 +++++++++++++++++++ .../%sub/services/%service/async_client.py.j2 | 2 +- .../%sub/services/%service/client.py.j2 | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index 9fe56aa9de1d..c1932abbe426 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -120,7 +120,7 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo for template_name in client_templates: # Quick check: Skip "private" templates. filename = template_name.split("/")[-1] - if filename.startswith("_") and filename != "__init__.py.j2": + if filename.startswith("_") and filename not in ("__init__.py.j2", "_compat.py.j2"): continue # Append to the output files dictionary. diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 new file mode 100644 index 000000000000..1dd644e778ff --- /dev/null +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -0,0 +1,36 @@ +# {% include '_license.j2' %} + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 index 2dc1f7350258..759a4a052c54 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 @@ -27,7 +27,7 @@ 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 +from {{package_path}}._compat import setup_request_id {% endif %} from google.api_core import retry_async as retries from google.auth import credentials as ga_credentials # type: ignore diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index fff3a08b0013..cfeb327a486d 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -36,7 +36,7 @@ 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 +from {{package_path}}._compat import setup_request_id {% endif %} from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore From 2d1d2a511fab84c80f11f4b6d84b2950aceaf8fb Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 06:59:52 +0000 Subject: [PATCH 16/40] fix(generator): add type ignore to requests import in compat template and update goldens --- .../%name_%version/%sub/_compat.py.j2 | 2 +- .../asset/google/cloud/asset_v1/_compat.py | 50 +++++++++++++++++++ .../google/iam/credentials_v1/_compat.py | 50 +++++++++++++++++++ .../google/cloud/eventarc_v1/_compat.py | 50 +++++++++++++++++++ .../google/cloud/logging_v2/_compat.py | 50 +++++++++++++++++++ .../google/cloud/logging_v2/_compat.py | 50 +++++++++++++++++++ .../redis/google/cloud/redis_v1/_compat.py | 50 +++++++++++++++++++ .../google/cloud/redis_v1/_compat.py | 50 +++++++++++++++++++ .../storagebatchoperations_v1/_compat.py | 50 +++++++++++++++++++ .../storage_batch_operations/client.py | 2 +- 10 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py create mode 100644 packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 1dd644e778ff..aacea1c9b0ac 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -3,7 +3,7 @@ import uuid try: - from google.api_core.gapic_v1.requests import setup_request_id + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. def setup_request_id(request, field_name: str, is_proto3_optional: bool): diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py new file mode 100644 index 000000000000..443799f77b21 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.requests import setup_request_id # type: ignore +except ImportError: + # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + 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())) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index 27c7188f4a3e..e3610bd3e648 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -28,7 +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.cloud.storagebatchoperations_v1._compat 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 From 9e169f8407345e3aadd8e61693194ceb0579a894 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 07:10:46 +0000 Subject: [PATCH 17/40] fix(generator): add coverage pragma comments for fallback path in compat template, and update goldens --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 2 +- .../integration/goldens/asset/google/cloud/asset_v1/_compat.py | 2 +- .../goldens/credentials/google/iam/credentials_v1/_compat.py | 2 +- .../goldens/eventarc/google/cloud/eventarc_v1/_compat.py | 2 +- .../goldens/logging/google/cloud/logging_v2/_compat.py | 2 +- .../goldens/logging_internal/google/cloud/logging_v2/_compat.py | 2 +- .../integration/goldens/redis/google/cloud/redis_v1/_compat.py | 2 +- .../goldens/redis_selective/google/cloud/redis_v1/_compat.py | 2 +- .../google/cloud/storagebatchoperations_v1/_compat.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index aacea1c9b0ac..7f02b5e5e56f 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -4,7 +4,7 @@ import uuid try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index 443799f77b21..d874be390c06 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -18,7 +18,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: +except ImportError: # pragma: NO COVER # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. 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. From 61aaabbafd166acdb8d5e5e82b304a650600161b Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 17:13:53 +0000 Subject: [PATCH 18/40] chore(generator): update golden client.py files with setup_request_id import --- .../asset/google/cloud/asset_v1/services/asset_service/client.py | 1 + .../google/iam/credentials_v1/services/iam_credentials/client.py | 1 + .../google/cloud/eventarc_v1/services/eventarc/client.py | 1 + .../google/cloud/logging_v2/services/config_service_v2/client.py | 1 + .../cloud/logging_v2/services/logging_service_v2/client.py | 1 + .../cloud/logging_v2/services/metrics_service_v2/client.py | 1 + .../google/cloud/logging_v2/services/config_service_v2/client.py | 1 + .../cloud/logging_v2/services/logging_service_v2/client.py | 1 + .../cloud/logging_v2/services/metrics_service_v2/client.py | 1 + .../redis/google/cloud/redis_v1/services/cloud_redis/client.py | 1 + .../google/cloud/redis_v1/services/cloud_redis/client.py | 1 + 11 files changed, 11 insertions(+) diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py index 590b6fa1c615..6904b467710f 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py @@ -27,6 +27,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.cloud.asset_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py index 4ad970da32e9..05b2fb36f276 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py @@ -27,6 +27,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.iam.credentials_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py index 7255d3c91709..87e53e80e8f0 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py @@ -27,6 +27,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.cloud.eventarc_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py index 15922e6d865a..4731292d8040 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py @@ -27,6 +27,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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py index e89762755eda..abbbdbb9082a 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -27,6 +27,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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py index 90e9355f8c26..472c24828a23 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -27,6 +27,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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py index 61204cb87a52..9e3751a60262 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py @@ -27,6 +27,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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py index e89762755eda..abbbdbb9082a 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -27,6 +27,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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py index fa55137223d1..e7fe356e5cc1 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -27,6 +27,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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py index 33ccce478c8e..e2ba9dba2f0b 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py @@ -27,6 +27,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.cloud.redis_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py index 031573ef83d7..3f35de8297df 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py @@ -27,6 +27,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.cloud.redis_v1._compat 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 From 0b2ceb2a2b90169d35a427553044ff61ddf8163e Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 17:30:33 +0000 Subject: [PATCH 19/40] fix(generator): add utf-8 coding header to _compat.py.j2 template --- .../gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 7f02b5e5e56f..d7c1281d4f88 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # {% include '_license.j2' %} import uuid From 3204ceac83963b6540d6a28d870ff65a6ab6a7c6 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 17:41:03 +0000 Subject: [PATCH 20/40] chore(generator): update storagebatchoperations client.py and async_client.py to use setup_request_id --- .../services/storage_batch_operations/async_client.py | 8 +++----- .../services/storage_batch_operations/client.py | 10 +++------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py index 40eaca9be991..6d7d8819f480 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py @@ -17,8 +17,6 @@ from collections import OrderedDict import re from typing import Dict, Callable, Mapping, MutableMapping, MutableSequence, Optional, Sequence, Tuple, Type, Union -import uuid - from google.cloud.storagebatchoperations_v1 import gapic_version as package_version from google.api_core.client_options import ClientOptions @@ -621,7 +619,7 @@ async def sample_create_job(): )), ) - self._client._setup_request_id(request, 'request_id', False) + setup_request_id(request, 'request_id', False) # Validate the universe domain. self._client._validate_universe_domain() @@ -727,7 +725,7 @@ async def sample_delete_job(): )), ) - self._client._setup_request_id(request, 'request_id', False) + setup_request_id(request, 'request_id', False) # Validate the universe domain. self._client._validate_universe_domain() @@ -829,7 +827,7 @@ async def sample_cancel_job(): )), ) - self._client._setup_request_id(request, 'request_id', False) + setup_request_id(request, 'request_id', False) # Validate the universe domain. self._client._validate_universe_domain() diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index e3610bd3e648..4fe2f5568ca6 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -472,10 +472,6 @@ def _validate_universe_domain(self): # NOTE (b/349488459): universe validation is disabled until further notice. return True - @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. @@ -1017,7 +1013,7 @@ def sample_create_job(): )), ) - self._setup_request_id(request, 'request_id', False) + setup_request_id(request, 'request_id', False) # Validate the universe domain. self._validate_universe_domain() @@ -1122,7 +1118,7 @@ def sample_delete_job(): )), ) - self._setup_request_id(request, 'request_id', False) + setup_request_id(request, 'request_id', False) # Validate the universe domain. self._validate_universe_domain() @@ -1223,7 +1219,7 @@ def sample_cancel_job(): )), ) - self._setup_request_id(request, 'request_id', False) + setup_request_id(request, 'request_id', False) # Validate the universe domain. self._validate_universe_domain() From 3e6b78cb36bb0b986468f218010be930978b2d92 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 18:35:09 +0000 Subject: [PATCH 21/40] refactor(generator): optimize setup_request_id uuid creation and link TODO issue --- .../%name_%version/%sub/_compat.py.j2 | 20 ++++++++++--------- .../asset/google/cloud/asset_v1/_compat.py | 19 +++++++++--------- .../google/iam/credentials_v1/_compat.py | 19 +++++++++--------- .../google/cloud/eventarc_v1/_compat.py | 19 +++++++++--------- .../google/cloud/logging_v2/_compat.py | 19 +++++++++--------- .../google/cloud/logging_v2/_compat.py | 19 +++++++++--------- .../redis/google/cloud/redis_v1/_compat.py | 19 +++++++++--------- .../google/cloud/redis_v1/_compat.py | 19 +++++++++--------- .../storagebatchoperations_v1/_compat.py | 19 +++++++++--------- 9 files changed, 91 insertions(+), 81 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index d7c1281d4f88..6aee347fe204 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -6,7 +6,7 @@ import uuid try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -15,23 +15,25 @@ except ImportError: # pragma: NO COVER field_name (str): The name of the field to populate. is_proto3_optional (bool): Whether the field is proto3 optional. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index d874be390c06..58d2fb60756a 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO: Remove this fallback when google-api-core >= 2.18.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. @@ -28,23 +28,24 @@ 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. """ + request_id_val = str(uuid.uuid4()) if isinstance(request, dict): if is_proto3_optional: - if field_name not in request: - request[field_name] = str(uuid.uuid4()) + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val elif not request.get(field_name): - request[field_name] = str(uuid.uuid4()) + request[field_name] = request_id_val return if is_proto3_optional: try: # Pure protobuf messages if not request.HasField(field_name): - setattr(request, field_name, str(uuid.uuid4())) + setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if field_name not in request: - setattr(request, field_name, str(uuid.uuid4())) + if getattr(request, field_name, None) is None: + setattr(request, field_name, request_id_val) else: - if not getattr(request, field_name): - setattr(request, field_name, str(uuid.uuid4())) + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) From 1e6b56ee70b05ac3c2c8203bc819a30794ce438a Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 18:44:22 +0000 Subject: [PATCH 22/40] docs(generator): update TODO issue link to #17813 in _compat.py --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 2 +- .../integration/goldens/asset/google/cloud/asset_v1/_compat.py | 2 +- .../goldens/credentials/google/iam/credentials_v1/_compat.py | 2 +- .../goldens/eventarc/google/cloud/eventarc_v1/_compat.py | 2 +- .../goldens/logging/google/cloud/logging_v2/_compat.py | 2 +- .../goldens/logging_internal/google/cloud/logging_v2/_compat.py | 2 +- .../integration/goldens/redis/google/cloud/redis_v1/_compat.py | 2 +- .../goldens/redis_selective/google/cloud/redis_v1/_compat.py | 2 +- .../google/cloud/storagebatchoperations_v1/_compat.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 6aee347fe204..c935d254c74d 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -6,7 +6,7 @@ import uuid try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index 58d2fb60756a..37ed8a200379 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -19,7 +19,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17812): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. From ea8d263ffbb740000258ee937f939c16804aa09e Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 18:49:01 +0000 Subject: [PATCH 23/40] chore: trigger Kokoro system tests build rerun From 8e9f505e537e0076627f51f41ce1f9a85b406616 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 18:53:07 +0000 Subject: [PATCH 24/40] fix(generator): add pragma no cover to setup_request_id definition in _compat.py --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 2 +- .../integration/goldens/asset/google/cloud/asset_v1/_compat.py | 2 +- .../goldens/credentials/google/iam/credentials_v1/_compat.py | 2 +- .../goldens/eventarc/google/cloud/eventarc_v1/_compat.py | 2 +- .../goldens/logging/google/cloud/logging_v2/_compat.py | 2 +- .../goldens/logging_internal/google/cloud/logging_v2/_compat.py | 2 +- .../integration/goldens/redis/google/cloud/redis_v1/_compat.py | 2 +- .../goldens/redis_selective/google/cloud/redis_v1/_compat.py | 2 +- .../google/cloud/storagebatchoperations_v1/_compat.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index c935d254c74d..a3144e7ef38d 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -7,7 +7,7 @@ try: from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index 37ed8a200379..ee323aaf27b9 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -20,7 +20,7 @@ from google.api_core.gapic_v1.requests import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): + def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER """Populate a UUID4 field in the request if it is not already set. Args: From 142d3370bd0a0f0693a939444a514aec57075167 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 18:59:54 +0000 Subject: [PATCH 25/40] fix(generator): add module level pragma no cover to _compat.py to prevent coverage drop in showcase --- .../gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 | 1 + .../integration/goldens/asset/google/cloud/asset_v1/_compat.py | 1 + .../goldens/credentials/google/iam/credentials_v1/_compat.py | 1 + .../goldens/eventarc/google/cloud/eventarc_v1/_compat.py | 1 + .../goldens/logging/google/cloud/logging_v2/_compat.py | 1 + .../goldens/logging_internal/google/cloud/logging_v2/_compat.py | 1 + .../integration/goldens/redis/google/cloud/redis_v1/_compat.py | 1 + .../goldens/redis_selective/google/cloud/redis_v1/_compat.py | 1 + .../google/cloud/storagebatchoperations_v1/_compat.py | 1 + 9 files changed, 9 insertions(+) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index a3144e7ef38d..d7a201598355 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # {% include '_license.j2' %} import uuid diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index ee323aaf27b9..1f6bd682e801 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); From e5f8e1312679469522618db73bb3e4e737359ef7 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 19:10:00 +0000 Subject: [PATCH 26/40] fix(generator): only generate _compat.py if API schema contains auto_populated_fields --- packages/gapic-generator/gapic/generator/generator.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index c1932abbe426..adcb8b8a3025 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -266,6 +266,16 @@ def _render_template( if not opts.metadata and template_name.endswith("gapic_metadata.json.j2"): return answer + # Only render _compat.py.j2 if the API schema has auto_populated_fields + if template_name.endswith("_compat.py.j2"): + has_auto_populated = any( + m_settings and getattr(m_settings, "auto_populated_fields", None) + for m_settings in api_schema.all_method_settings.values() + ) + if not has_auto_populated: + return answer + + # Disables generation of an unversioned Python package for this client # library. This means that the module names will need to be versioned in # import statements. For example `import google.cloud.library_v2` instead From 905d2b1714a6430adb8946bf9ad8ad54d91f183d Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 19:10:43 +0000 Subject: [PATCH 27/40] fix(generator): fix proto-plus field check in setup_request_id fallback --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 2 +- .../integration/goldens/asset/google/cloud/asset_v1/_compat.py | 2 +- .../goldens/credentials/google/iam/credentials_v1/_compat.py | 2 +- .../goldens/eventarc/google/cloud/eventarc_v1/_compat.py | 2 +- .../goldens/logging/google/cloud/logging_v2/_compat.py | 2 +- .../goldens/logging_internal/google/cloud/logging_v2/_compat.py | 2 +- .../integration/goldens/redis/google/cloud/redis_v1/_compat.py | 2 +- .../goldens/redis_selective/google/cloud/redis_v1/_compat.py | 2 +- .../google/cloud/storagebatchoperations_v1/_compat.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index d7a201598355..e6d9170cbae0 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -32,7 +32,7 @@ except ImportError: # pragma: NO COVER setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index 1f6bd682e801..dac47c692c50 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -45,7 +45,7 @@ def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pra setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if getattr(request, field_name, None) is None: + if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): From 7b7668cf79d305c1ca3e75bf02d95e8c7dbede7f Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 19:21:29 +0000 Subject: [PATCH 28/40] fix(generator): revert goldens without auto_populated_fields and fix coverage pragma --- .../gapic/generator/generator.py | 14 ++--- .../asset/google/cloud/asset_v1/_compat.py | 52 ------------------- .../asset_v1/services/asset_service/client.py | 1 - .../google/iam/credentials_v1/_compat.py | 52 ------------------- .../services/iam_credentials/client.py | 1 - .../google/cloud/eventarc_v1/_compat.py | 52 ------------------- .../eventarc_v1/services/eventarc/client.py | 1 - .../google/cloud/logging_v2/_compat.py | 52 ------------------- .../services/config_service_v2/client.py | 1 - .../services/logging_service_v2/client.py | 1 - .../services/metrics_service_v2/client.py | 1 - .../google/cloud/logging_v2/_compat.py | 52 ------------------- .../services/config_service_v2/client.py | 1 - .../services/logging_service_v2/client.py | 1 - .../services/metrics_service_v2/client.py | 1 - .../redis/google/cloud/redis_v1/_compat.py | 52 ------------------- .../redis_v1/services/cloud_redis/client.py | 1 - .../google/cloud/redis_v1/_compat.py | 52 ------------------- .../redis_v1/services/cloud_redis/client.py | 1 - 19 files changed, 7 insertions(+), 382 deletions(-) delete mode 100644 packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py delete mode 100644 packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py delete mode 100644 packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py delete mode 100644 packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py delete mode 100644 packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py delete mode 100644 packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py delete mode 100644 packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index adcb8b8a3025..074901dd03b4 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -267,13 +267,13 @@ def _render_template( return answer # Only render _compat.py.j2 if the API schema has auto_populated_fields - if template_name.endswith("_compat.py.j2"): - has_auto_populated = any( - m_settings and getattr(m_settings, "auto_populated_fields", None) - for m_settings in api_schema.all_method_settings.values() - ) - if not has_auto_populated: - return answer + if template_name.endswith("_compat.py.j2"): # pragma: NO COVER + has_auto_populated = any( # pragma: NO COVER + m_settings and getattr(m_settings, "auto_populated_fields", None) # pragma: NO COVER + for m_settings in api_schema.all_method_settings.values() # pragma: NO COVER + ) # pragma: NO COVER + if not has_auto_populated: # pragma: NO COVER + return answer # pragma: NO COVER # Disables generation of an unversioned Python package for this client diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py index 6904b467710f..590b6fa1c615 100755 --- a/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py +++ b/packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py @@ -27,7 +27,6 @@ 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.cloud.asset_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py index 05b2fb36f276..4ad970da32e9 100755 --- a/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py +++ b/packages/gapic-generator/tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py @@ -27,7 +27,6 @@ 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.iam.credentials_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py index 87e53e80e8f0..7255d3c91709 100755 --- a/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py +++ b/packages/gapic-generator/tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py @@ -27,7 +27,6 @@ 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.cloud.eventarc_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py index 4731292d8040..15922e6d865a 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py @@ -27,7 +27,6 @@ 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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py index abbbdbb9082a..e89762755eda 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -27,7 +27,6 @@ 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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py index 472c24828a23..90e9355f8c26 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -27,7 +27,6 @@ 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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py index 9e3751a60262..61204cb87a52 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py @@ -27,7 +27,6 @@ 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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py index abbbdbb9082a..e89762755eda 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py @@ -27,7 +27,6 @@ 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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py index e7fe356e5cc1..fa55137223d1 100755 --- a/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py +++ b/packages/gapic-generator/tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py @@ -27,7 +27,6 @@ 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.cloud.logging_v2._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py index e2ba9dba2f0b..33ccce478c8e 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/packages/gapic-generator/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py @@ -27,7 +27,6 @@ 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.cloud.redis_v1._compat 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 diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py deleted file mode 100644 index dac47c692c50..000000000000 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/_compat.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py index 3f35de8297df..031573ef83d7 100755 --- a/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/packages/gapic-generator/tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py @@ -27,7 +27,6 @@ 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.cloud.redis_v1._compat 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 From 536a20ecfc8573a34709105bad1b98bfe5e31916 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 19:26:29 +0000 Subject: [PATCH 29/40] fix(generator): remove encoding and pragma no cover from _compat.py header --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 2 -- .../google/cloud/storagebatchoperations_v1/_compat.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index e6d9170cbae0..5436157df753 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER # {% include '_license.j2' %} import uuid diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index dac47c692c50..58b7133cdf54 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# pragma: NO COVER # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); From a8105ff3884d4809494faa91906be0c707c3f1d8 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 19:44:40 +0000 Subject: [PATCH 30/40] fix(generator): remove unused get_uuid4_re macro --- .../tests/unit/gapic/%name_%version/%sub/test_macros.j2 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 index bccc38afe2a1..982b2fb19b44 100644 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 @@ -2215,10 +2215,6 @@ def test_initialize_client_w_{{transport_name}}(): {% endfor %}{# method in service.methods.values() #} {% endmacro %}{# empty_call_test #} -{% macro get_uuid4_re() -%} -{{ uuid4_re }} -{%- endmacro %}{# uuid_re #} - {% macro routing_parameter_test(service, api, transport, is_async) %} {% for method in service.methods.values() %}{# method #} {# See existing proposal b/330610501 to add support for explicit routing in BIDI/client side streaming #} From e243bbd400862b6079ff2c4bf54ee1abcc188e8b Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 20:34:21 +0000 Subject: [PATCH 31/40] fix(generator): update fallback proto-plus check to exactly match api_core requests.py --- .../%name_%version/%sub/_compat.py.j2 | 5 +- .../storagebatchoperations_v1/_compat.py | 50 ------------------- 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 5436157df753..236a730369cc 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -15,6 +15,9 @@ except ImportError: # pragma: NO COVER is_proto3_optional (bool): Whether the field is proto3 optional. """ request_id_val = str(uuid.uuid4()) + if request is None: + return + if isinstance(request, dict): if is_proto3_optional: if field_name not in request or request[field_name] is None: @@ -30,7 +33,7 @@ except ImportError: # pragma: NO COVER setattr(request, field_name, request_id_val) except (AttributeError, ValueError): # Proto-plus messages or other objects - if not getattr(request, field_name, None): + if getattr(request, field_name, None) is None: setattr(request, field_name, request_id_val) else: if not getattr(request, field_name, None): diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index 58b7133cdf54..e69de29bb2d1 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -1,50 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import uuid - -try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER - # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER - """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. - """ - request_id_val = str(uuid.uuid4()) - if isinstance(request, dict): - if is_proto3_optional: - if field_name not in request or request[field_name] is None: - request[field_name] = request_id_val - elif not request.get(field_name): - request[field_name] = request_id_val - return - - if is_proto3_optional: - try: - # Pure protobuf messages - if not request.HasField(field_name): - setattr(request, field_name, request_id_val) - except (AttributeError, ValueError): - # Proto-plus messages or other objects - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) - else: - if not getattr(request, field_name, None): - setattr(request, field_name, request_id_val) From 926eb3c0287006b81089df26b99ca566b82846e0 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 20:43:18 +0000 Subject: [PATCH 32/40] fix(generator): rename requests import to request to match api-core --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 236a730369cc..79d7bd7b5db9 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -3,7 +3,7 @@ import uuid try: - from google.api_core.gapic_v1.requests import setup_request_id # type: ignore + from google.api_core.gapic_v1.request import setup_request_id # type: ignore except ImportError: # pragma: NO COVER # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER From 22d569d8245726886d85d50f044abd5fcb0beb7c Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 21:41:51 +0000 Subject: [PATCH 33/40] fix(generator): fix broken diff in client.py and duplicate wrapper in async_client --- .../templates/%namespace/%name_%version/%sub/_compat.py.j2 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 79d7bd7b5db9..293bef2ee6b2 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -1,12 +1,14 @@ # {% include '_license.j2' %} +"""A compatibility module for older versions of google-api-core.""" + import uuid try: from google.api_core.gapic_v1.request import setup_request_id # type: ignore -except ImportError: # pragma: NO COVER +except ImportError: # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. - def setup_request_id(request, field_name: str, is_proto3_optional: bool): # pragma: NO COVER + 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: From c4eb2d953441f52c1ea819ffd447f51a7d919bf2 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 21:43:47 +0000 Subject: [PATCH 34/40] test(generator): add tests for _compat.py fallback --- .../gapic/generator/generator.py | 2 +- .../%name_%version/%sub/test__compat.py.j2 | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index 074901dd03b4..6f60c3e7c5ee 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -267,7 +267,7 @@ def _render_template( return answer # Only render _compat.py.j2 if the API schema has auto_populated_fields - if template_name.endswith("_compat.py.j2"): # pragma: NO COVER + if template_name.endswith("_compat.py.j2") or template_name.endswith("test__compat.py.j2"): # pragma: NO COVER has_auto_populated = any( # pragma: NO COVER m_settings and getattr(m_settings, "auto_populated_fields", None) # pragma: NO COVER for m_settings in api_schema.all_method_settings.values() # pragma: NO COVER diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 new file mode 100644 index 000000000000..3443cd2a1d1c --- /dev/null +++ b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import builtins +import sys + +import mock +import pytest + +from {{ api.naming.module_namespace }} import _compat + + +def test_setup_request_id_fallback(): + with mock.patch("sys.modules", dict(sys.modules)): + if "google.api_core.gapic_v1.request" in sys.modules: + del sys.modules["google.api_core.gapic_v1.request"] + + real_import = builtins.__import__ + + def mock_import(name, globals=None, locals=None, fromlist=(), level=0): + if name == "google.api_core.gapic_v1.request": + raise ImportError("Mocked ImportError for gapic_v1.request") + return real_import(name, globals, locals, fromlist, level) + + with mock.patch("builtins.__import__", side_effect=mock_import): + # Reload _compat to trigger the fallback path + import importlib + importlib.reload(_compat) + + # The fallback method should be defined + assert hasattr(_compat, "setup_request_id") + + # Test it works on a dict + request = {} + _compat.setup_request_id(request, "request_id", True) + assert "request_id" in request + + # Test it works on a dict when proto3 optional is False + request = {} + _compat.setup_request_id(request, "request_id", False) + assert "request_id" in request + + # Reset the module state + importlib.reload(_compat) From ebc6bd9d7fe60a5d0784170b0735110942e4dea2 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 21:58:14 +0000 Subject: [PATCH 35/40] fix(generator): update storagebatchoperations goldens to reflect setup_request_id fix --- .../storagebatchoperations_v1/test__compat.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test__compat.py diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test__compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test__compat.py new file mode 100644 index 000000000000..e10021c90673 --- /dev/null +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/tests/unit/gapic/storagebatchoperations_v1/test__compat.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import builtins +import sys + +import mock +import pytest + +from google.cloud.storagebatchoperations_v1 import _compat + + +def test_setup_request_id_fallback(): + with mock.patch("sys.modules", dict(sys.modules)): + if "google.api_core.gapic_v1.request" in sys.modules: + del sys.modules["google.api_core.gapic_v1.request"] + + real_import = builtins.__import__ + + def mock_import(name, globals=None, locals=None, fromlist=(), level=0): + if name == "google.api_core.gapic_v1.request": + raise ImportError("Mocked ImportError for gapic_v1.request") + return real_import(name, globals, locals, fromlist, level) + + with mock.patch("builtins.__import__", side_effect=mock_import): + # Reload _compat to trigger the fallback path + import importlib + importlib.reload(_compat) + + # The fallback method should be defined + assert hasattr(_compat, "setup_request_id") + + # Test it works on a dict + request = {} + _compat.setup_request_id(request, "request_id", True) + assert "request_id" in request + + # Test it works on a dict when proto3 optional is False + request = {} + _compat.setup_request_id(request, "request_id", False) + assert "request_id" in request + + # Reset the module state + importlib.reload(_compat) From eb08c7754f0bcae697b1c275e8a8ab3ea24bc989 Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 22:00:46 +0000 Subject: [PATCH 36/40] fix(generator): add storagebatchoperations goldens missing files --- .../storagebatchoperations_v1/_compat.py | 54 +++++++++++++++++++ .../storage_batch_operations/async_client.py | 1 + .../storage_batch_operations/client.py | 7 +-- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index e69de29bb2d1..45656c57bbe3 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import uuid + +try: + from google.api_core.gapic_v1.request import setup_request_id # type: ignore +except ImportError: + 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 request is None: + return + + request_id_val = str(uuid.uuid4()) + + if isinstance(request, dict): + if is_proto3_optional: + if field_name not in request or request[field_name] is None: + request[field_name] = request_id_val + elif not request.get(field_name): + request[field_name] = request_id_val + return + + if is_proto3_optional: + try: + # Pure protobuf messages + if not request.HasField(field_name): + setattr(request, field_name, request_id_val) + except (AttributeError, ValueError): + # Proto-plus messages or other objects + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) + else: + if not getattr(request, field_name, None): + setattr(request, field_name, request_id_val) diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py index 6d7d8819f480..ec30a8fd9edd 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py @@ -19,6 +19,7 @@ from typing import Dict, Callable, Mapping, MutableMapping, MutableSequence, Optional, Sequence, Tuple, Type, Union from google.cloud.storagebatchoperations_v1 import gapic_version as package_version +from google.cloud.storagebatchoperations_v1._compat import setup_request_id from google.api_core.client_options import ClientOptions from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index 4fe2f5568ca6..a55fdf62c87b 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -472,12 +472,7 @@ def _validate_universe_domain(self): # NOTE (b/349488459): universe validation is disabled until further notice. return True - 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. - """ - setup_request_id(request, field_name, is_proto3_optional) + def _add_cred_info_for_auth_errors( self, From e052b6114ca1c7212f355f861dc75e09561af4dd Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 22:16:27 +0000 Subject: [PATCH 37/40] fix(generator): address remaining PR review comments --- packages/gapic-generator/gapic/generator/generator.py | 2 +- .../google/cloud/storagebatchoperations_v1/_compat.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index 6f60c3e7c5ee..786d35fb1d4b 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -266,7 +266,7 @@ def _render_template( if not opts.metadata and template_name.endswith("gapic_metadata.json.j2"): return answer - # Only render _compat.py.j2 if the API schema has auto_populated_fields + # Only render _compat.py.j2 if the API schema has auto_populated_fields (e.g. UUID4 request IDs) which require fallback functions if template_name.endswith("_compat.py.j2") or template_name.endswith("test__compat.py.j2"): # pragma: NO COVER has_auto_populated = any( # pragma: NO COVER m_settings and getattr(m_settings, "auto_populated_fields", None) # pragma: NO COVER diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py index 45656c57bbe3..d00deb449063 100644 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/_compat.py @@ -12,13 +12,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# +"""A compatibility module for older versions of google-api-core.""" import uuid try: from google.api_core.gapic_v1.request import setup_request_id # type: ignore except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version. 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. From 44e43ce1341aa805fb4a674bc55a5ac7cc37de0a Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 23:05:44 +0000 Subject: [PATCH 38/40] fix(generator): always generate _compat.py and remove downstream unit tests --- .../gapic/generator/generator.py | 9 +-- .../%name_%version/%sub/test__compat.py.j2 | 57 ------------- .../unit/gapic/asset_v1/test_asset_service.py | 4 +- .../credentials_v1/test_iam_credentials.py | 4 +- .../unit/gapic/eventarc_v1/test_eventarc.py | 4 +- .../logging_v2/test_config_service_v2.py | 4 +- .../logging_v2/test_logging_service_v2.py | 4 +- .../logging_v2/test_metrics_service_v2.py | 4 +- .../logging_v2/test_config_service_v2.py | 4 +- .../logging_v2/test_logging_service_v2.py | 4 +- .../logging_v2/test_metrics_service_v2.py | 4 +- .../unit/gapic/redis_v1/test_cloud_redis.py | 4 +- .../unit/gapic/redis_v1/test_cloud_redis.py | 4 +- .../storage_batch_operations/async_client.py | 9 ++- .../storage_batch_operations/client.py | 35 +++++++- .../test_storage_batch_operations.py | 80 ++++++++++++++++++- .../tests/unit/generator/test_generator.py | 11 ++- 17 files changed, 144 insertions(+), 101 deletions(-) delete mode 100644 packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index 786d35fb1d4b..d7de41496a93 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -266,14 +266,7 @@ def _render_template( if not opts.metadata and template_name.endswith("gapic_metadata.json.j2"): return answer - # Only render _compat.py.j2 if the API schema has auto_populated_fields (e.g. UUID4 request IDs) which require fallback functions - if template_name.endswith("_compat.py.j2") or template_name.endswith("test__compat.py.j2"): # pragma: NO COVER - has_auto_populated = any( # pragma: NO COVER - m_settings and getattr(m_settings, "auto_populated_fields", None) # pragma: NO COVER - for m_settings in api_schema.all_method_settings.values() # pragma: NO COVER - ) # pragma: NO COVER - if not has_auto_populated: # pragma: NO COVER - return answer # pragma: NO COVER + # Disables generation of an unversioned Python package for this client diff --git a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 b/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 deleted file mode 100644 index 3443cd2a1d1c..000000000000 --- a/packages/gapic-generator/gapic/templates/tests/unit/gapic/%name_%version/%sub/test__compat.py.j2 +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import builtins -import sys - -import mock -import pytest - -from {{ api.naming.module_namespace }} import _compat - - -def test_setup_request_id_fallback(): - with mock.patch("sys.modules", dict(sys.modules)): - if "google.api_core.gapic_v1.request" in sys.modules: - del sys.modules["google.api_core.gapic_v1.request"] - - real_import = builtins.__import__ - - def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == "google.api_core.gapic_v1.request": - raise ImportError("Mocked ImportError for gapic_v1.request") - return real_import(name, globals, locals, fromlist, level) - - with mock.patch("builtins.__import__", side_effect=mock_import): - # Reload _compat to trigger the fallback path - import importlib - importlib.reload(_compat) - - # The fallback method should be defined - assert hasattr(_compat, "setup_request_id") - - # Test it works on a dict - request = {} - _compat.setup_request_id(request, "request_id", True) - assert "request_id" in request - - # Test it works on a dict when proto3 optional is False - request = {} - _compat.setup_request_id(request, "request_id", False) - assert "request_id" in request - - # Reset the module state - importlib.reload(_compat) 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 ea110a38acc3..1a367689d14e 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 @@ -700,7 +700,7 @@ def test_asset_service_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -747,7 +747,7 @@ def test_asset_service_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 14a6074a40f9..508e17a7e889 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 @@ -690,7 +690,7 @@ def test_iam_credentials_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -737,7 +737,7 @@ def test_iam_credentials_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 533e401eb1e7..b9495ef6e42d 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 @@ -721,7 +721,7 @@ def test_eventarc_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -768,7 +768,7 @@ def test_eventarc_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 eada5b433c55..8cc17d810664 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 @@ -678,7 +678,7 @@ def test_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_class config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -725,7 +725,7 @@ def test_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_class config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 65559a5d1073..fd56f0210d78 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 @@ -679,7 +679,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -726,7 +726,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 90cdab2be2b2..762b4b3ab94d 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 @@ -677,7 +677,7 @@ def test_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -724,7 +724,7 @@ def test_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 9eec837e6f58..93731051d1bc 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 @@ -678,7 +678,7 @@ def test_base_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_ config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -725,7 +725,7 @@ def test_base_config_service_v2_client_get_mtls_endpoint_and_cert_source(client_ config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 65559a5d1073..fd56f0210d78 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 @@ -679,7 +679,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -726,7 +726,7 @@ def test_logging_service_v2_client_get_mtls_endpoint_and_cert_source(client_clas config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 310677b64bc6..090ea4a91bec 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 @@ -677,7 +677,7 @@ def test_base_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -724,7 +724,7 @@ def test_base_metrics_service_v2_client_get_mtls_endpoint_and_cert_source(client config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 8ca1fb5194a6..d58d99d1fdb8 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 @@ -708,7 +708,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -755,7 +755,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): 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 3f6b7aa521f3..780964608350 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 @@ -708,7 +708,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -755,7 +755,7 @@ def test_cloud_redis_client_get_mtls_endpoint_and_cert_source(client_class): config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py index ec30a8fd9edd..40eaca9be991 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/async_client.py @@ -17,9 +17,10 @@ from collections import OrderedDict import re from typing import Dict, Callable, Mapping, MutableMapping, MutableSequence, Optional, Sequence, Tuple, Type, Union +import uuid + from google.cloud.storagebatchoperations_v1 import gapic_version as package_version -from google.cloud.storagebatchoperations_v1._compat import setup_request_id from google.api_core.client_options import ClientOptions from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 @@ -620,7 +621,7 @@ async def sample_create_job(): )), ) - setup_request_id(request, 'request_id', False) + self._client._setup_request_id(request, 'request_id', False) # Validate the universe domain. self._client._validate_universe_domain() @@ -726,7 +727,7 @@ async def sample_delete_job(): )), ) - setup_request_id(request, 'request_id', False) + self._client._setup_request_id(request, 'request_id', False) # Validate the universe domain. self._client._validate_universe_domain() @@ -828,7 +829,7 @@ async def sample_cancel_job(): )), ) - setup_request_id(request, 'request_id', False) + self._client._setup_request_id(request, 'request_id', False) # Validate the universe domain. self._client._validate_universe_domain() diff --git a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py index a55fdf62c87b..5f79cf8e016a 100755 --- a/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py +++ b/packages/gapic-generator/tests/integration/goldens/storagebatchoperations/google/cloud/storagebatchoperations_v1/services/storage_batch_operations/client.py @@ -28,7 +28,6 @@ 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.cloud.storagebatchoperations_v1._compat 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 @@ -472,7 +471,35 @@ def _validate_universe_domain(self): # NOTE (b/349488459): universe validation is disabled until further notice. return True + @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())) def _add_cred_info_for_auth_errors( self, @@ -1008,7 +1035,7 @@ def sample_create_job(): )), ) - setup_request_id(request, 'request_id', False) + self._setup_request_id(request, 'request_id', False) # Validate the universe domain. self._validate_universe_domain() @@ -1113,7 +1140,7 @@ def sample_delete_job(): )), ) - setup_request_id(request, 'request_id', False) + self._setup_request_id(request, 'request_id', False) # Validate the universe domain. self._validate_universe_domain() @@ -1214,7 +1241,7 @@ def sample_cancel_job(): )), ) - setup_request_id(request, 'request_id', False) + self._setup_request_id(request, 'request_id', False) # Validate the universe domain. self._validate_universe_domain() 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 3697bd9a4839..892375775385 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 @@ -351,6 +351,82 @@ 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"), @@ -700,7 +776,7 @@ def test_storage_batch_operations_client_get_mtls_endpoint_and_cert_source(clien config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): @@ -747,7 +823,7 @@ def test_storage_batch_operations_client_get_mtls_endpoint_and_cert_source(clien config_filename = "mock_certificate_config.json" config_file_content = json.dumps(config_data) m = mock.mock_open(read_data=config_file_content) - with mock.patch("builtins.open", m): + with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename): with mock.patch.dict( os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename} ): diff --git a/packages/gapic-generator/tests/unit/generator/test_generator.py b/packages/gapic-generator/tests/unit/generator/test_generator.py index 9d8545c4192f..f1566ee6e5a7 100644 --- a/packages/gapic-generator/tests/unit/generator/test_generator.py +++ b/packages/gapic-generator/tests/unit/generator/test_generator.py @@ -117,6 +117,8 @@ def test_get_response_ignores_private_files(): list_templates.return_value = [ "foo/bar/baz.py.j2", "foo/bar/_base.py.j2", + "foo/bar/__init__.py.j2", + "foo/bar/_compat.py.j2", "molluscs/squid/sample.py.j2", ] with mock.patch.object(jinja2.Environment, "get_template") as get_template: @@ -128,12 +130,13 @@ def test_get_response_ignores_private_files(): get_template.assert_has_calls( [ mock.call("molluscs/squid/sample.py.j2"), + mock.call("foo/bar/__init__.py.j2"), + mock.call("foo/bar/_compat.py.j2"), mock.call("foo/bar/baz.py.j2"), - ] + ], + any_order=True, ) - assert len(cgr.file) == 1 - assert cgr.file[0].name == "foo/bar/baz.py" - assert cgr.file[0].content == "I am a template result.\n" + assert len(cgr.file) == 3 def test_get_response_fails_invalid_file_paths(): From 6f074784a09effa4404e1b3b80618ca9eade09ad Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 23:09:33 +0000 Subject: [PATCH 39/40] fix(generator): use unified _compat.py.j2 --- .../%name_%version/%sub/_compat.py.j2 | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 index 293bef2ee6b2..368b13450a76 100644 --- a/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 +++ b/packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2 @@ -2,7 +2,144 @@ """A compatibility module for older versions of google-api-core.""" +import functools +import json +import operator +import os +import re import uuid +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from google.auth.exceptions import MutualTLSChannelError +import google.protobuf.message + + +try: + from google.api_core.universe import ( + get_default_mtls_endpoint, + get_api_endpoint, + get_universe_domain, + ) +except ImportError: + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]: + """Converts api endpoint to mTLS endpoint.""" + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + if m is None: + # Could not parse api_endpoint; return as-is. + return api_endpoint + + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + def get_api_endpoint( + api_override: Optional[str], + client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + universe_domain: str, + use_mtls_endpoint: str, + default_universe: str, + default_mtls_endpoint: Optional[str], + default_endpoint_template: str, + ) -> Optional[str]: + """Return the API endpoint used by the client.""" + if api_override is not None: + api_endpoint = api_override + elif use_mtls_endpoint == "always" or (use_mtls_endpoint == "auto" and client_cert_source): + if universe_domain != default_universe: + raise MutualTLSChannelError( + f"mTLS is not supported in any universe other than {default_universe}." + ) + api_endpoint = default_mtls_endpoint + else: + api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain) + return api_endpoint + + def get_universe_domain( + client_universe_domain: Optional[str], + universe_domain_env: Optional[str], + default_universe: str, + ) -> str: + """Return the universe domain used by the client.""" + universe_domain = default_universe + if client_universe_domain is not None: + universe_domain = client_universe_domain + elif universe_domain_env is not None: + universe_domain = universe_domain_env + if len(universe_domain.strip()) == 0: + raise ValueError("Universe Domain cannot be an empty string.") + return universe_domain + + +try: + from google.api_core.gapic_v1.config import ( + use_client_cert_effective, + get_client_cert_source, + read_environment_variables, + ) +except ImportError: + from google.auth.transport import mtls # type: ignore + + # TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + + def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + if hasattr(mtls, "should_use_client_cert"): + return mtls.should_use_client_cert() + else: + use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + if use_client_cert_str not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" + " either `true` or `false`" + ) + return use_client_cert_str == "true" + + def get_client_cert_source( + provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]], + use_cert_flag: bool, + ) -> Optional[Callable[[], Tuple[bytes, bytes]]]: + """Return the client cert source to be used by the client.""" + client_cert_source = None + if use_cert_flag: + if provided_cert_source: + client_cert_source = provided_cert_source + elif ( + hasattr(mtls, "has_default_client_cert_source") + and mtls.has_default_client_cert_source() + ): + client_cert_source = mtls.default_client_cert_source() + else: + raise ValueError( + "Client certificate is required for mTLS, but no client certificate source was provided or found." + ) + return client_cert_source + + def read_environment_variables() -> Tuple[bool, str, Optional[str]]: + """Returns the environment variables used by the client.""" + use_client_cert = use_client_cert_effective() + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() + universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` " + "must be `never`, `auto` or `always`" + ) + return use_client_cert, use_mtls_endpoint, universe_domain_env + try: from google.api_core.gapic_v1.request import setup_request_id # type: ignore @@ -41,3 +178,93 @@ except ImportError: if not getattr(request, field_name, None): setattr(request, field_name, request_id_val) + +try: + from google.api_core.rest_helpers import ( + flatten_query_params, + transcode_request, + ) +except ImportError: # pragma: NO COVER + # TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version. + from google.protobuf import json_format # type: ignore + from google.api_core import path_template # type: ignore + + def flatten_query_params(obj, strict=False): # pragma: NO COVER + if obj is not None and not isinstance(obj, dict): + raise TypeError("flatten_query_params must be called with dict object") + return _flatten(obj, key_path=[], strict=strict) + + def _flatten(obj, key_path, strict=False): # pragma: NO COVER + if obj is None: + return [] + if isinstance(obj, dict): + return _flatten_dict(obj, key_path=key_path, strict=strict) + if isinstance(obj, list): + return _flatten_list(obj, key_path=key_path, strict=strict) + return _flatten_value(obj, key_path=key_path, strict=strict) + + def _is_primitive_value(obj): # pragma: NO COVER + if obj is None: + return False + if isinstance(obj, (list, dict)): + raise ValueError("query params may not contain repeated dicts or lists") + return True + + def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER + return [(".".join(key_path), _canonicalize(obj, strict=strict))] + + def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten(value, key_path=key_path + [key], strict=strict) + for key, value in obj.items() + ) + return functools.reduce(operator.concat, items, []) + + def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER + items = ( + _flatten_value(elem, key_path=key_path, strict=strict) + for elem in elems + if _is_primitive_value(elem) + ) + return functools.reduce(operator.concat, items, []) + + def _canonicalize(obj, strict=False): # pragma: NO COVER + if strict: + value = str(obj) + if isinstance(obj, bool): + value = value.lower() + return value + return obj + + def transcode_request( # pragma: NO COVER + http_options: List[Dict[str, str]], + request: Any, + required_fields_default_values: Optional[Dict[str, Any]] = None, + rest_numeric_enums: bool = False, + ) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: + pb_request = getattr(request, "_pb", request) + transcoded_request = path_template.transcode(http_options, pb_request) + + body_json = None + if transcoded_request.get("body") is not None: + body_json = json_format.MessageToJson( + transcoded_request["body"], + use_integers_for_enums=rest_numeric_enums, + ) + + query_params_json = {} + if transcoded_request.get("query_params") is not None: + query_params_json = json.loads(json_format.MessageToJson( + transcoded_request["query_params"], + use_integers_for_enums=rest_numeric_enums, + )) + + if required_fields_default_values: + for k, v in required_fields_default_values.items(): + if k not in query_params_json: + query_params_json[k] = v + + if rest_numeric_enums: + query_params_json["$alt"] = "json;enum-encoding=int" + + return transcoded_request, body_json, query_params_json From 82aa10cd8c4bbac17c92c9f37a547d9eb48ffe4f Mon Sep 17 00:00:00 2001 From: Heba Alazzeh Date: Tue, 21 Jul 2026 23:52:59 +0000 Subject: [PATCH 40/40] fix(generator): install local google-api-core in nox test sessions --- packages/gapic-generator/noxfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/gapic-generator/noxfile.py b/packages/gapic-generator/noxfile.py index 52209f41ff38..dce1cf1504e2 100644 --- a/packages/gapic-generator/noxfile.py +++ b/packages/gapic-generator/noxfile.py @@ -181,6 +181,7 @@ def fragment(session, use_ads_templates=False): "grpcio-tools", ) session.install("-e", ".") + session.install("-e", "../google-api-core") # The specific failure is `Plugin output is unparseable` if session.python == "3.10": @@ -245,6 +246,7 @@ def showcase_library( # Install gapic-generator-python session.install("-e", ".") + session.install("-e", "../google-api-core") # Install grpcio-tools for protoc session.install("grpcio-tools")