google-api-core 2.32.0__tar.gz → 2.34.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {google_api_core-2.32.0 → google_api_core-2.34.0}/PKG-INFO +6 -9
  2. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/__init__.py +1 -2
  3. google_api_core-2.34.0/google/api_core/_feature_gating_helpers.py +139 -0
  4. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/_python_package_support.py +4 -6
  5. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/_python_version_support.py +3 -4
  6. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/bidi.py +1 -1
  7. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/bidi_async.py +1 -3
  8. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/client_logging.py +1 -2
  9. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/datetime_helpers.py +0 -1
  10. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/future/_helpers.py +0 -1
  11. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/future/async_future.py +2 -4
  12. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/future/polling.py +1 -2
  13. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/__init__.py +3 -1
  14. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/client_info.py +0 -1
  15. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/config.py +1 -4
  16. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/method.py +72 -11
  17. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/method_async.py +5 -3
  18. google_api_core-2.34.0/google/api_core/gapic_v1/requests.py +100 -0
  19. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/grpc_helpers.py +16 -7
  20. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/grpc_helpers_async.py +0 -1
  21. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operation.py +3 -3
  22. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operation_async.py +3 -3
  23. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/abstract_operations_base_client.py +2 -3
  24. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/abstract_operations_client.py +8 -7
  25. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/operations_async_client.py +3 -2
  26. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/operations_client.py +4 -4
  27. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/operations_rest_client_async.py +5 -4
  28. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/pagers.py +1 -0
  29. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/pagers_async.py +2 -1
  30. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/transports/__init__.py +1 -1
  31. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/transports/base.py +7 -5
  32. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/transports/rest.py +13 -9
  33. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/transports/rest_asyncio.py +31 -15
  34. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/path_template.py +2 -3
  35. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/protobuf_helpers.py +1 -4
  36. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/rest_helpers.py +68 -0
  37. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/rest_streaming.py +2 -1
  38. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/rest_streaming_async.py +1 -0
  39. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry/__init__.py +19 -16
  40. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry/retry_base.py +2 -3
  41. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry/retry_streaming.py +15 -14
  42. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry/retry_streaming_async.py +17 -17
  43. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry/retry_unary.py +10 -9
  44. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry/retry_unary_async.py +12 -11
  45. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/retry_async.py +10 -7
  46. google_api_core-2.34.0/google/api_core/universe.py +199 -0
  47. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/version.py +1 -1
  48. {google_api_core-2.32.0 → google_api_core-2.34.0}/google_api_core.egg-info/PKG-INFO +6 -9
  49. {google_api_core-2.32.0 → google_api_core-2.34.0}/google_api_core.egg-info/SOURCES.txt +5 -0
  50. google_api_core-2.34.0/google_api_core.egg-info/requires.txt +17 -0
  51. {google_api_core-2.32.0 → google_api_core-2.34.0}/pyproject.toml +5 -8
  52. {google_api_core-2.32.0 → google_api_core-2.34.0}/setup.py +0 -1
  53. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/gapic/test_config_async.py +0 -1
  54. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/gapic/test_method_async.py +8 -6
  55. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/operations_v1/test_operations_async_client.py +3 -4
  56. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/retry/test_retry_streaming_async.py +3 -2
  57. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/retry/test_retry_unary_async.py +1 -2
  58. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/test_bidi_async.py +1 -3
  59. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/test_grpc_helpers_async.py +4 -3
  60. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/test_operation_async.py +3 -6
  61. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/test_rest_streaming_async.py +5 -8
  62. google_api_core-2.34.0/tests/conftest.py +31 -0
  63. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/helpers.py +2 -4
  64. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/gapic/test_config.py +0 -1
  65. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/gapic/test_method.py +99 -3
  66. google_api_core-2.34.0/tests/unit/gapic/test_requests.py +152 -0
  67. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/operations_v1/test_operations_client.py +52 -4
  68. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/operations_v1/test_operations_rest_client.py +19 -10
  69. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/retry/test_retry_base.py +6 -11
  70. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/retry/test_retry_imports.py +5 -3
  71. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/retry/test_retry_streaming.py +3 -3
  72. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/retry/test_retry_unary.py +3 -3
  73. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_bidi.py +1 -2
  74. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_client_options.py +3 -1
  75. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_datetime_helpers.py +1 -2
  76. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_extended_operation.py +1 -3
  77. google_api_core-2.34.0/tests/unit/test_feature_gating_helpers.py +224 -0
  78. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_grpc_helpers.py +3 -2
  79. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_iam.py +3 -2
  80. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_operation.py +3 -6
  81. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_path_template.py +2 -1
  82. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_protobuf_helpers.py +13 -12
  83. google_api_core-2.34.0/tests/unit/test_rest_helpers.py +278 -0
  84. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_rest_streaming.py +2 -4
  85. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_timeout.py +2 -1
  86. google_api_core-2.34.0/tests/unit/test_universe.py +261 -0
  87. google_api_core-2.32.0/google/api_core/universe.py +0 -82
  88. google_api_core-2.32.0/google_api_core.egg-info/requires.txt +0 -24
  89. google_api_core-2.32.0/tests/unit/test_rest_helpers.py +0 -94
  90. google_api_core-2.32.0/tests/unit/test_universe.py +0 -63
  91. {google_api_core-2.32.0 → google_api_core-2.34.0}/LICENSE +0 -0
  92. {google_api_core-2.32.0 → google_api_core-2.34.0}/MANIFEST.in +0 -0
  93. {google_api_core-2.32.0 → google_api_core-2.34.0}/README.rst +0 -0
  94. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/_rest_streaming_base.py +3 -3
  95. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/bidi_base.py +0 -0
  96. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/client_info.py +0 -0
  97. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/client_options.py +1 -1
  98. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/exceptions.py +0 -0
  99. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/extended_operation.py +0 -0
  100. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/future/__init__.py +0 -0
  101. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/future/base.py +0 -0
  102. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/config_async.py +0 -0
  103. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/gapic_v1/routing_header.py +0 -0
  104. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/general_helpers.py +0 -0
  105. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/iam.py +0 -0
  106. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/__init__.py +3 -3
  107. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/operations_client_config.py +0 -0
  108. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/operations_v1/pagers_base.py +0 -0
  109. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/page_iterator.py +0 -0
  110. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/page_iterator_async.py +0 -0
  111. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/py.typed +0 -0
  112. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/timeout.py +0 -0
  113. {google_api_core-2.32.0 → google_api_core-2.34.0}/google/api_core/version_header.py +0 -0
  114. {google_api_core-2.32.0 → google_api_core-2.34.0}/google_api_core.egg-info/dependency_links.txt +0 -0
  115. {google_api_core-2.32.0 → google_api_core-2.34.0}/google_api_core.egg-info/top_level.txt +0 -0
  116. {google_api_core-2.32.0 → google_api_core-2.34.0}/setup.cfg +0 -0
  117. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/__init__.py +0 -0
  118. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/__init__.py +0 -0
  119. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/future/__init__.py +0 -0
  120. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/future/test_async_future.py +0 -0
  121. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/operations_v1/__init__.py +0 -0
  122. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/retry/__init__.py +0 -0
  123. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/asyncio/test_page_iterator_async.py +0 -0
  124. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/__init__.py +0 -0
  125. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/future/__init__.py +0 -0
  126. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/future/test__helpers.py +0 -0
  127. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/future/test_polling.py +0 -0
  128. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/gapic/test_client_info.py +0 -0
  129. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/gapic/test_routing_header.py +0 -0
  130. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/operations_v1/__init__.py +0 -0
  131. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/retry/__init__.py +0 -0
  132. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_client_info.py +0 -0
  133. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_client_logging.py +2 -2
  134. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_exceptions.py +0 -0
  135. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_packaging.py +0 -0
  136. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_page_iterator.py +0 -0
  137. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_python_package_support.py +4 -4
  138. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_python_version_support.py +0 -0
  139. {google_api_core-2.32.0 → google_api_core-2.34.0}/tests/unit/test_version_header.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-api-core
3
- Version: 2.32.0
3
+ Version: 2.34.0
4
4
  Summary: Google API client core library
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache 2.0
@@ -22,21 +22,18 @@ Classifier: Topic :: Internet
22
22
  Requires-Python: >=3.10
23
23
  Description-Content-Type: text/x-rst
24
24
  License-File: LICENSE
25
- Requires-Dist: googleapis-common-protos<2.0.0,>=1.63.2
26
- Requires-Dist: protobuf<8.0.0,>=5.29.6
27
- Requires-Dist: proto-plus<2.0.0,>=1.24.0
28
- Requires-Dist: proto-plus<2.0.0,>=1.25.0; python_version >= "3.13"
25
+ Requires-Dist: googleapis-common-protos<2.0.0,>=1.69.2
26
+ Requires-Dist: protobuf<8.0.0,>=6.33.5
27
+ Requires-Dist: proto-plus<2.0.0,>=1.26.1
29
28
  Requires-Dist: google-auth<3.0.0,>=2.14.1
30
29
  Requires-Dist: requests<3.0.0,>=2.33.0
31
30
  Provides-Extra: async-rest
32
31
  Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.14.1; extra == "async-rest"
33
32
  Requires-Dist: aiohttp>=3.13.4; extra == "async-rest"
34
33
  Provides-Extra: grpc
35
- Requires-Dist: grpcio<2.0.0,>=1.41.0; extra == "grpc"
36
- Requires-Dist: grpcio<2.0.0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
34
+ Requires-Dist: grpcio<2.0.0,>=1.59.0; extra == "grpc"
37
35
  Requires-Dist: grpcio<2.0.0,>=1.75.1; python_version >= "3.14" and extra == "grpc"
38
- Requires-Dist: grpcio-status<2.0.0,>=1.41.0; extra == "grpc"
39
- Requires-Dist: grpcio-status<2.0.0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
36
+ Requires-Dist: grpcio-status<2.0.0,>=1.59.0; extra == "grpc"
40
37
  Requires-Dist: grpcio-status<2.0.0,>=1.75.1; python_version >= "3.14" and extra == "grpc"
41
38
  Dynamic: license-file
42
39
 
@@ -17,8 +17,7 @@
17
17
  This package contains common code and utilities used by Google client libraries.
18
18
  """
19
19
 
20
- from google.api_core import _python_package_support
21
- from google.api_core import _python_version_support
20
+ from google.api_core import _python_package_support, _python_version_support
22
21
  from google.api_core import version as api_core_version
23
22
 
24
23
  __version__ = api_core_version.__version__
@@ -0,0 +1,139 @@
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
+ """Observability environment variable and client options resolution helpers."""
18
+
19
+ import os
20
+ import warnings
21
+ from typing import Any
22
+
23
+ # We import ClientOptions for type hinting
24
+ from google.api_core.client_options import ClientOptions
25
+
26
+ # Allowed truthy and falsy patterns for environment variables
27
+ _TRUTHY_VALUES = ("y", "yes", "t", "true", "on", "1")
28
+ _FALSY_VALUES = ("n", "no", "f", "false", "off", "0")
29
+
30
+
31
+ class FeatureGatingError(ValueError):
32
+ """Raised when feature gating resolution fails or is misconfigured."""
33
+
34
+ pass
35
+
36
+
37
+ def _strtobool(val: str) -> bool | None:
38
+ """Convert a string representation of truth to a boolean."""
39
+ clean_val = val.lower().strip()
40
+ if not clean_val:
41
+ return None
42
+ if clean_val in _TRUTHY_VALUES:
43
+ return True
44
+ if clean_val in _FALSY_VALUES:
45
+ return False
46
+ raise ValueError(f"Invalid truth value: {val!r}")
47
+
48
+
49
+ def _get_env_bool(name: str) -> bool | None:
50
+ """Retrieve the boolean value of an environment variable."""
51
+ val = os.getenv(name)
52
+ if val is None:
53
+ return None
54
+ try:
55
+ return _strtobool(val)
56
+ except ValueError as e:
57
+ warnings.warn(f"Ignored invalid value for {name}: {e}", RuntimeWarning)
58
+ return None
59
+
60
+
61
+ def _has_feature_key(
62
+ *, configuration: ClientOptions | dict[str, Any] | None, feature_key: str
63
+ ) -> bool:
64
+ """Checks if a specific feature key is present and not None in configuration."""
65
+ if configuration is None:
66
+ return False
67
+
68
+ if feature_key.startswith("__"):
69
+ return False
70
+
71
+ if isinstance(configuration, dict):
72
+ return configuration.get(feature_key) is not None
73
+
74
+ return getattr(configuration, feature_key, None) is not None
75
+
76
+
77
+ def resolve_feature_flags(
78
+ *,
79
+ env_var: str,
80
+ feature_key: str,
81
+ configuration: ClientOptions | dict[str, Any] | None = None,
82
+ ) -> bool:
83
+ """Determines if a feature is enabled based on environment variables and configuration.
84
+
85
+ Behavior depends on whether the `env_var` name contains "EXPERIMENTAL":
86
+
87
+ - **Experimental Path** (env_var contains "EXPERIMENTAL"):
88
+ Strict control. Requires the environment variable to be explicitly 'true'.
89
+ If a programmatic feature key is passed but the environment variable is not 'true',
90
+ raises FeatureGatingError (Fail Fast).
91
+
92
+ - **GA Path** (env_var does not contain "EXPERIMENTAL"):
93
+ Standard precedence. Enabled if a programmatic feature key is passed,
94
+ otherwise falls back to the environment variable value.
95
+
96
+ Args:
97
+ env_var: The name of the environment variable controlling this feature.
98
+ feature_key: The key in configuration/attributes for the programmatic configuration.
99
+ configuration: Optional. A dictionary or object containing client configuration.
100
+
101
+ Returns:
102
+ bool: True if the feature is resolved to enabled, False otherwise.
103
+
104
+ Raises:
105
+ FeatureGatingError: If a feature key is provided for an experimental feature without enabling the experimental environment variable.
106
+ """
107
+
108
+ # Check for programmatic feature configuration
109
+ has_feature_key = _has_feature_key(
110
+ configuration=configuration, feature_key=feature_key
111
+ )
112
+
113
+ # Read environment variable
114
+ env_var_setting = _get_env_bool(env_var)
115
+
116
+ # EXPERIMENTAL PATH:
117
+ # Resolution Hierarchy:
118
+ # 1. EXPERIMENTAL Environment Variable
119
+ # 2. Fail Fast if Feature Key present but EXPERIMENTAL Environment Variable is not enabled
120
+ if "EXPERIMENTAL" in env_var:
121
+ # Fail Fast if feature key present but experimental environment variable is not enabled
122
+ if env_var_setting is not True and has_feature_key:
123
+ raise FeatureGatingError(
124
+ f"Experimental feature requires {env_var} to be set to 'true' to use programmatic configuration."
125
+ )
126
+
127
+ return bool(env_var_setting)
128
+
129
+ # GENERAL AVAILABILITY PATH:
130
+ # Resolution Hierarchy:
131
+ # 1. Programmatic Configuration (Feature Key)
132
+ # 2. Environment Variable
133
+
134
+ # Check Programmatic Configuration
135
+ if has_feature_key:
136
+ return True
137
+
138
+ # Check Environment Variable
139
+ return bool(env_var_setting)
@@ -15,17 +15,15 @@
15
15
  """Code to check versions of dependencies used by Google Cloud Client Libraries."""
16
16
 
17
17
  import warnings
18
- from typing import Optional, Tuple
19
-
20
18
  from collections import namedtuple
19
+ from importlib import metadata
20
+ from typing import Optional, Tuple
21
21
 
22
22
  from ._python_version_support import (
23
23
  _flatten_message,
24
24
  _get_distribution_and_import_packages,
25
25
  )
26
26
 
27
- from importlib import metadata
28
-
29
27
  ParsedVersion = Tuple[int, ...]
30
28
 
31
29
  # Here we list all the packages for which we want to issue warnings
@@ -37,7 +35,7 @@ DependencyConstraint = namedtuple(
37
35
  _PACKAGE_DEPENDENCY_WARNINGS = [
38
36
  DependencyConstraint(
39
37
  "google.protobuf",
40
- minimum_fully_supported_version="4.25.8",
38
+ minimum_fully_supported_version="6.33.5",
41
39
  recommended_version="6.x",
42
40
  )
43
41
  ]
@@ -52,7 +50,7 @@ UNKNOWN_VERSION_STRING = "--"
52
50
  def parse_version_to_tuple(version_string: str) -> ParsedVersion:
53
51
  """Safely converts a semantic version string to a comparable tuple of integers.
54
52
 
55
- Example: "4.25.8" -> (4, 25, 8)
53
+ Example: "6.33.5" -> (6, 33, 5)
56
54
  Ignores non-numeric parts and handles common version formats.
57
55
 
58
56
  Args:
@@ -17,13 +17,12 @@
17
17
  import datetime
18
18
  import enum
19
19
  import functools
20
- from importlib import metadata
21
20
  import logging
22
- import warnings
23
21
  import sys
24
22
  import textwrap
25
- from typing import Any, List, NamedTuple, Optional, Dict, Tuple
26
-
23
+ import warnings
24
+ from importlib import metadata
25
+ from typing import Any, Dict, List, NamedTuple, Optional, Tuple
27
26
 
28
27
  _LOGGER = logging.getLogger(__name__)
29
28
 
@@ -113,7 +113,7 @@ class _RequestQueueGenerator(object):
113
113
  except queue_module.Empty:
114
114
  if not self._is_active():
115
115
  _LOGGER.debug(
116
- "Empty queue and inactive call, exiting request " "generator."
116
+ "Empty queue and inactive call, exiting request generator."
117
117
  )
118
118
  return
119
119
  else:
@@ -18,14 +18,12 @@ import asyncio
18
18
  import logging
19
19
  from typing import Callable, Optional, Union
20
20
 
21
+ from google.protobuf.message import Message as ProtobufMessage
21
22
  from grpc import aio
22
23
 
23
24
  from google.api_core import exceptions
24
25
  from google.api_core.bidi_base import BidiRpcBase
25
26
 
26
- from google.protobuf.message import Message as ProtobufMessage
27
-
28
-
29
27
  _LOGGER = logging.getLogger(__name__)
30
28
 
31
29
 
@@ -1,7 +1,6 @@
1
- import logging
2
1
  import json
2
+ import logging
3
3
  import os
4
-
5
4
  from typing import List, Optional
6
5
 
7
6
  _LOGGING_INITIALIZED = False
@@ -20,7 +20,6 @@ import re
20
20
 
21
21
  from google.protobuf import timestamp_pb2
22
22
 
23
-
24
23
  _UTC_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
25
24
  _RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
26
25
  _RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S"
@@ -17,7 +17,6 @@
17
17
  import logging
18
18
  import threading
19
19
 
20
-
21
20
  _LOGGER = logging.getLogger(__name__)
22
21
 
23
22
 
@@ -16,9 +16,7 @@
16
16
 
17
17
  import asyncio
18
18
 
19
- from google.api_core import exceptions
20
- from google.api_core import retry
21
- from google.api_core import retry_async
19
+ from google.api_core import exceptions, retry, retry_async
22
20
  from google.api_core.future import base
23
21
 
24
22
 
@@ -101,7 +99,7 @@ class AsyncFuture(base.Future):
101
99
  await retry_(self._done_or_raise)()
102
100
  except exceptions.RetryError:
103
101
  raise asyncio.TimeoutError(
104
- "Operation did not complete within the designated " "timeout."
102
+ "Operation did not complete within the designated timeout."
105
103
  )
106
104
 
107
105
  async def result(self, timeout=None):
@@ -19,8 +19,7 @@ import concurrent.futures
19
19
 
20
20
  from google.api_core import exceptions
21
21
  from google.api_core import retry as retries
22
- from google.api_core.future import _helpers
23
- from google.api_core.future import base
22
+ from google.api_core.future import _helpers, base
24
23
 
25
24
 
26
25
  class _OperationNotComplete(Exception):
@@ -25,9 +25,10 @@ _has_grpc = importlib.util.find_spec("grpc") is not None
25
25
  # Older Python versions safely ignore this variable.
26
26
  __lazy_modules__: Set[str] = {
27
27
  "google.api_core.gapic_v1.client_info",
28
+ "google.api_core.gapic_v1.requests",
28
29
  "google.api_core.gapic_v1.routing_header",
29
30
  }
30
- __all__ = ["client_info", "routing_header"]
31
+ __all__ = ["client_info", "requests", "routing_header"]
31
32
 
32
33
  if _has_grpc:
33
34
  __lazy_modules__.update(
@@ -41,6 +42,7 @@ if _has_grpc:
41
42
 
42
43
  from google.api_core.gapic_v1 import ( # noqa: E402
43
44
  client_info,
45
+ requests,
44
46
  routing_header,
45
47
  )
46
48
 
@@ -20,7 +20,6 @@ such as the library and Python version, to API services.
20
20
 
21
21
  from google.api_core import client_info
22
22
 
23
-
24
23
  METRICS_METADATA_KEY = "x-goog-api-client"
25
24
 
26
25
 
@@ -22,10 +22,7 @@ import collections
22
22
 
23
23
  import grpc
24
24
 
25
- from google.api_core import exceptions
26
- from google.api_core import retry
27
- from google.api_core import timeout
28
-
25
+ from google.api_core import exceptions, retry, timeout
29
26
 
30
27
  _MILLIS_PER_SECOND = 1000.0
31
28
 
@@ -20,6 +20,7 @@ compression, pagination, and long-running operations to gRPC methods.
20
20
 
21
21
  import enum
22
22
  import functools
23
+ from typing import List, Tuple
23
24
 
24
25
  from google.api_core import grpc_helpers
25
26
  from google.api_core.gapic_v1 import client_info
@@ -57,6 +58,52 @@ def _apply_decorators(func, decorators):
57
58
  return func
58
59
 
59
60
 
61
+ def _deduplicate_metadata_tokens(*headers: str | None) -> str:
62
+ """
63
+ Given one or more metadata payload strings, create a combined
64
+ string with deduplicated tokens, while preserving token order.
65
+
66
+ Inputs are expected to contain a set of metadata tokens separated by spaces
67
+ Example: `gl-python/3.14.0 grpc/1.76.0 gax/2.29.0 gapic/3.8.0 pb/6.33.4`
68
+
69
+ Args:
70
+ *headers: one or more metadata payload strings
71
+
72
+ Returns:
73
+ a single combined payload string
74
+ """
75
+ # Split all non-empty headers into individual tokens
76
+ token_list = " ".join(filter(None, headers)).split()
77
+ # Deduplicate while preserving order
78
+ return " ".join(dict.fromkeys(token_list))
79
+
80
+
81
+ def _extract_metrics_header(metadata) -> Tuple[str, List[Tuple[str, str]]]:
82
+ """Extract x-google-api-client header from metadata list.
83
+
84
+ Args:
85
+ metadata (Sequence[Tuple[str, str]]): The metadata to extract from.
86
+
87
+ Returns:
88
+ A tuple containing:
89
+ - a string representing the header value.
90
+ - A sequence of remaining metadata tuples.
91
+ """
92
+ if not metadata:
93
+ return "", []
94
+
95
+ key_to_find = client_info.METRICS_METADATA_KEY
96
+
97
+ metric_str = _deduplicate_metadata_tokens(
98
+ " ".join([v for k, v in metadata if k == key_to_find])
99
+ )
100
+ if not metric_str:
101
+ return "", list(metadata)
102
+
103
+ arbitrary_metadata = [item for item in metadata if item[0] != key_to_find]
104
+ return metric_str, arbitrary_metadata
105
+
106
+
60
107
  class _GapicCallable(object):
61
108
  """Callable that applies retry, timeout, and metadata logic.
62
109
 
@@ -90,7 +137,16 @@ class _GapicCallable(object):
90
137
  self._retry = retry
91
138
  self._timeout = timeout
92
139
  self._compression = compression
93
- self._metadata = metadata
140
+ # Pre-extract the x-goog-api-client header from the initialized metadata.
141
+ self._x_goog_api_client, remaining = _extract_metrics_header(metadata)
142
+ self._static_metadata = tuple(remaining)
143
+ if self._x_goog_api_client:
144
+ self._default_metadata = (
145
+ (client_info.METRICS_METADATA_KEY, self._x_goog_api_client),
146
+ *self._static_metadata,
147
+ )
148
+ else:
149
+ self._default_metadata = self._static_metadata
94
150
 
95
151
  def __call__(
96
152
  self, *args, timeout=DEFAULT, retry=DEFAULT, compression=DEFAULT, **kwargs
@@ -112,16 +168,21 @@ class _GapicCallable(object):
112
168
  # Apply all applicable decorators.
113
169
  wrapped_func = _apply_decorators(self._target, [retry, timeout])
114
170
 
115
- # Add the user agent metadata to the call.
116
- if self._metadata is not None:
117
- metadata = kwargs.get("metadata", [])
118
- # Due to the nature of invocation, None should be treated the same
119
- # as not specified.
120
- if metadata is None:
121
- metadata = []
122
- metadata = list(metadata)
123
- metadata.extend(self._metadata)
124
- kwargs["metadata"] = metadata
171
+ if user_metadata := kwargs.get("metadata"):
172
+ # Add the user agent metadata to the call.
173
+ final_metadata = list(self._static_metadata)
174
+ user_x_goog, remaining = _extract_metrics_header(user_metadata)
175
+
176
+ merged_header = _deduplicate_metadata_tokens(
177
+ self._x_goog_api_client, user_x_goog
178
+ )
179
+ if merged_header:
180
+ final_metadata.append((client_info.METRICS_METADATA_KEY, merged_header))
181
+ final_metadata.extend(remaining)
182
+ kwargs["metadata"] = final_metadata
183
+ elif self._default_metadata:
184
+ kwargs["metadata"] = self._default_metadata
185
+
125
186
  if self._compression is not None:
126
187
  kwargs["compression"] = compression
127
188
 
@@ -21,9 +21,11 @@ import functools
21
21
 
22
22
  from google.api_core import grpc_helpers_async
23
23
  from google.api_core.gapic_v1 import client_info
24
- from google.api_core.gapic_v1.method import _GapicCallable
25
- from google.api_core.gapic_v1.method import DEFAULT # noqa: F401
26
- from google.api_core.gapic_v1.method import USE_DEFAULT_METADATA # noqa: F401
24
+ from google.api_core.gapic_v1.method import ( # noqa: F401
25
+ DEFAULT,
26
+ USE_DEFAULT_METADATA,
27
+ _GapicCallable,
28
+ )
27
29
 
28
30
  _DEFAULT_ASYNC_TRANSPORT_KIND = "grpc_asyncio"
29
31
 
@@ -0,0 +1,100 @@
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 preparing and structuring API requests.
18
+
19
+ This module provides utilities to preprocess request parameters and objects
20
+ before invoking API methods, such as automatically generating request IDs
21
+ if they are not already set.
22
+ """
23
+
24
+ import uuid
25
+ from typing import TYPE_CHECKING, Union
26
+
27
+ import google.protobuf.message
28
+
29
+ if TYPE_CHECKING: # pragma: NO COVER
30
+ import proto # type: ignore[import-untyped]
31
+
32
+
33
+ def setup_request_id(
34
+ request: Union[google.protobuf.message.Message, "proto.Message", dict, None],
35
+ field_name: str,
36
+ is_proto3_optional: bool,
37
+ ) -> None:
38
+ """Populate a UUID4 field in the request if it is not already set.
39
+
40
+ This helper is used to ensure request idempotency by automatically
41
+ generating a unique identifier (such as `request_id`) for requests
42
+ that support it. If a request is retried, the same identifier can be
43
+ sent on subsequent retries, allowing the server to recognize the retried
44
+ request and prevent duplicate processing (e.g., creating duplicate
45
+ resources).
46
+
47
+ Args:
48
+ request (Union[google.protobuf.message.Message, proto.Message, dict, None]): The
49
+ request object or dictionary.
50
+ field_name (str): The name of the field to populate (e.g., "request_id").
51
+ is_proto3_optional (bool): Whether the field supports explicit presence
52
+ (defined with `optional` in proto3 syntax). When True, empty strings ("")
53
+ are preserved as explicit user input per AIP-4235, and UUID auto-population
54
+ occurs only if the field is unset. When False, any empty or falsy value is
55
+ populated with a UUID.
56
+ """
57
+ if request is None:
58
+ return
59
+
60
+ # Evaluate whether the field is considered "unset" and needs auto-population.
61
+ #
62
+ # According to AIP-4235, optional request ID fields must be populated
63
+ # if and only if they have explicit presence (`is_proto3_optional=True`)
64
+ # and were not set by the user (i.e. unset). Explicitly provided empty
65
+ # strings ('') must be preserved when `is_proto3_optional=True`.
66
+ should_populate = False
67
+ if isinstance(request, dict):
68
+ if is_proto3_optional:
69
+ # Case 1a: Dictionary request with explicit presence (`is_proto3_optional=True`).
70
+ # Per AIP-4235, auto-populate only if the key is completely missing from
71
+ # the dictionary or its value is explicitly set to None.
72
+ # An explicit empty string ('') must NOT be overwritten.
73
+ should_populate = field_name not in request or request[field_name] is None
74
+ else:
75
+ # Case 1b: Dictionary request without explicit presence (`is_proto3_optional=False`).
76
+ # Auto-populate if the key is missing, None, or falsy (e.g., empty string '').
77
+ should_populate = not request.get(field_name)
78
+ else:
79
+ if is_proto3_optional:
80
+ # Case 2a: Proto request with explicit presence (`is_proto3_optional=True`)
81
+ # (proto-plus wrapper or pure protobuf message).
82
+ # Extract the protobuf from proto-plus if wrapped.
83
+ pure_pb: google.protobuf.message.Message = getattr(request, "_pb", request)
84
+ try:
85
+ should_populate = not pure_pb.HasField(field_name)
86
+ except (AttributeError, ValueError):
87
+ # Fall back if `HasField` fails or is unsupported.
88
+ should_populate = getattr(pure_pb, field_name, None) is None
89
+ else:
90
+ # Case 2b: Proto request without explicit presence (`is_proto3_optional=False`).
91
+ # Auto-populate if the field value is falsy (None or empty string '').
92
+ should_populate = not bool(getattr(request, field_name, False))
93
+
94
+ # If the field was found to be empty, set random id
95
+ if should_populate:
96
+ generated_id = str(uuid.uuid4())
97
+ if isinstance(request, dict):
98
+ request[field_name] = generated_id
99
+ else:
100
+ setattr(request, field_name, generated_id)
@@ -13,10 +13,11 @@
13
13
  # limitations under the License.
14
14
 
15
15
  """Helpers for :mod:`grpc`."""
16
+
16
17
  import collections
17
18
  import functools
18
- from typing import Generic, Iterator, Optional, TypeVar
19
19
  import warnings
20
+ from typing import Generic, Iterator, Optional, TypeVar
20
21
 
21
22
  import google.auth
22
23
  import google.auth.credentials
@@ -27,7 +28,6 @@ import grpc
27
28
 
28
29
  from google.api_core import exceptions, general_helpers
29
30
 
30
-
31
31
  # The list of gRPC Callable interfaces that return iterators.
32
32
  _STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)
33
33
 
@@ -254,11 +254,20 @@ def _create_composite_credentials(
254
254
  request = google.auth.transport.requests.Request()
255
255
 
256
256
  # Create the metadata plugin for inserting the authorization header.
257
- metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
258
- credentials,
259
- request,
260
- default_host=default_host,
261
- )
257
+ try:
258
+ metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
259
+ credentials,
260
+ request,
261
+ default_host=default_host,
262
+ suppress_metrics_header=True,
263
+ )
264
+ except TypeError:
265
+ # Support older versions of google-auth that do not accept suppress_metrics_header
266
+ metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
267
+ credentials,
268
+ request,
269
+ default_host=default_host,
270
+ )
262
271
 
263
272
  # Create a set of grpc.CallCredentials using the metadata plugin.
264
273
  google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
@@ -21,7 +21,6 @@ functions. This module is implementing the same surface with AsyncIO semantics.
21
21
  import asyncio
22
22
  import functools
23
23
  import warnings
24
-
25
24
  from typing import AsyncGenerator, Generic, Iterator, Optional, TypeVar
26
25
 
27
26
  import grpc
@@ -39,13 +39,13 @@ Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:
39
39
  import functools
40
40
  import threading
41
41
 
42
- from google.api_core import exceptions
43
- from google.api_core import protobuf_helpers
44
- from google.api_core.future import polling
45
42
  from google.longrunning import operations_pb2
46
43
  from google.protobuf import json_format
47
44
  from google.rpc import code_pb2
48
45
 
46
+ from google.api_core import exceptions, protobuf_helpers
47
+ from google.api_core.future import polling
48
+
49
49
 
50
50
  class Operation(polling.PollingFuture):
51
51
  """A Future for interacting with a Google API Long-Running Operation.