google-api-core 2.24.2__tar.gz → 2.25.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 (123) hide show
  1. {google_api_core-2.24.2/google_api_core.egg-info → google_api_core-2.25.0}/PKG-INFO +8 -8
  2. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/bidi.py +2 -1
  3. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/client_info.py +6 -0
  4. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/client_info.py +1 -0
  5. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry/retry_base.py +13 -4
  6. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry/retry_streaming.py +7 -6
  7. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry/retry_streaming_async.py +8 -5
  8. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry/retry_unary.py +7 -6
  9. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry/retry_unary_async.py +7 -6
  10. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/version.py +1 -1
  11. {google_api_core-2.24.2 → google_api_core-2.25.0/google_api_core.egg-info}/PKG-INFO +8 -8
  12. {google_api_core-2.24.2 → google_api_core-2.25.0}/google_api_core.egg-info/requires.txt +7 -7
  13. {google_api_core-2.24.2 → google_api_core-2.25.0}/pyproject.toml +7 -7
  14. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/gapic/test_method_async.py +1 -0
  15. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/retry/test_retry_streaming_async.py +32 -3
  16. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/retry/test_retry_unary_async.py +26 -1
  17. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/gapic/test_method.py +1 -0
  18. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/retry/test_retry_streaming.py +32 -3
  19. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/retry/test_retry_unary.py +27 -1
  20. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_bidi.py +24 -0
  21. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_client_info.py +8 -2
  22. {google_api_core-2.24.2 → google_api_core-2.25.0}/LICENSE +0 -0
  23. {google_api_core-2.24.2 → google_api_core-2.25.0}/MANIFEST.in +0 -0
  24. {google_api_core-2.24.2 → google_api_core-2.25.0}/README.rst +0 -0
  25. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/__init__.py +0 -0
  26. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/_rest_streaming_base.py +0 -0
  27. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/client_logging.py +0 -0
  28. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/client_options.py +0 -0
  29. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/datetime_helpers.py +0 -0
  30. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/exceptions.py +0 -0
  31. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/extended_operation.py +0 -0
  32. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/future/__init__.py +0 -0
  33. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/future/_helpers.py +0 -0
  34. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/future/async_future.py +0 -0
  35. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/future/base.py +0 -0
  36. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/future/polling.py +0 -0
  37. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/__init__.py +0 -0
  38. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/config.py +0 -0
  39. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/config_async.py +0 -0
  40. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/method.py +0 -0
  41. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/method_async.py +0 -0
  42. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/gapic_v1/routing_header.py +0 -0
  43. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/general_helpers.py +0 -0
  44. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/grpc_helpers.py +0 -0
  45. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/grpc_helpers_async.py +0 -0
  46. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/iam.py +0 -0
  47. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operation.py +0 -0
  48. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operation_async.py +0 -0
  49. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/__init__.py +0 -0
  50. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/abstract_operations_base_client.py +0 -0
  51. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/abstract_operations_client.py +0 -0
  52. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/operations_async_client.py +0 -0
  53. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/operations_client.py +0 -0
  54. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/operations_client_config.py +0 -0
  55. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/operations_rest_client_async.py +0 -0
  56. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/pagers.py +0 -0
  57. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/pagers_async.py +0 -0
  58. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/pagers_base.py +0 -0
  59. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/transports/__init__.py +0 -0
  60. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/transports/base.py +0 -0
  61. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/transports/rest.py +0 -0
  62. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/operations_v1/transports/rest_asyncio.py +0 -0
  63. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/page_iterator.py +0 -0
  64. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/page_iterator_async.py +0 -0
  65. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/path_template.py +0 -0
  66. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/protobuf_helpers.py +0 -0
  67. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/py.typed +0 -0
  68. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/rest_helpers.py +0 -0
  69. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/rest_streaming.py +0 -0
  70. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/rest_streaming_async.py +0 -0
  71. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry/__init__.py +0 -0
  72. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/retry_async.py +0 -0
  73. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/timeout.py +0 -0
  74. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/universe.py +0 -0
  75. {google_api_core-2.24.2 → google_api_core-2.25.0}/google/api_core/version_header.py +0 -0
  76. {google_api_core-2.24.2 → google_api_core-2.25.0}/google_api_core.egg-info/SOURCES.txt +0 -0
  77. {google_api_core-2.24.2 → google_api_core-2.25.0}/google_api_core.egg-info/dependency_links.txt +0 -0
  78. {google_api_core-2.24.2 → google_api_core-2.25.0}/google_api_core.egg-info/top_level.txt +0 -0
  79. {google_api_core-2.24.2 → google_api_core-2.25.0}/setup.cfg +0 -0
  80. {google_api_core-2.24.2 → google_api_core-2.25.0}/setup.py +0 -0
  81. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/__init__.py +0 -0
  82. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/__init__.py +0 -0
  83. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/future/__init__.py +0 -0
  84. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/future/test_async_future.py +0 -0
  85. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/gapic/test_config_async.py +0 -0
  86. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/operations_v1/__init__.py +0 -0
  87. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/operations_v1/test_operations_async_client.py +0 -0
  88. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/retry/__init__.py +0 -0
  89. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/test_grpc_helpers_async.py +0 -0
  90. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/test_operation_async.py +0 -0
  91. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/test_page_iterator_async.py +0 -0
  92. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/asyncio/test_rest_streaming_async.py +0 -0
  93. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/helpers.py +0 -0
  94. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/__init__.py +0 -0
  95. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/future/__init__.py +0 -0
  96. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/future/test__helpers.py +0 -0
  97. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/future/test_polling.py +0 -0
  98. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/gapic/test_client_info.py +0 -0
  99. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/gapic/test_config.py +0 -0
  100. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/gapic/test_routing_header.py +0 -0
  101. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/operations_v1/__init__.py +0 -0
  102. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/operations_v1/test_operations_client.py +0 -0
  103. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/operations_v1/test_operations_rest_client.py +0 -0
  104. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/retry/__init__.py +0 -0
  105. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/retry/test_retry_base.py +0 -0
  106. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/retry/test_retry_imports.py +0 -0
  107. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_client_logging.py +0 -0
  108. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_client_options.py +0 -0
  109. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_datetime_helpers.py +0 -0
  110. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_exceptions.py +0 -0
  111. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_extended_operation.py +0 -0
  112. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_grpc_helpers.py +0 -0
  113. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_iam.py +0 -0
  114. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_operation.py +0 -0
  115. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_packaging.py +0 -0
  116. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_page_iterator.py +0 -0
  117. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_path_template.py +0 -0
  118. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_protobuf_helpers.py +0 -0
  119. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_rest_helpers.py +0 -0
  120. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_rest_streaming.py +0 -0
  121. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_timeout.py +0 -0
  122. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_universe.py +0 -0
  123. {google_api_core-2.24.2 → google_api_core-2.25.0}/tests/unit/test_version_header.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-api-core
3
- Version: 2.24.2
3
+ Version: 2.25.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
@@ -30,16 +30,16 @@ Requires-Dist: proto-plus<2.0.0,>=1.25.0; python_version >= "3.13"
30
30
  Requires-Dist: google-auth<3.0.0,>=2.14.1
31
31
  Requires-Dist: requests<3.0.0,>=2.18.0
32
32
  Provides-Extra: async-rest
33
- Requires-Dist: google-auth[aiohttp]<3.0.dev0,>=2.35.0; extra == "async-rest"
33
+ Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.35.0; extra == "async-rest"
34
34
  Provides-Extra: grpc
35
- Requires-Dist: grpcio<2.0dev,>=1.33.2; extra == "grpc"
36
- Requires-Dist: grpcio<2.0dev,>=1.49.1; python_version >= "3.11" and extra == "grpc"
37
- Requires-Dist: grpcio-status<2.0.dev0,>=1.33.2; extra == "grpc"
38
- Requires-Dist: grpcio-status<2.0.dev0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
35
+ Requires-Dist: grpcio<2.0.0,>=1.33.2; extra == "grpc"
36
+ Requires-Dist: grpcio<2.0.0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
37
+ Requires-Dist: grpcio-status<2.0.0,>=1.33.2; extra == "grpc"
38
+ Requires-Dist: grpcio-status<2.0.0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
39
39
  Provides-Extra: grpcgcp
40
- Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcgcp"
40
+ Requires-Dist: grpcio-gcp<1.0.0,>=0.2.2; extra == "grpcgcp"
41
41
  Provides-Extra: grpcio-gcp
42
- Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcio-gcp"
42
+ Requires-Dist: grpcio-gcp<1.0.0,>=0.2.2; extra == "grpcio-gcp"
43
43
 
44
44
  Core Library for Google Client Libraries
45
45
  ========================================
@@ -664,7 +664,8 @@ class BackgroundConsumer(object):
664
664
  _LOGGER.debug("waiting for recv.")
665
665
  response = self._bidi_rpc.recv()
666
666
  _LOGGER.debug("recved response.")
667
- self._on_response(response)
667
+ if self._on_response is not None:
668
+ self._on_response(response)
668
669
 
669
670
  except exceptions.GoogleAPICallError as exc:
670
671
  _LOGGER.debug(
@@ -59,6 +59,7 @@ class ClientInfo(object):
59
59
  Recommended format: ``application-or-tool-ID/major.minor.version``.
60
60
  rest_version (Optional[str]): A string with labeled versions of the
61
61
  dependencies used for REST transport.
62
+ protobuf_runtime_version (Optional[str]): The protobuf runtime version.
62
63
  """
63
64
 
64
65
  def __init__(
@@ -70,6 +71,7 @@ class ClientInfo(object):
70
71
  client_library_version=None,
71
72
  user_agent=None,
72
73
  rest_version=None,
74
+ protobuf_runtime_version=None,
73
75
  ):
74
76
  self.python_version = python_version
75
77
  self.grpc_version = grpc_version
@@ -78,6 +80,7 @@ class ClientInfo(object):
78
80
  self.client_library_version = client_library_version
79
81
  self.user_agent = user_agent
80
82
  self.rest_version = rest_version
83
+ self.protobuf_runtime_version = protobuf_runtime_version
81
84
 
82
85
  def to_user_agent(self):
83
86
  """Returns the user-agent string for this client info."""
@@ -105,4 +108,7 @@ class ClientInfo(object):
105
108
  if self.client_library_version is not None:
106
109
  ua += "gccl/{client_library_version} "
107
110
 
111
+ if self.protobuf_runtime_version is not None:
112
+ ua += "pb/{protobuf_runtime_version} "
113
+
108
114
  return ua.format(**self.__dict__).strip()
@@ -47,6 +47,7 @@ class ClientInfo(client_info.ClientInfo):
47
47
  Recommended format: ``application-or-tool-ID/major.minor.version``.
48
48
  rest_version (Optional[str]): A string with labeled versions of the
49
49
  dependencies used for REST transport.
50
+ protobuf_runtime_version (Optional[str]): The protobuf runtime version.
50
51
  """
51
52
 
52
53
  def to_grpc_metadata(self):
@@ -25,7 +25,7 @@ import random
25
25
  import time
26
26
 
27
27
  from enum import Enum
28
- from typing import Any, Callable, Optional, TYPE_CHECKING
28
+ from typing import Any, Callable, Optional, Iterator, TYPE_CHECKING
29
29
 
30
30
  import requests.exceptions
31
31
 
@@ -174,7 +174,7 @@ def build_retry_error(
174
174
  def _retry_error_helper(
175
175
  exc: Exception,
176
176
  deadline: float | None,
177
- next_sleep: float,
177
+ sleep_iterator: Iterator[float],
178
178
  error_list: list[Exception],
179
179
  predicate_fn: Callable[[Exception], bool],
180
180
  on_error_fn: Callable[[Exception], None] | None,
@@ -183,7 +183,7 @@ def _retry_error_helper(
183
183
  tuple[Exception, Exception | None],
184
184
  ],
185
185
  original_timeout: float | None,
186
- ):
186
+ ) -> float:
187
187
  """
188
188
  Shared logic for handling an error for all retry implementations
189
189
 
@@ -194,13 +194,15 @@ def _retry_error_helper(
194
194
  Args:
195
195
  - exc: the exception that was raised
196
196
  - deadline: the deadline for the retry, calculated as a diff from time.monotonic()
197
- - next_sleep: the next sleep interval
197
+ - sleep_iterator: iterator to draw the next backoff value from
198
198
  - error_list: the list of exceptions that have been raised so far
199
199
  - predicate_fn: takes `exc` and returns true if the operation should be retried
200
200
  - on_error_fn: callback to execute when a retryable error occurs
201
201
  - exc_factory_fn: callback used to build the exception to be raised on terminal failure
202
202
  - original_timeout_val: the original timeout value for the retry (in seconds),
203
203
  to be passed to the exception factory for building an error message
204
+ Returns:
205
+ - the sleep value chosen before the next attempt
204
206
  """
205
207
  error_list.append(exc)
206
208
  if not predicate_fn(exc):
@@ -212,6 +214,12 @@ def _retry_error_helper(
212
214
  raise final_exc from source_exc
213
215
  if on_error_fn is not None:
214
216
  on_error_fn(exc)
217
+ # next_sleep is fetched after the on_error callback, to allow clients
218
+ # to update sleep_iterator values dynamically in response to errors
219
+ try:
220
+ next_sleep = next(sleep_iterator)
221
+ except StopIteration:
222
+ raise ValueError("Sleep generator stopped yielding sleep values.") from exc
215
223
  if deadline is not None and time.monotonic() + next_sleep > deadline:
216
224
  final_exc, source_exc = exc_factory_fn(
217
225
  error_list,
@@ -222,6 +230,7 @@ def _retry_error_helper(
222
230
  _LOGGER.debug(
223
231
  "Retrying due to {}, sleeping {:.1f}s ...".format(error_list[-1], next_sleep)
224
232
  )
233
+ return next_sleep
225
234
 
226
235
 
227
236
  class _BaseRetry(object):
@@ -107,8 +107,11 @@ def retry_target_stream(
107
107
  time.monotonic() + timeout if timeout is not None else None
108
108
  )
109
109
  error_list: list[Exception] = []
110
+ sleep_iter = iter(sleep_generator)
110
111
 
111
- for sleep in sleep_generator:
112
+ # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
113
+ # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
114
+ while True:
112
115
  # Start a new retry loop
113
116
  try:
114
117
  # Note: in the future, we can add a ResumptionStrategy object
@@ -121,10 +124,10 @@ def retry_target_stream(
121
124
  # This function explicitly must deal with broad exceptions.
122
125
  except Exception as exc:
123
126
  # defer to shared logic for handling errors
124
- _retry_error_helper(
127
+ next_sleep = _retry_error_helper(
125
128
  exc,
126
129
  deadline,
127
- sleep,
130
+ sleep_iter,
128
131
  error_list,
129
132
  predicate,
130
133
  on_error,
@@ -132,9 +135,7 @@ def retry_target_stream(
132
135
  timeout,
133
136
  )
134
137
  # if exception not raised, sleep before next attempt
135
- time.sleep(sleep)
136
-
137
- raise ValueError("Sleep generator stopped yielding sleep values.")
138
+ time.sleep(next_sleep)
138
139
 
139
140
 
140
141
  class StreamingRetry(_BaseRetry):
@@ -109,9 +109,12 @@ async def retry_target_stream(
109
109
  deadline = time.monotonic() + timeout if timeout else None
110
110
  # keep track of retryable exceptions we encounter to pass in to exception_factory
111
111
  error_list: list[Exception] = []
112
+ sleep_iter = iter(sleep_generator)
112
113
  target_is_generator: bool | None = None
113
114
 
114
- for sleep in sleep_generator:
115
+ # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
116
+ # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
117
+ while True:
115
118
  # Start a new retry loop
116
119
  try:
117
120
  # Note: in the future, we can add a ResumptionStrategy object
@@ -174,10 +177,10 @@ async def retry_target_stream(
174
177
  # This function explicitly must deal with broad exceptions.
175
178
  except Exception as exc:
176
179
  # defer to shared logic for handling errors
177
- _retry_error_helper(
180
+ next_sleep = _retry_error_helper(
178
181
  exc,
179
182
  deadline,
180
- sleep,
183
+ sleep_iter,
181
184
  error_list,
182
185
  predicate,
183
186
  on_error,
@@ -185,11 +188,11 @@ async def retry_target_stream(
185
188
  timeout,
186
189
  )
187
190
  # if exception not raised, sleep before next attempt
188
- await asyncio.sleep(sleep)
191
+ await asyncio.sleep(next_sleep)
192
+
189
193
  finally:
190
194
  if target_is_generator and target_iterator is not None:
191
195
  await cast(AsyncGenerator["_Y", None], target_iterator).aclose()
192
- raise ValueError("Sleep generator stopped yielding sleep values.")
193
196
 
194
197
 
195
198
  class AsyncStreamingRetry(_BaseRetry):
@@ -138,8 +138,11 @@ def retry_target(
138
138
 
139
139
  deadline = time.monotonic() + timeout if timeout is not None else None
140
140
  error_list: list[Exception] = []
141
+ sleep_iter = iter(sleep_generator)
141
142
 
142
- for sleep in sleep_generator:
143
+ # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
144
+ # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
145
+ while True:
143
146
  try:
144
147
  result = target()
145
148
  if inspect.isawaitable(result):
@@ -150,10 +153,10 @@ def retry_target(
150
153
  # This function explicitly must deal with broad exceptions.
151
154
  except Exception as exc:
152
155
  # defer to shared logic for handling errors
153
- _retry_error_helper(
156
+ next_sleep = _retry_error_helper(
154
157
  exc,
155
158
  deadline,
156
- sleep,
159
+ sleep_iter,
157
160
  error_list,
158
161
  predicate,
159
162
  on_error,
@@ -161,9 +164,7 @@ def retry_target(
161
164
  timeout,
162
165
  )
163
166
  # if exception not raised, sleep before next attempt
164
- time.sleep(sleep)
165
-
166
- raise ValueError("Sleep generator stopped yielding sleep values.")
167
+ time.sleep(next_sleep)
167
168
 
168
169
 
169
170
  class Retry(_BaseRetry):
@@ -149,18 +149,21 @@ async def retry_target(
149
149
 
150
150
  deadline = time.monotonic() + timeout if timeout is not None else None
151
151
  error_list: list[Exception] = []
152
+ sleep_iter = iter(sleep_generator)
152
153
 
153
- for sleep in sleep_generator:
154
+ # continue trying until an attempt completes, or a terminal exception is raised in _retry_error_helper
155
+ # TODO: support max_attempts argument: https://github.com/googleapis/python-api-core/issues/535
156
+ while True:
154
157
  try:
155
158
  return await target()
156
159
  # pylint: disable=broad-except
157
160
  # This function explicitly must deal with broad exceptions.
158
161
  except Exception as exc:
159
162
  # defer to shared logic for handling errors
160
- _retry_error_helper(
163
+ next_sleep = _retry_error_helper(
161
164
  exc,
162
165
  deadline,
163
- sleep,
166
+ sleep_iter,
164
167
  error_list,
165
168
  predicate,
166
169
  on_error,
@@ -168,9 +171,7 @@ async def retry_target(
168
171
  timeout,
169
172
  )
170
173
  # if exception not raised, sleep before next attempt
171
- await asyncio.sleep(sleep)
172
-
173
- raise ValueError("Sleep generator stopped yielding sleep values.")
174
+ await asyncio.sleep(next_sleep)
174
175
 
175
176
 
176
177
  class AsyncRetry(_BaseRetry):
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "2.24.2"
15
+ __version__ = "2.25.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: google-api-core
3
- Version: 2.24.2
3
+ Version: 2.25.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
@@ -30,16 +30,16 @@ Requires-Dist: proto-plus<2.0.0,>=1.25.0; python_version >= "3.13"
30
30
  Requires-Dist: google-auth<3.0.0,>=2.14.1
31
31
  Requires-Dist: requests<3.0.0,>=2.18.0
32
32
  Provides-Extra: async-rest
33
- Requires-Dist: google-auth[aiohttp]<3.0.dev0,>=2.35.0; extra == "async-rest"
33
+ Requires-Dist: google-auth[aiohttp]<3.0.0,>=2.35.0; extra == "async-rest"
34
34
  Provides-Extra: grpc
35
- Requires-Dist: grpcio<2.0dev,>=1.33.2; extra == "grpc"
36
- Requires-Dist: grpcio<2.0dev,>=1.49.1; python_version >= "3.11" and extra == "grpc"
37
- Requires-Dist: grpcio-status<2.0.dev0,>=1.33.2; extra == "grpc"
38
- Requires-Dist: grpcio-status<2.0.dev0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
35
+ Requires-Dist: grpcio<2.0.0,>=1.33.2; extra == "grpc"
36
+ Requires-Dist: grpcio<2.0.0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
37
+ Requires-Dist: grpcio-status<2.0.0,>=1.33.2; extra == "grpc"
38
+ Requires-Dist: grpcio-status<2.0.0,>=1.49.1; python_version >= "3.11" and extra == "grpc"
39
39
  Provides-Extra: grpcgcp
40
- Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcgcp"
40
+ Requires-Dist: grpcio-gcp<1.0.0,>=0.2.2; extra == "grpcgcp"
41
41
  Provides-Extra: grpcio-gcp
42
- Requires-Dist: grpcio-gcp<1.0.dev0,>=0.2.2; extra == "grpcio-gcp"
42
+ Requires-Dist: grpcio-gcp<1.0.0,>=0.2.2; extra == "grpcio-gcp"
43
43
 
44
44
  Core Library for Google Client Libraries
45
45
  ========================================
@@ -8,18 +8,18 @@ requests<3.0.0,>=2.18.0
8
8
  proto-plus<2.0.0,>=1.25.0
9
9
 
10
10
  [async_rest]
11
- google-auth[aiohttp]<3.0.dev0,>=2.35.0
11
+ google-auth[aiohttp]<3.0.0,>=2.35.0
12
12
 
13
13
  [grpc]
14
- grpcio<2.0dev,>=1.33.2
15
- grpcio-status<2.0.dev0,>=1.33.2
14
+ grpcio<2.0.0,>=1.33.2
15
+ grpcio-status<2.0.0,>=1.33.2
16
16
 
17
17
  [grpc:python_version >= "3.11"]
18
- grpcio<2.0dev,>=1.49.1
19
- grpcio-status<2.0.dev0,>=1.49.1
18
+ grpcio<2.0.0,>=1.49.1
19
+ grpcio-status<2.0.0,>=1.49.1
20
20
 
21
21
  [grpcgcp]
22
- grpcio-gcp<1.0.dev0,>=0.2.2
22
+ grpcio-gcp<1.0.0,>=0.2.2
23
23
 
24
24
  [grpcio-gcp]
25
- grpcio-gcp<1.0.dev0,>=0.2.2
25
+ grpcio-gcp<1.0.0,>=0.2.2
@@ -58,15 +58,15 @@ Documentation = "https://googleapis.dev/python/google-api-core/latest/"
58
58
  Repository = "https://github.com/googleapis/python-api-core"
59
59
 
60
60
  [project.optional-dependencies]
61
- async_rest = ["google-auth[aiohttp] >= 2.35.0, < 3.0.dev0"]
61
+ async_rest = ["google-auth[aiohttp] >= 2.35.0, < 3.0.0"]
62
62
  grpc = [
63
- "grpcio >= 1.33.2, < 2.0dev",
64
- "grpcio >= 1.49.1, < 2.0dev; python_version >= '3.11'",
65
- "grpcio-status >= 1.33.2, < 2.0.dev0",
66
- "grpcio-status >= 1.49.1, < 2.0.dev0; python_version >= '3.11'",
63
+ "grpcio >= 1.33.2, < 2.0.0",
64
+ "grpcio >= 1.49.1, < 2.0.0; python_version >= '3.11'",
65
+ "grpcio-status >= 1.33.2, < 2.0.0",
66
+ "grpcio-status >= 1.49.1, < 2.0.0; python_version >= '3.11'",
67
67
  ]
68
- grpcgcp = ["grpcio-gcp >= 0.2.2, < 1.0.dev0"]
69
- grpcio-gcp = ["grpcio-gcp >= 0.2.2, < 1.0.dev0"]
68
+ grpcgcp = ["grpcio-gcp >= 0.2.2, < 1.0.0"]
69
+ grpcio-gcp = ["grpcio-gcp >= 0.2.2, < 1.0.0"]
70
70
 
71
71
  [tool.setuptools.dynamic]
72
72
  version = { attr = "google.api_core.version.__version__" }
@@ -81,6 +81,7 @@ async def test_wrap_method_with_custom_client_info():
81
81
  api_core_version=3,
82
82
  gapic_version=4,
83
83
  client_library_version=5,
84
+ protobuf_runtime_version=6,
84
85
  )
85
86
  fake_call = grpc_helpers_async.FakeUnaryUnaryCall()
86
87
  method = mock.Mock(spec=aio.UnaryUnaryMultiCallable, return_value=fake_call)
@@ -36,7 +36,36 @@ async def test_retry_streaming_target_bad_sleep_generator():
36
36
  from google.api_core.retry.retry_streaming_async import retry_target_stream
37
37
 
38
38
  with pytest.raises(ValueError, match="Sleep generator"):
39
- await retry_target_stream(None, None, [], None).__anext__()
39
+ await retry_target_stream(None, lambda x: True, [], None).__anext__()
40
+
41
+
42
+ @mock.patch("asyncio.sleep", autospec=True)
43
+ @pytest.mark.asyncio
44
+ async def test_retry_streaming_target_dynamic_backoff(sleep):
45
+ """
46
+ sleep_generator should be iterated after on_error, to support dynamic backoff
47
+ """
48
+ from functools import partial
49
+ from google.api_core.retry.retry_streaming_async import retry_target_stream
50
+
51
+ sleep.side_effect = RuntimeError("stop after sleep")
52
+ # start with empty sleep generator; values are added after exception in push_sleep_value
53
+ sleep_values = []
54
+ error_target = partial(TestAsyncStreamingRetry._generator_mock, error_on=0)
55
+ inserted_sleep = 99
56
+
57
+ def push_sleep_value(err):
58
+ sleep_values.append(inserted_sleep)
59
+
60
+ with pytest.raises(RuntimeError):
61
+ await retry_target_stream(
62
+ error_target,
63
+ predicate=lambda x: True,
64
+ sleep_generator=sleep_values,
65
+ on_error=push_sleep_value,
66
+ ).__anext__()
67
+ assert sleep.call_count == 1
68
+ sleep.assert_called_once_with(inserted_sleep)
40
69
 
41
70
 
42
71
  class TestAsyncStreamingRetry(Test_BaseRetry):
@@ -66,8 +95,8 @@ class TestAsyncStreamingRetry(Test_BaseRetry):
66
95
  str(retry_),
67
96
  )
68
97
 
98
+ @staticmethod
69
99
  async def _generator_mock(
70
- self,
71
100
  num=5,
72
101
  error_on=None,
73
102
  exceptions_seen=None,
@@ -87,7 +116,7 @@ class TestAsyncStreamingRetry(Test_BaseRetry):
87
116
  for i in range(num):
88
117
  if sleep_time:
89
118
  await asyncio.sleep(sleep_time)
90
- if error_on and i == error_on:
119
+ if error_on is not None and i == error_on:
91
120
  raise ValueError("generator mock error")
92
121
  yield i
93
122
  except (Exception, BaseException, GeneratorExit) as e:
@@ -136,9 +136,34 @@ async def test_retry_target_timeout_exceeded(monotonic, sleep, use_deadline_arg)
136
136
  @pytest.mark.asyncio
137
137
  async def test_retry_target_bad_sleep_generator():
138
138
  with pytest.raises(ValueError, match="Sleep generator"):
139
+ await retry_async.retry_target(mock.sentinel.target, lambda x: True, [], None)
140
+
141
+
142
+ @mock.patch("asyncio.sleep", autospec=True)
143
+ @pytest.mark.asyncio
144
+ async def test_retry_target_dynamic_backoff(sleep):
145
+ """
146
+ sleep_generator should be iterated after on_error, to support dynamic backoff
147
+ """
148
+ sleep.side_effect = RuntimeError("stop after sleep")
149
+ # start with empty sleep generator; values are added after exception in push_sleep_value
150
+ sleep_values = []
151
+ exception = ValueError("trigger retry")
152
+ error_target = mock.Mock(side_effect=exception)
153
+ inserted_sleep = 99
154
+
155
+ def push_sleep_value(err):
156
+ sleep_values.append(inserted_sleep)
157
+
158
+ with pytest.raises(RuntimeError):
139
159
  await retry_async.retry_target(
140
- mock.sentinel.target, mock.sentinel.predicate, [], None
160
+ error_target,
161
+ predicate=lambda x: True,
162
+ sleep_generator=sleep_values,
163
+ on_error=push_sleep_value,
141
164
  )
165
+ assert sleep.call_count == 1
166
+ sleep.assert_called_once_with(inserted_sleep)
142
167
 
143
168
 
144
169
  class TestAsyncRetry(Test_BaseRetry):
@@ -76,6 +76,7 @@ def test_wrap_method_with_custom_client_info():
76
76
  api_core_version=3,
77
77
  gapic_version=4,
78
78
  client_library_version=5,
79
+ protobuf_runtime_version=6,
79
80
  )
80
81
  method = mock.Mock(spec=["__call__"])
81
82
 
@@ -33,7 +33,36 @@ def test_retry_streaming_target_bad_sleep_generator():
33
33
  with pytest.raises(
34
34
  ValueError, match="Sleep generator stopped yielding sleep values"
35
35
  ):
36
- next(retry_streaming.retry_target_stream(None, None, [], None))
36
+ next(retry_streaming.retry_target_stream(None, lambda x: True, [], None))
37
+
38
+
39
+ @mock.patch("time.sleep", autospec=True)
40
+ def test_retry_streaming_target_dynamic_backoff(sleep):
41
+ """
42
+ sleep_generator should be iterated after on_error, to support dynamic backoff
43
+ """
44
+ from functools import partial
45
+
46
+ sleep.side_effect = RuntimeError("stop after sleep")
47
+ # start with empty sleep generator; values are added after exception in push_sleep_value
48
+ sleep_values = []
49
+ error_target = partial(TestStreamingRetry._generator_mock, error_on=0)
50
+ inserted_sleep = 99
51
+
52
+ def push_sleep_value(err):
53
+ sleep_values.append(inserted_sleep)
54
+
55
+ with pytest.raises(RuntimeError):
56
+ next(
57
+ retry_streaming.retry_target_stream(
58
+ error_target,
59
+ predicate=lambda x: True,
60
+ sleep_generator=sleep_values,
61
+ on_error=push_sleep_value,
62
+ )
63
+ )
64
+ assert sleep.call_count == 1
65
+ sleep.assert_called_once_with(inserted_sleep)
37
66
 
38
67
 
39
68
  class TestStreamingRetry(Test_BaseRetry):
@@ -63,8 +92,8 @@ class TestStreamingRetry(Test_BaseRetry):
63
92
  str(retry_),
64
93
  )
65
94
 
95
+ @staticmethod
66
96
  def _generator_mock(
67
- self,
68
97
  num=5,
69
98
  error_on=None,
70
99
  return_val=None,
@@ -82,7 +111,7 @@ class TestStreamingRetry(Test_BaseRetry):
82
111
  """
83
112
  try:
84
113
  for i in range(num):
85
- if error_on and i == error_on:
114
+ if error_on is not None and i == error_on:
86
115
  raise ValueError("generator mock error")
87
116
  yield i
88
117
  return return_val
@@ -146,7 +146,33 @@ def test_retry_target_timeout_exceeded(monotonic, sleep, use_deadline_arg):
146
146
 
147
147
  def test_retry_target_bad_sleep_generator():
148
148
  with pytest.raises(ValueError, match="Sleep generator"):
149
- retry.retry_target(mock.sentinel.target, mock.sentinel.predicate, [], None)
149
+ retry.retry_target(mock.sentinel.target, lambda x: True, [], None)
150
+
151
+
152
+ @mock.patch("time.sleep", autospec=True)
153
+ def test_retry_target_dynamic_backoff(sleep):
154
+ """
155
+ sleep_generator should be iterated after on_error, to support dynamic backoff
156
+ """
157
+ sleep.side_effect = RuntimeError("stop after sleep")
158
+ # start with empty sleep generator; values are added after exception in push_sleep_value
159
+ sleep_values = []
160
+ exception = ValueError("trigger retry")
161
+ error_target = mock.Mock(side_effect=exception)
162
+ inserted_sleep = 99
163
+
164
+ def push_sleep_value(err):
165
+ sleep_values.append(inserted_sleep)
166
+
167
+ with pytest.raises(RuntimeError):
168
+ retry.retry_target(
169
+ error_target,
170
+ predicate=lambda x: True,
171
+ sleep_generator=sleep_values,
172
+ on_error=push_sleep_value,
173
+ )
174
+ assert sleep.call_count == 1
175
+ sleep.assert_called_once_with(inserted_sleep)
150
176
 
151
177
 
152
178
  class TestRetry(Test_BaseRetry):
@@ -16,6 +16,7 @@ import datetime
16
16
  import logging
17
17
  import queue
18
18
  import threading
19
+ import time
19
20
 
20
21
  try:
21
22
  from unittest import mock
@@ -894,3 +895,26 @@ class TestBackgroundConsumer(object):
894
895
 
895
896
  # calling stop twice should not result in an error.
896
897
  consumer.stop()
898
+
899
+ def test_stop_error_logs(self, caplog):
900
+ """
901
+ Closing the client should result in no internal error logs
902
+
903
+ https://github.com/googleapis/python-api-core/issues/788
904
+ """
905
+ caplog.set_level(logging.DEBUG)
906
+ bidi_rpc = mock.create_autospec(bidi.BidiRpc, instance=True)
907
+ bidi_rpc.is_active = True
908
+ on_response = mock.Mock(spec=["__call__"])
909
+
910
+ consumer = bidi.BackgroundConsumer(bidi_rpc, on_response)
911
+
912
+ consumer.start()
913
+ consumer.stop()
914
+ # let the background thread run for a while before exiting
915
+ time.sleep(0.1)
916
+ bidi_rpc.is_active = False
917
+ # running thread should not result in error logs
918
+ error_logs = [r.message for r in caplog.records if r.levelname == "ERROR"]
919
+ assert not error_logs, f"Found unexpected ERROR logs: {error_logs}"
920
+ bidi_rpc.is_active = False
@@ -46,6 +46,7 @@ def test_constructor_options():
46
46
  client_library_version="5",
47
47
  user_agent="6",
48
48
  rest_version="7",
49
+ protobuf_runtime_version="8",
49
50
  )
50
51
 
51
52
  assert info.python_version == "1"
@@ -55,11 +56,15 @@ def test_constructor_options():
55
56
  assert info.client_library_version == "5"
56
57
  assert info.user_agent == "6"
57
58
  assert info.rest_version == "7"
59
+ assert info.protobuf_runtime_version == "8"
58
60
 
59
61
 
60
62
  def test_to_user_agent_minimal():
61
63
  info = client_info.ClientInfo(
62
- python_version="1", api_core_version="2", grpc_version=None
64
+ python_version="1",
65
+ api_core_version="2",
66
+ grpc_version=None,
67
+ protobuf_runtime_version=None,
63
68
  )
64
69
 
65
70
  user_agent = info.to_user_agent()
@@ -75,11 +80,12 @@ def test_to_user_agent_full():
75
80
  gapic_version="4",
76
81
  client_library_version="5",
77
82
  user_agent="app-name/1.0",
83
+ protobuf_runtime_version="6",
78
84
  )
79
85
 
80
86
  user_agent = info.to_user_agent()
81
87
 
82
- assert user_agent == "app-name/1.0 gl-python/1 grpc/2 gax/3 gapic/4 gccl/5"
88
+ assert user_agent == "app-name/1.0 gl-python/1 grpc/2 gax/3 gapic/4 gccl/5 pb/6"
83
89
 
84
90
 
85
91
  def test_to_user_agent_rest():