Skip to content

Commit 613b392

Browse files
committed
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.
1 parent 1ef0340 commit 613b392

3 files changed

Lines changed: 118 additions & 1 deletion

File tree

packages/google-api-core/google/api_core/gapic_v1/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
# Older Python versions safely ignore this variable.
2626
__lazy_modules__: Set[str] = {
2727
"google.api_core.gapic_v1.client_info",
28+
"google.api_core.gapic_v1.method_helpers",
2829
"google.api_core.gapic_v1.routing_header",
2930
}
30-
__all__ = ["client_info", "routing_header"]
31+
__all__ = ["client_info", "method_helpers", "routing_header"]
3132

3233
if _has_grpc:
3334
__lazy_modules__.update(
@@ -41,6 +42,7 @@
4142

4243
from google.api_core.gapic_v1 import ( # noqa: E402
4344
client_info,
45+
method_helpers,
4446
routing_header,
4547
)
4648

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
"""Helpers for method requests."""
18+
19+
import uuid
20+
from typing import Any
21+
22+
23+
def setup_request_id(request: Any, field_name: str, is_proto3_optional: bool) -> None:
24+
"""Populate a UUID4 field in the request if it is not already set.
25+
26+
Args:
27+
request (Union[google.protobuf.message.Message, dict]): The
28+
request object.
29+
field_name (str): The name of the field to populate.
30+
is_proto3_optional (bool): Whether the field is proto3 optional.
31+
"""
32+
if isinstance(request, dict):
33+
if is_proto3_optional:
34+
if field_name not in request:
35+
request[field_name] = str(uuid.uuid4())
36+
elif not request.get(field_name):
37+
request[field_name] = str(uuid.uuid4())
38+
return
39+
40+
if is_proto3_optional:
41+
try:
42+
# Pure protobuf messages
43+
if not request.HasField(field_name):
44+
setattr(request, field_name, str(uuid.uuid4()))
45+
except (AttributeError, ValueError):
46+
# Proto-plus messages or other objects
47+
if not getattr(request, field_name, None):
48+
setattr(request, field_name, str(uuid.uuid4()))
49+
else:
50+
if not getattr(request, field_name):
51+
setattr(request, field_name, str(uuid.uuid4()))
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2026 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
from google.api_core.gapic_v1.method_helpers import setup_request_id
18+
19+
20+
def test_setup_request_id():
21+
import uuid
22+
23+
# test dict request
24+
req = {}
25+
setup_request_id(req, "request_id", True)
26+
assert "request_id" in req
27+
uuid_str = req["request_id"]
28+
uuid.UUID(uuid_str) # verify it is a valid UUID
29+
30+
# test dict request when already set
31+
req = {"request_id": "existing"}
32+
setup_request_id(req, "request_id", True)
33+
assert req["request_id"] == "existing"
34+
35+
class DummyRequest:
36+
def __init__(self):
37+
self.request_id = ""
38+
39+
def HasField(self, field_name):
40+
if not hasattr(self, field_name):
41+
raise ValueError()
42+
return bool(getattr(self, field_name))
43+
44+
# test object request proto3 optional true
45+
req_obj = DummyRequest()
46+
setup_request_id(req_obj, "request_id", True)
47+
assert req_obj.request_id != ""
48+
uuid.UUID(req_obj.request_id)
49+
50+
# test object request proto3 optional false
51+
req_obj2 = DummyRequest()
52+
setup_request_id(req_obj2, "request_id", False)
53+
assert req_obj2.request_id != ""
54+
uuid.UUID(req_obj2.request_id)
55+
56+
class CustomRequestWrapper:
57+
def __init__(self):
58+
self.request_id = ""
59+
60+
# test custom non-iterable object wrapper
61+
req_obj3 = CustomRequestWrapper()
62+
setup_request_id(req_obj3, "request_id", True)
63+
assert req_obj3.request_id != ""
64+
uuid.UUID(req_obj3.request_id)

0 commit comments

Comments
 (0)