fix(api-core): use truthiness check in setup_request_id to support proto-plus messages - #18000
Conversation
…oto-plus messages
There was a problem hiding this comment.
Code Review
This pull request updates the setup_request_id function to use a truthiness check instead of an identity check (is None) when handling proto-plus messages or other objects, ensuring empty string request IDs are auto-populated with a UUID. The feedback suggests extending this truthiness check to the dictionary handling block for consistency, and updating the removed test cases to assert that empty strings are now correctly populated with a UUID rather than deleting them.
| except (AttributeError, ValueError): | ||
| # Proto-plus messages or other objects | ||
| if getattr(request, field_name, None) is None: | ||
| if not getattr(request, field_name, None): |
There was a problem hiding this comment.
While updating the fallback for proto-plus/other objects to use a truthiness check (if not getattr(...)), the dictionary handling block at the beginning of setup_request_id (which is outside this diff) was not updated. It likely still uses if request.get(field_name) is None: when is_proto3_optional is True.
To ensure consistent behavior between dictionaries and objects (especially since the corresponding dictionary test case ({"request_id": ""}, True, "") was removed), please also update the dictionary handling block to use a truthiness check:
if isinstance(request, dict):
if is_proto3_optional:
if not request.get(field_name):
request[field_name] = str(uuid.uuid4())| (MockRequest(), True, "uuid"), | ||
| (MockRequest(request_id="already_set"), True, "already_set"), | ||
| (MockRequest(request_id=""), True, ""), | ||
| (MockRequest(request_id=""), False, "uuid"), |
There was a problem hiding this comment.
| ({"request_id": None}, True, "uuid"), | ||
| ({"request_id": "already_set"}, True, "already_set"), | ||
| ({"request_id": ""}, True, ""), | ||
| ({"request_id": ""}, False, "uuid"), |
There was a problem hiding this comment.
Instead of removing the test case ({"request_id": ""}, True, ""), it should be updated to expect "uuid" (i.e., ({"request_id": ""}, True, "uuid")) to explicitly verify that an empty string in a dictionary is now correctly auto-populated with a UUID when is_proto3_optional is True (once the dictionary handling in setup_request_id is also updated).
| except (AttributeError, ValueError): | ||
| # Proto-plus messages or other objects | ||
| if getattr(request, field_name, None) is None: | ||
| if not getattr(request, field_name, None): |
There was a problem hiding this comment.
Gemini suggested this refactor because of the following issues:
- AIP-4235 Violation (see link below): Shifting to a truthiness check
if not getattr(...)on objects withis_proto3_optional=Truebreaks the explicit presence contract. Any explicit empty string""provided by a user will be silently overwritten by a generated UUID. We need to useHasFieldto check if the value was set to empty string by the user. We need to useHasFieldin theproto-plushandling also: https://protobuf.dev/programming-guides/field_presence/#using-the-generated-code
From https://google.aip.dev/client-libraries/4235#expected-generator-and-client-library-behavior,
The field must be automatically populated if and only if one of the following conditions holds:
The field supports explicit presence, and has not been set by the user
-
High Redundancy: The token generation block
str(uuid.uuid4())is duplicated 5 separate times throughout the function. -
Overly Complex Branching: Dictionaries, pure protobufs, proto-plus messages, and custom objects are routed through separate, deeply nested conditional blocks, which makes maintenance error-prone.
import uuid
from typing import Any, Union
def setup_request_id(
request: Union[Any, dict, None],
field_name: str,
is_proto3_optional: bool,
) -> None:
"""Populate a UUID4 field in the request if it is not already set.
Ensures request idempotency by automatically generating a unique
identifier (such as `request_id`) for requests supporting it.
"""
if request is None:
return
# 1. Evaluate whether the field is considered "unset" and needs population
should_populate = False
if isinstance(request, dict):
if is_proto3_optional:
# AIP-4235: Only populate if completely missing or strictly None
should_populate = field_name not in request or request[field_name] is None
else:
# Populate if the field is missing or falsy (e.g. empty string)
should_populate = not request.get(field_name)
else:
# Check for proto-plus wrapper (which has an underlying ._pb message)
is_proto_plus = hasattr(request, "_pb") and hasattr(request._pb, "HasField")
if is_proto3_optional:
if is_proto_plus:
try:
# Ask the underlying protobuf if the field has explicit presence
should_populate = not request._pb.HasField(field_name)
except ValueError:
# Fallback for non-optional fields or non-presence primitives
should_populate = getattr(request, field_name, None) is None
else:
try:
# Pure protobuf messages
should_populate = not request.HasField(field_name)
except (AttributeError, ValueError):
# Standard Python objects / Mock requests: Only populate if strictly None
should_populate = getattr(request, field_name, None) is None
else:
# If not proto3 optional, populate on any falsy value (None or empty string)
should_populate = not getattr(request, field_name, None)
# 2. Consolidate mutation to a single, clean DRY block
if should_populate:
generated_id = str(uuid.uuid4())
if isinstance(request, dict):
request[field_name] = generated_id
else:
setattr(request, field_name, generated_id)
| # MockRequest cases | ||
| (MockRequest(), True, "uuid"), | ||
| (MockRequest(request_id="already_set"), True, "already_set"), | ||
| (MockRequest(request_id=""), True, ""), |
There was a problem hiding this comment.
Since is_proto3_optional is True, we may still need this test case to follow the AIP
From https://google.aip.dev/client-libraries/4235#expected-generator-and-client-library-behavior,
The field must be automatically populated if and only if one of the following conditions holds:
The field supports explicit presence, and has not been set by the user
…d auto-population
|
|
||
| def setup_request_id( | ||
| request: Union[google.protobuf.message.Message, dict, None], | ||
| request: Union[Any, dict, None], |
There was a problem hiding this comment.
do we have to lose this typing? Can it really be anything?
There was a problem hiding this comment.
In Python, proto-plus message classes (proto.Message) are wrappers around underlying protobuf messages (._pb) and do not inherit from google.protobuf.message.Message.
Because of this, if we keep request: Union[google.protobuf.message.Message, dict, None], static type checkers (like mypy) will report type incompatibilities whenever a proto-plus request object is passed into setup_request_id.
Using Any here allows the function to accept:
proto-plusmessage wrappers (proto.Message)- Pure protobuf messages (
google.protobuf.message.Message) - Dictionaries (
dict) - Custom/Mock request objects
This avoids needing a hard runtime dependency/import on proto-plus just for type hinting while ensuring static type checkers don't fail when proto-plus requests are passed.
| request (Union[Any, dict, None]): The | ||
| request object. | ||
| field_name (str): The name of the field to populate. | ||
| is_proto3_optional (bool): Whether the field is proto3 optional. |
There was a problem hiding this comment.
Can you explain this field a bit more? I'm not sure what exactly this means in this context, but it seems important
There was a problem hiding this comment.
Gemini suggested this as a docstring, does it seem accurate?
is_proto3_optional (bool): Whether the field is declared as `optional`
in the proto schema (`proto3 optional`). Enforces proto presence
semantics across message objects and dictionaries:
- If True, explicit empty strings ("") are preserved and only unset
fields (or missing/None dict keys) are auto-populated.
- If False, any empty or unset string is replaced with a generated UUID.
(In hingsight, I wish we gave this a better name, like "preserve_empty_strings". But probably not worth the potential breaking change)
There was a problem hiding this comment.
In protobuf 3 (proto3), fields originally did not support explicit presence—there was no way to distinguish whether a user explicitly set a field to its default value (like "") or left it unset.
When a field is marked with optional in proto3 (optional string request_id = 2;), it enables explicit presence tracking. In GAPIC and api-core, is_proto3_optional indicates whether the target field (field_name) was defined with explicit presence in the proto schema.
Why this is important for setup_request_id (AIP-4235 Compliance):
According to AIP-4235 (Idempotency / Request ID):
"The field must be automatically populated if and only if one of the following conditions holds:
The field supports explicit presence, and has not been set by the user."
When is_proto3_optional = True:
-
Unset field (user never passed
request_id): We checknot request._pb.HasField(field_name)$\rightarrow$ auto-populate a UUID. -
Explicitly set to empty string (user passed
request_id=""):HasFieldreturnsTrue$\rightarrow$ preserve the user's explicit empty string""(do NOT overwrite with a UUID).
When is_proto3_optional = False (no explicit presence):
- We fall back to a truthiness check (
not getattr(...)/not request.get(...)), where any falsy value (Noneor"") is treated as unset and auto-populated with a UUID.
| return | ||
|
|
||
| should_populate = False | ||
| if isinstance(request, dict): |
There was a problem hiding this comment.
more comments would be helpful here. There are a lot of nested cases, it's hard to follow
Maybe this should even be broken into multiple helpers
There was a problem hiding this comment.
added more comments!
| pb_msg = getattr(request, "_pb", None) | ||
| is_proto_plus = pb_msg is not None and hasattr(pb_msg, "HasField") | ||
| if is_proto3_optional: | ||
| if is_proto_plus and pb_msg is not None: |
There was a problem hiding this comment.
nit: it looks like is_proto_plus already implies pb_msg is not None
There was a problem hiding this comment.
Done. Resolved by unwrapping the underlying _pb directly with getattr(request, "_pb", request) and removing the redundant intermediate checks.
| should_populate = not request.HasField(field_name) | ||
| except (AttributeError, ValueError): | ||
| # Fall back for objects/mocks that do not implement `HasField` or where `HasField` fails. | ||
| should_populate = getattr(request, field_name, None) is None |
There was a problem hiding this comment.
proto-plus objects contain a pure-proto instance, so case 2a and 2b seem redundant. Looking at the code, it seems to be the same logic, just using different variables? Can we just unwrap the proto-plus object, and use the same parsing logic?
Something like:
if is_proto3_optional:
# extract the protobuf from proto-plus if wrapped
pure_pb: google.protobuf.message.Message = getattr(request, "_pb", request)
try:
should_populate = not pure_pb.HasField(field_name)
except ValueError:
should_populate = getattr(pure_pb, field_name, None) is None
else:
should_populate = not getattr(request, field_name, None)
There was a problem hiding this comment.
Done. Updated to unwrap pure_pb: google.protobuf.message.Message = getattr(request, "_pb", request) and consolidated the parsing logic into a single presence-checking block.
| else: | ||
| # Case 2c: Object request without explicit presence (`is_proto3_optional=False`). | ||
| # Auto-populate if the field value is falsy (None or empty string ''). | ||
| should_populate = not getattr(request, field_name, None) |
There was a problem hiding this comment.
nit: this may be more readable if cast to bool:
should_populate = not bool(getattr(request, field_name, False))
There was a problem hiding this comment.
Done. Cast to bool as suggested.
| # Auto-populate if the field value is falsy (None or empty string ''). | ||
| should_populate = not getattr(request, field_name, None) | ||
|
|
||
| # Consolidate mutation to a single, clean DRY block. |
There was a problem hiding this comment.
I'd leave out the meta-commentary about this being a DRY block, and focus on what the block is supposed to accomplish.
Maybe something like:
# If the field was found to be empty, set random id
There was a problem hiding this comment.
Done. Updated the comment to # If the field was found to be empty, set random id.
| else: | ||
| if not getattr(request, field_name, None): | ||
| setattr(request, field_name, str(uuid.uuid4())) | ||
| # Case 2: Object request (proto-plus wrapper, pure protobuf message, or mock/dict-like object). |
There was a problem hiding this comment.
probably no need to mention mock/dict-like objects, since those aren't covered by our type annotations, so we don't formally support them
There was a problem hiding this comment.
Done. Updated the comment to # Case 2: Object request (proto-plus wrapper or pure protobuf message).
|
|
||
| def __contains__(self, key): | ||
| return hasattr(self, key) | ||
|
|
There was a problem hiding this comment.
nit: have you considered using magic mock to test some of these? Custom classes with getattr, setattr and hasattr implementations shouldn't be needed
There was a problem hiding this comment.
Removed all unnecessary contains implementations from the mock classes. Kept the lightweight class structures so attribute access and HasField presence behavior remain explicit and deterministic without MagicMock auto-generating truthy attributes.
| setattr(request, field_name, str(uuid.uuid4())) | ||
| # Case 2: Object request (proto-plus wrapper or pure protobuf message). | ||
| if is_proto3_optional: | ||
| # Extract the protobuf from proto-plus if wrapped. |
There was a problem hiding this comment.
very small nit: it feels a bit unbalanced that we have Case 1a and 1b, but then Case 2 and 2b
(also, "Proto request" feels more descriptive than "Object request")
🤖 I have created a release *beep* *boop* --- <details><summary>bigquery-magics: 0.15.1</summary> ## [0.15.1](bigquery-magics-v0.15.0...bigquery-magics-v0.15.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>gapic-generator: 1.38.0</summary> ## [1.38.0](gapic-generator-v1.37.1...gapic-generator-v1.38.0) (2026-08-06) ### Features * **generator:** delegate REST transcoding to google-api-core ([#17766](#17766)) ([7b05aab](7b05aab)) * **generator:** gapic generator centralization routing ([#17816](#17816)) ([32a2442](32a2442)) ### Bug Fixes * add typing for header_params ([#17914](#17914)) ([9e98b93](9e98b93)) * **api-core:** use truthiness check in setup_request_id to support proto-plus messages ([#18000](#18000)) ([ad8f93c](ad8f93c)) * avoid retaining routing parameter instances in cache ([#17961](#17961)) ([f64ada2](f64ada2)) * bump aiohttp from 3.13.5 to 3.14.3 in /packages/gapic-generator ([#17990](#17990)) ([6ff5815](6ff5815)) * bump cryptography from 48.0.1 to 50.0.0 in /packages/gapic-generator ([#17991](#17991)) ([d607f25](d607f25)) * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * **generator:** use flat_ref_types in test templates and delete remove-unused-imports ([#17900](#17900)) ([395f764](395f764)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) * resolve collision with reserved words in samples ([#17912](#17912)) ([588cda9](588cda9)) * upgrade Protobuf and gRPC in WORKSPACE ([#17882](#17882)) ([5b5ece5](5b5ece5)) </details> <details><summary>gcp-sphinx-docfx-yaml: 3.3.1</summary> ## [3.3.1](gcp-sphinx-docfx-yaml-v3.3.0...gcp-sphinx-docfx-yaml-v3.3.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-ads-admanager: 0.10.1</summary> ## [0.10.1](google-ads-admanager-v0.10.0...google-ads-admanager-v0.10.1) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-api-core: 2.34.0</summary> ## [2.34.0](google-api-core-v2.33.0...google-api-core-v2.34.0) (2026-08-06) ### Features * Add Feature Gating configuration helpers. ([#17524](#17524)) ([eceea95](eceea95)) * **api-core:** centralize rest transcoding helpers ([#17765](#17765)) ([4f21b8b](4f21b8b)) ### Bug Fixes * **api-core:** use truthiness check in setup_request_id to support proto-plus messages ([#18000](#18000)) ([ad8f93c](ad8f93c)) * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * deduplicate x-goog-api-client headers ([#17616](#17616)) ([6167e41](6167e41)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-apps-chat: 0.10.4</summary> ## [0.10.4](google-apps-chat-v0.10.3...google-apps-chat-v0.10.4) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-auth: 2.56.3</summary> ## [2.56.3](google-auth-v2.56.2...google-auth-v2.56.3) (2026-08-06) ### Bug Fixes * **auth:** avoid creating mTLS SSL context for custom async transports ([#17825](#17825)) ([fbe33f9](fbe33f9)), refs [#17622](#17622) * **auth:** only trigger mTLS certificate rotation on mTLS endpoints ([#17928](#17928)) ([f7b49ea](f7b49ea)) * **auth:** properly extract stdout from gnubby webauthn plugin failures ([#17885](#17885)) ([744e826](744e826)) * deduplicate x-goog-api-client headers ([#17616](#17616)) ([6167e41](6167e41)) * **oauth2:** avoid redundant JWKS network fetches ([#17891](#17891)) ([de53298](de53298)) ### Performance Improvements * **auth:** use generator expression in any() to allow short-circuiting ([735e565](735e565)) * **auth:** use generator expression in any() to allow short-circuiting ([#17937](#17937)) ([735e565](735e565)) </details> <details><summary>google-auth-httplib2: 0.4.1</summary> ## [0.4.1](google-auth-httplib2-v0.4.0...google-auth-httplib2-v0.4.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-access-context-manager: 0.6.1</summary> ## [0.6.1](google-cloud-access-context-manager-v0.6.0...google-cloud-access-context-manager-v0.6.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-agentidentitycredentials: 0.1.1</summary> ## [0.1.1](google-cloud-agentidentitycredentials-v0.1.0...google-cloud-agentidentitycredentials-v0.1.1) (2026-08-06) ### Features * **google/cloud/agentidentitycredentials/v1beta:** add google-cloud-agentidentitycredentials v1beta ([#17898](#17898)) ([b692dae](b692dae)) * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-audit-log: 0.6.1</summary> ## [0.6.1](google-cloud-audit-log-v0.6.0...google-cloud-audit-log-v0.6.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-backupdr: 0.10.1</summary> ## [0.10.1](google-cloud-backupdr-v0.10.0...google-cloud-backupdr-v0.10.1) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-batch: 0.22.2</summary> ## [0.22.2](google-cloud-batch-v0.22.1...google-cloud-batch-v0.22.2) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-biglake: 0.5.1</summary> ## [0.5.1](google-cloud-biglake-v0.5.0...google-cloud-biglake-v0.5.1) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-biglake-hive: 0.3.2</summary> ## [0.3.2](google-cloud-biglake-hive-v0.3.1...google-cloud-biglake-hive-v0.3.2) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-bigquery: 3.43.0</summary> ## [3.43.0](google-cloud-bigquery-v3.42.3...google-cloud-bigquery-v3.43.0) (2026-08-06) ### Features * add pandas-gbq capability helper ([#17957](#17957)) ([2207ca6](2207ca6)) ### Bug Fixes * **bigquery:** Fix bigquery socket leak ([#17953](#17953)) ([8c26b38](8c26b38)) * remove stray debug print in RangeQueryParameter constructor ([#17973](#17973)) ([fe7bfd0](fe7bfd0)) ### Documentation * add connector libraries overview table to package README ([#17939](#17939)) ([71bc622](71bc622)) * correct return type in CellDataParser.time_to_py docstring ([#17972](#17972)) ([bd1e224](bd1e224)) </details> <details><summary>google-cloud-bigquery-storage: 2.40.0</summary> ## [2.40.0](google-cloud-bigquery-storage-v2.39.0...google-cloud-bigquery-storage-v2.40.0) (2026-08-06) ### Features * delegate ReadRowsPage.to_arrow to pandas_gbq.arrow ([#17938](#17938)) ([aedc66f](aedc66f)) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) </details> <details><summary>google-cloud-binary-authorization: 1.19.1</summary> ## [1.19.1](google-cloud-binary-authorization-v1.19.0...google-cloud-binary-authorization-v1.19.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-build: 3.38.1</summary> ## [3.38.1](google-cloud-build-v3.38.0...google-cloud-build-v3.38.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-commerceproducer: 0.1.1</summary> ## [0.1.1](google-cloud-commerceproducer-v0.1.0...google-cloud-commerceproducer-v0.1.1) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-containeranalysis: 2.22.1</summary> ## [2.22.1](google-cloud-containeranalysis-v2.22.0...google-cloud-containeranalysis-v2.22.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-core: 2.6.1</summary> ## [2.6.1](google-cloud-core-v2.6.0...google-cloud-core-v2.6.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-documentai-toolbox: 0.17.2</summary> ## [0.17.2](google-cloud-documentai-toolbox-v0.17.1...google-cloud-documentai-toolbox-v0.17.2) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-filestore: 1.17.1</summary> ## [1.17.1](google-cloud-filestore-v1.17.0...google-cloud-filestore-v1.17.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-firestore: 2.28.1</summary> ## [2.28.1](google-cloud-firestore-v2.28.0...google-cloud-firestore-v2.28.1) (2026-08-06) ### Bug Fixes * **firestore:** BulkWriter pop from an empty deque ([#17490](#17490)) ([8e826f0](8e826f0)) * **firestore:** preserve async limit_to_last ordering ([#17879](#17879)) ([caf2fdb](caf2fdb)) </details> <details><summary>google-cloud-gke-hub: 1.25.1</summary> ## [1.25.1](google-cloud-gke-hub-v1.25.0...google-cloud-gke-hub-v1.25.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-iam: 2.24.1</summary> ## [2.24.1](google-cloud-iam-v2.24.0...google-cloud-iam-v2.24.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-logging: 3.16.2</summary> ## [3.16.2](google-cloud-logging-v3.16.1...google-cloud-logging-v3.16.2) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-memorystore: 0.5.4</summary> ## [0.5.4](google-cloud-memorystore-v0.5.3...google-cloud-memorystore-v0.5.4) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-ndb: 2.5.1</summary> ## [2.5.1](google-cloud-ndb-v2.5.0...google-cloud-ndb-v2.5.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-network-connectivity: 2.17.0</summary> ## [2.17.0](google-cloud-network-connectivity-v2.16.0...google-cloud-network-connectivity-v2.17.0) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-network-services: 0.10.2</summary> ## [0.10.2](google-cloud-network-services-v0.10.1...google-cloud-network-services-v0.10.2) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) * update googleapis and regenerate ([#17933](#17933)) ([f7a23a0](f7a23a0)) </details> <details><summary>google-cloud-pubsub: 2.39.1</summary> ## [2.39.1](google-cloud-pubsub-v2.39.0...google-cloud-pubsub-v2.39.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) </details> <details><summary>google-cloud-quotas: 0.6.2</summary> ## [0.6.2](google-cloud-quotas-v0.6.1...google-cloud-quotas-v0.6.2) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-redis-cluster: 0.5.2</summary> ## [0.5.2](google-cloud-redis-cluster-v0.5.1...google-cloud-redis-cluster-v0.5.2) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-service-control: 1.21.0</summary> ## [1.21.0](google-cloud-service-control-v1.20.0...google-cloud-service-control-v1.21.0) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-spanner: 3.69.1</summary> ## [3.69.1](google-cloud-spanner-v3.69.0...google-cloud-spanner-v3.69.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * **metrics:** fix GFE and AFE metrics publishing ([#17561](#17561)) ([abf1178](abf1178)) * **spanner:** escape embedded backticks in dbapi escape_name ([#17810](#17810)) ([c8b0b28](c8b0b28)) * **spanner:** implement dict protocol and nested unwrapping for JsonObject ([#17915](#17915)) ([06c1f05](06c1f05)), refs [#15870](#15870) </details> <details><summary>google-cloud-storage: 3.13.1</summary> ## [3.13.1](google-cloud-storage-v3.13.0...google-cloud-storage-v3.13.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-cloud-storage-control: 1.13.0</summary> ## [1.13.0](google-cloud-storage-control-v1.12.0...google-cloud-storage-control-v1.13.0) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-support: 0.5.2</summary> ## [0.5.2](google-cloud-support-v0.5.1...google-cloud-support-v0.5.2) (2026-08-06) ### Features * update googleapis and regenerate ([#17933](#17933)) ([f7a23a0](f7a23a0)) </details> <details><summary>google-cloud-tasks: 2.24.0</summary> ## [2.24.0](google-cloud-tasks-v2.23.0...google-cloud-tasks-v2.24.0) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-cloud-testutils: 1.9.2</summary> ## [1.9.2](google-cloud-testutils-v1.9.1...google-cloud-testutils-v1.9.2) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>google-developer-knowledge: 0.1.1</summary> ## [0.1.1](google-developer-knowledge-v0.1.0...google-developer-knowledge-v0.1.1) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-maps-navconnect: 0.2.1</summary> ## [0.2.1](google-maps-navconnect-v0.2.0...google-maps-navconnect-v0.2.1) (2026-08-06) ### Features * update googleapis and regenerate ([#17893](#17893)) ([e70ab6f](e70ab6f)) </details> <details><summary>google-resumable-media: 2.10.1</summary> ## [2.10.1](google-resumable-media-v2.10.0...google-resumable-media-v2.10.1) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>googleapis-common-protos: 1.75.1</summary> ## [1.75.1](googleapis-common-protos-v1.75.0...googleapis-common-protos-v1.75.1) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>grafeas: 1.24.0</summary> ## [1.24.0](grafeas-v1.23.0...grafeas-v1.24.0) (2026-08-06) ### Features * update googleapis and regenerate ([#17933](#17933)) ([f7a23a0](f7a23a0)) </details> <details><summary>grpc-google-iam-v1: 0.14.5</summary> ## [0.14.5](grpc-google-iam-v1-v0.14.4...grpc-google-iam-v1-v0.14.5) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>proto-plus: 1.28.3</summary> ## [1.28.3](proto-plus-v1.28.2...proto-plus-v1.28.3) (2026-08-06) ### Bug Fixes * bump grpcio to 1.59.0; require Python 3.10+ ([#17351](#17351)) ([a53487a](a53487a)) * **proto-plus:** add context to TypeErrors during message manipulation ([#17682](#17682)) ([08f21a6](08f21a6)) * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> <details><summary>sqlalchemy-bigquery: 1.17.2</summary> ## [1.17.2](sqlalchemy-bigquery-v1.17.1...sqlalchemy-bigquery-v1.17.2) (2026-08-06) ### Bug Fixes * require Protobuf 6.33.5+ ([#17743](#17743)) ([d267342](d267342)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Overview
Updates
setup_request_idingoogle.api_core.gapic_v1.requeststo use a truthiness check (if not getattr(...)) instead of an identity check (if getattr(...) is None:) for non-protobuf objects whenis_proto3_optional=True.Why is this change necessary?
proto-plusmessages: In generated Google Cloud Python libraries, request objects are primarily instances ofproto.Message(from theproto-pluslibrary). When an optional string field (is_proto3_optional=True) is unset,getattr(request, "request_id", None)returns the protobuf default string value:""(empty string), notNone.getattr(...) is Nonealways evaluates toFalse: Because"" is NoneisFalse,setup_request_idsilently failed to auto-populate UUIDs on all unsetproto-plusmessages across generated client libraries.MockRequestclass where missing attributes returnNone, masking real-worldproto.Messagebehavior.if not getattr(...)ensures unset string fields (not ""True) are correctly populated with UUID4 tokens, restoring 100% test pass rates in downstream integration suites (showcase_v1beta1).Summary of Changes
google/api_core/gapic_v1/requests.py: Changedif getattr(request, field_name, None) is None:toif not getattr(request, field_name, None):in theexcept (AttributeError, ValueError):block foris_proto3_optional=True.tests/unit/gapic/test_requests.py: Removed test assertions expecting explicit empty strings ("") to be preserved without auto-population.