Skip to content

Commit 20adea6

Browse files
committed
chore: address PR review comments for gapic centralization request ID
1 parent 62fbcc8 commit 20adea6

2 files changed

Lines changed: 97 additions & 97 deletions

File tree

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ def setup_request_id(
3434
) -> None:
3535
"""Populate a UUID4 field in the request if it is not already set.
3636
37+
This helper is used to ensure request idempotency by automatically
38+
generating a unique identifier (such as `request_id`) for requests
39+
that support it. If a request is retried, the same identifier can be
40+
sent on subsequent retries, allowing the server to recognize the retried
41+
request and prevent duplicate processing (e.g., creating duplicate
42+
resources).
43+
3744
Args:
3845
request (Union[google.protobuf.message.Message, dict]): The
3946
request object.

packages/google-api-core/tests/unit/gapic/test_request.py

Lines changed: 90 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -15,105 +15,98 @@
1515

1616
import re
1717

18-
from google.api_core.gapic_v1.request import setup_request_id
19-
20-
21-
def test_setup_request_id():
22-
class MockRequest:
23-
def __init__(self, **kwargs):
24-
for k, v in kwargs.items():
25-
setattr(self, k, v)
26-
27-
def __contains__(self, key):
28-
return hasattr(self, key)
29-
30-
class MockProtoRequest:
31-
def __init__(self, **kwargs):
32-
for k, v in kwargs.items():
33-
setattr(self, k, v)
34-
35-
def HasField(self, key):
36-
return hasattr(self, key)
37-
38-
# Test with proto3 optional field not in request
39-
request = MockRequest()
40-
setup_request_id(request, "request_id", True)
41-
assert re.match(
42-
r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
43-
request.request_id,
44-
)
45-
46-
# Test with proto3 optional field already in request
47-
request = MockRequest(request_id="already_set")
48-
setup_request_id(request, "request_id", True)
49-
assert request.request_id == "already_set"
50-
51-
# Test with non-proto3 optional field empty
52-
request = MockRequest(request_id="")
53-
setup_request_id(request, "request_id", False)
54-
assert re.match(
55-
r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
56-
request.request_id,
57-
)
18+
import pytest
5819

59-
# Test with non-proto3 optional field already set
60-
request = MockRequest(request_id="already_set")
61-
setup_request_id(request, "request_id", False)
62-
assert request.request_id == "already_set"
63-
64-
# Test with proto3 optional field not in request (MockProtoRequest)
65-
request = MockProtoRequest()
66-
setup_request_id(request, "request_id", True)
67-
assert re.match(
68-
r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
69-
request.request_id,
70-
)
71-
72-
# Test with proto3 optional field already in request (MockProtoRequest)
73-
request = MockProtoRequest(request_id="already_set")
74-
setup_request_id(request, "request_id", True)
75-
assert request.request_id == "already_set"
76-
77-
# Test with ValueError
78-
class MockValueErrorRequest:
79-
def HasField(self, key):
80-
raise ValueError("Mismatched field")
81-
82-
def __contains__(self, key):
83-
return hasattr(self, key)
84-
85-
request = MockValueErrorRequest()
86-
setup_request_id(request, "request_id", True)
87-
assert re.match(
88-
r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
89-
request.request_id,
90-
)
20+
from google.api_core.gapic_v1.request import setup_request_id
9121

92-
# Test with dict and proto3 optional field not in request
93-
request = {}
94-
setup_request_id(request, "request_id", True)
95-
assert re.match(
96-
r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
97-
request["request_id"],
98-
)
9922

100-
# Test with dict and proto3 optional field already in request
101-
request = {"request_id": "already_set"}
102-
setup_request_id(request, "request_id", True)
103-
assert request["request_id"] == "already_set"
104-
105-
# Test with dict and non-proto3 optional field empty
106-
request = {"request_id": ""}
107-
setup_request_id(request, "request_id", False)
108-
assert re.match(
109-
r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
110-
request["request_id"],
23+
# --- Mock Request Helper Classes ---
24+
25+
26+
class MockRequest:
27+
def __init__(self, **kwargs):
28+
for k, v in kwargs.items():
29+
setattr(self, k, v)
30+
31+
def __contains__(self, key):
32+
return hasattr(self, key)
33+
34+
35+
class MockProtoRequest:
36+
def __init__(self, **kwargs):
37+
for k, v in kwargs.items():
38+
setattr(self, k, v)
39+
40+
def HasField(self, key):
41+
return hasattr(self, key)
42+
43+
44+
class MockValueErrorRequest:
45+
def HasField(self, key):
46+
raise ValueError("Mismatched field")
47+
48+
def __contains__(self, key):
49+
return hasattr(self, key)
50+
51+
52+
# --- Parameterized Test ---
53+
54+
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}"
55+
56+
57+
@pytest.mark.parametrize(
58+
"request_obj, is_proto3_optional, expected",
59+
[
60+
# MockRequest cases
61+
(MockRequest(), True, "uuid"),
62+
(MockRequest(request_id="already_set"), True, "already_set"),
63+
(MockRequest(request_id=""), False, "uuid"),
64+
(MockRequest(request_id="already_set"), False, "already_set"),
65+
# MockProtoRequest cases
66+
(MockProtoRequest(), True, "uuid"),
67+
(MockProtoRequest(request_id="already_set"), True, "already_set"),
68+
# ValueError case
69+
(MockValueErrorRequest(), True, "uuid"),
70+
# Dict cases
71+
({}, True, "uuid"),
72+
({"request_id": "already_set"}, True, "already_set"),
73+
({"request_id": ""}, False, "uuid"),
74+
({"request_id": "already_set"}, False, "already_set"),
75+
# None case
76+
(None, True, "none"),
77+
],
78+
ids=[
79+
"proto3_optional_not_in_request",
80+
"proto3_optional_already_in_request",
81+
"non_proto3_optional_empty",
82+
"non_proto3_optional_already_set",
83+
"proto3_optional_not_in_request_proto",
84+
"proto3_optional_already_in_request_proto",
85+
"value_error_fallback",
86+
"dict_proto3_optional_not_in_request",
87+
"dict_proto3_optional_already_in_request",
88+
"dict_non_proto3_optional_empty",
89+
"dict_non_proto3_optional_already_set",
90+
"none_request",
91+
],
92+
)
93+
def test_setup_request_id(request_obj, is_proto3_optional, expected):
94+
# Act
95+
setup_request_id(request_obj, "request_id", is_proto3_optional)
96+
97+
# Assert
98+
if expected == "none":
99+
assert request_obj is None
100+
return
101+
102+
# Extract the resulting value depending on container type
103+
value = (
104+
request_obj["request_id"]
105+
if isinstance(request_obj, dict)
106+
else request_obj.request_id
111107
)
112108

113-
# Test with dict and non-proto3 optional field already set
114-
request = {"request_id": "already_set"}
115-
setup_request_id(request, "request_id", False)
116-
assert request["request_id"] == "already_set"
117-
118-
# Test with None request (should handle gracefully without raising exception)
119-
setup_request_id(None, "request_id", True)
109+
if expected == "uuid":
110+
assert re.match(UUID_REGEX, value)
111+
else:
112+
assert value == expected

0 commit comments

Comments
 (0)