feat: move request-id auto-population logic to gapic_v1 public helpers#17738
feat: move request-id auto-population logic to gapic_v1 public helpers#17738hebaalazzeh wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new helper module method_helpers containing a setup_request_id function to populate a UUID4 field in requests if not already set, supporting dicts, protobuf, and proto-plus messages. It also adds a session-scoped pytest fixture to isolate unit tests from workstation mTLS environments, updates a nox session configuration, and relaxes a timing assertion in test_bidi.py. As there are no review comments, no additional feedback is provided.
a0bff77 to
613b392
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a new helper module method_helpers containing the setup_request_id function, which populates a UUID4 field in request objects or dictionaries if not already set, along with corresponding unit tests. The review feedback suggests adding a None check at the beginning of setup_request_id and using a default value in getattr to prevent potential AttributeErrors, as well as adding a test case to verify graceful handling of None requests.
| def setup_request_id(request: Any, 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 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): | ||
| setattr(request, field_name, str(uuid.uuid4())) |
There was a problem hiding this comment.
To ensure robust defensive programming and prevent potential runtime errors, we should add a None check at the beginning of setup_request_id. Additionally, using getattr(request, field_name, None) instead of getattr(request, field_name) in the else block prevents an AttributeError if the attribute is completely missing from the object.
def setup_request_id(request: Any, 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()))References
- When processing optional parameters with a helper function, prefer placing the null or None check within the function body rather than at the call site to simplify the calling code and improve encapsulation.
| # test custom non-iterable object wrapper | ||
| req_obj3 = CustomRequestWrapper() | ||
| setup_request_id(req_obj3, "request_id", True) | ||
| assert req_obj3.request_id != "" | ||
| uuid.UUID(req_obj3.request_id) |
There was a problem hiding this comment.
Add a test case to verify that setup_request_id handles a None request gracefully without raising any exceptions.
| # test custom non-iterable object wrapper | |
| req_obj3 = CustomRequestWrapper() | |
| setup_request_id(req_obj3, "request_id", True) | |
| assert req_obj3.request_id != "" | |
| uuid.UUID(req_obj3.request_id) | |
| # test custom non-iterable object wrapper | |
| req_obj3 = CustomRequestWrapper() | |
| setup_request_id(req_obj3, "request_id", True) | |
| assert req_obj3.request_id != "" | |
| uuid.UUID(req_obj3.request_id) | |
| # test None request | |
| setup_request_id(None, "request_id", True) |
08dce4a to
e02ee4b
Compare
…lic helpers Introduces method_helpers module containing setup_request_id helper. Exposes the module as public in gapic_v1.
e02ee4b to
db040bb
Compare
| # limitations under the License. | ||
| # | ||
|
|
||
| """Helpers for method requests.""" |
There was a problem hiding this comment.
Can you add a better docstring?
| @@ -0,0 +1,59 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
Can we use a better name for this file? We already have api_core.gapic_v1.method, so api_core.gapic_v1.method_helpers seems confusing.
Would this fit in with the other helpers in api_core.gapic_v1.method? Or one of the other existing files? (method.py describes itself as "Helpers for wrapping low-level gRPC methods with common functionality". I can't remember if that's how this method is used)
If not, maybe we could call this api_core.gapic_v1.request, since it seems to be for building requests? Are other similar methods coming later, or is this a one-off?
Introduces method_helpers module containing setup_request_id helper. Exposes the module as public in gapic_v1.
The unit tests in test_method_helpers.py are now identical to the generator's original test template test_service.py.j2 (with the exception of our added None request test case).