diff --git a/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 8f0bf28f7416..e8953eb0742e 100644 --- a/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -70,8 +70,10 @@ from google.iam.v1 import policy_pb2 # type: ignore {% endfilter %} {{ shared_macros.add_google_api_core_version_header_import(service.version) }} +{% if api.all_method_settings.values()|map(attribute="auto_populated_fields", default=[])|list %} +_UUID4_RE = re.compile(r"{{ uuid4_re }}") +{% endif %} -{% with uuid4_re = "[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}" %} def client_cert_source_callback(): return b"cert bytes", b"key bytes" @@ -692,7 +694,7 @@ def test_{{ method_name }}_empty_call(): {% if method_settings is not none %} {% for auto_populated_field in method_settings.auto_populated_fields %} # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"{{ uuid4_re }}", args[0].{{ auto_populated_field }}) + assert _UUID4_RE.match(args[0].{{ auto_populated_field }}) # clear UUID field so that the check below succeeds args[0].{{ auto_populated_field }} = None {% endfor %} @@ -730,7 +732,7 @@ def test_{{ method_name }}_non_empty_request_with_auto_populated_field(): {% if method_settings is not none %} {% for auto_populated_field in method_settings.auto_populated_fields %} # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"{{ uuid4_re }}", args[0].{{ auto_populated_field }}) + assert _UUID4_RE.match(args[0].{{ auto_populated_field }}) # clear UUID field so that the check below succeeds args[0].{{ auto_populated_field }} = None {% endfor %} @@ -2488,5 +2490,4 @@ def test_client_ctx(): pass close.assert_called() -{% endwith %}{# uuid4_re #} {% endblock %} diff --git a/packages/gapic-generator/gapic/generator/generator.py b/packages/gapic-generator/gapic/generator/generator.py index e9e006e74884..9fe56aa9de1d 100644 --- a/packages/gapic-generator/gapic/generator/generator.py +++ b/packages/gapic-generator/gapic/generator/generator.py @@ -75,6 +75,9 @@ def __init__(self, opts: Options) -> None: self._env.tests["str_field_pb"] = utils.is_str_field_pb self._env.tests["msg_field_pb"] = utils.is_msg_field_pb + # Add global variables. + self._env.globals["uuid4_re"] = utils.UUID4_RE + self._sample_configs = opts.sample_configs def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorResponse: 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 ea93a9b48c26..bd5d5a42ff7f 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 @@ -106,6 +106,9 @@ 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=[])|list %} +_UUID4_RE = re.compile(r"{{ uuid4_re }}") +{% endif %} async def mock_async_gen(data, chunk_size=1): 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 c3dfea385b17..648f2e868812 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 @@ -150,7 +150,7 @@ def test_{{ method_name }}_non_empty_request_with_auto_populated_field(): {% if method_settings is not none %} {% for auto_populated_field in method_settings.auto_populated_fields %} # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"{{ get_uuid4_re() }}", args[0].{{ auto_populated_field }}) + assert _UUID4_RE.fullmatch(args[0].{{ auto_populated_field }}) request_msg.{{ auto_populated_field }} = args[0].{{ auto_populated_field }} {% endfor %} {% endif %}{# if method_settings is not none #} @@ -1208,7 +1208,7 @@ def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ide # Ensure that the uuid4 field is set according to AIP 4235 for i, (key, value) in enumerate(req.call_args.kwargs['params']): if key == "{{ auto_populated_field|camel_case }}": - assert re.fullmatch(r"{{ get_uuid4_re() }}", value) + assert _UUID4_RE.match(value) break # Include {{ auto_populated_field|camel_case }} within expected_params with value mock.ANY @@ -1565,7 +1565,7 @@ def test_{{ method_name }}_rest_no_http_options(): {% if method_settings is not none %} {% for auto_populated_field in method_settings.auto_populated_fields %} # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"{{ get_uuid4_re() }}", args[0].{{ auto_populated_field }}) + assert _UUID4_RE.fullmatch(args[0].{{ auto_populated_field }}) request_msg.{{ auto_populated_field }} = args[0].{{ auto_populated_field }} {% endfor %}{# for auto_populated_field in method_settings.auto_populated_fields #} {% endif %}{# if method_settings is not none #} @@ -2196,7 +2196,7 @@ def test_initialize_client_w_{{transport_name}}(): {% endmacro %}{# empty_call_test #} {% macro get_uuid4_re() -%} -[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12} +{{ uuid4_re }} {%- endmacro %}{# uuid_re #} {% macro routing_parameter_test(service, api, transport, is_async) %} diff --git a/packages/gapic-generator/gapic/utils/__init__.py b/packages/gapic-generator/gapic/utils/__init__.py index 23c573915695..18d78c31dd1e 100644 --- a/packages/gapic-generator/gapic/utils/__init__.py +++ b/packages/gapic-generator/gapic/utils/__init__.py @@ -22,6 +22,7 @@ from gapic.utils.code import nth from gapic.utils.code import partition from gapic.utils.code import make_private +from gapic.utils.constants import UUID4_RE from gapic.utils.doc import doc from gapic.utils.filename import to_valid_filename from gapic.utils.filename import to_valid_module_name @@ -52,5 +53,6 @@ "to_camel_case", "to_valid_filename", "to_valid_module_name", + "UUID4_RE", "wrap", ) diff --git a/packages/gapic-generator/gapic/utils/constants.py b/packages/gapic-generator/gapic/utils/constants.py new file mode 100644 index 000000000000..e21f7cd2170f --- /dev/null +++ b/packages/gapic-generator/gapic/utils/constants.py @@ -0,0 +1,16 @@ +# 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 +# +# https://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. + +# The regex for a UUID4 as specified in AIP-4235. +UUID4_RE = r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}" 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 51af4c7b84d7..f25a28395082 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 @@ -74,6 +74,7 @@ "principal": "service-account@example.com", } CRED_INFO_STRING = json.dumps(CRED_INFO_JSON) +_UUID4_RE = re.compile(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}") async def mock_async_gen(data, chunk_size=1): @@ -1947,7 +1948,7 @@ def test_create_job_non_empty_request_with_auto_populated_field(): job_id='job_id_value', ) # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -2293,7 +2294,7 @@ def test_delete_job_non_empty_request_with_auto_populated_field(): name='name_value', ) # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -2606,7 +2607,7 @@ def test_cancel_job_non_empty_request_with_auto_populated_field(): name='name_value', ) # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -4204,7 +4205,7 @@ def test_create_job_rest_required_fields(request_type=storage_batch_operations.C # Ensure that the uuid4 field is set according to AIP 4235 for i, (key, value) in enumerate(req.call_args.kwargs['params']): if key == "requestId": - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", value) + assert _UUID4_RE.match(value) break # Include requestId within expected_params with value mock.ANY @@ -4382,7 +4383,7 @@ def test_delete_job_rest_required_fields(request_type=storage_batch_operations.D # Ensure that the uuid4 field is set according to AIP 4235 for i, (key, value) in enumerate(req.call_args.kwargs['params']): if key == "requestId": - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", value) + assert _UUID4_RE.match(value) break # Include requestId within expected_params with value mock.ANY @@ -4558,7 +4559,7 @@ def test_cancel_job_rest_required_fields(request_type=storage_batch_operations.C # Ensure that the uuid4 field is set according to AIP 4235 for i, (key, value) in enumerate(req.call_args.kwargs['params']): if key == "requestId": - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", value) + assert _UUID4_RE.match(value) break # Include requestId within expected_params with value mock.ANY @@ -5193,7 +5194,7 @@ def test_create_job_empty_call_grpc(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.CreateJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -5218,7 +5219,7 @@ def test_delete_job_empty_call_grpc(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.DeleteJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -5243,7 +5244,7 @@ def test_cancel_job_empty_call_grpc(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.CancelJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -5388,7 +5389,7 @@ async def test_create_job_empty_call_grpc_asyncio(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.CreateJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -5415,7 +5416,7 @@ async def test_delete_job_empty_call_grpc_asyncio(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.DeleteJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -5443,7 +5444,7 @@ async def test_cancel_job_empty_call_grpc_asyncio(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.CancelJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -6695,7 +6696,7 @@ def test_create_job_empty_call_rest(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.CreateJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -6719,7 +6720,7 @@ def test_delete_job_empty_call_rest(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.DeleteJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg @@ -6743,7 +6744,7 @@ def test_cancel_job_empty_call_rest(): _, args, _ = call.mock_calls[0] request_msg = storage_batch_operations.CancelJobRequest() # Ensure that the uuid4 field is set according to AIP 4235 - assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) + assert _UUID4_RE.fullmatch(args[0].request_id) request_msg.request_id = args[0].request_id assert args[0] == request_msg