google-cloud-bigtable 2.30.0__py3-none-any.whl

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 (115) hide show
  1. google/cloud/bigtable/__init__.py +25 -0
  2. google/cloud/bigtable/app_profile.py +377 -0
  3. google/cloud/bigtable/backup.py +490 -0
  4. google/cloud/bigtable/batcher.py +414 -0
  5. google/cloud/bigtable/client.py +475 -0
  6. google/cloud/bigtable/cluster.py +541 -0
  7. google/cloud/bigtable/column_family.py +362 -0
  8. google/cloud/bigtable/data/__init__.py +103 -0
  9. google/cloud/bigtable/data/_async/__init__.py +25 -0
  10. google/cloud/bigtable/data/_async/_mutate_rows.py +226 -0
  11. google/cloud/bigtable/data/_async/_read_rows.py +363 -0
  12. google/cloud/bigtable/data/_async/client.py +1522 -0
  13. google/cloud/bigtable/data/_async/mutations_batcher.py +534 -0
  14. google/cloud/bigtable/data/_cross_sync/__init__.py +20 -0
  15. google/cloud/bigtable/data/_cross_sync/_decorators.py +441 -0
  16. google/cloud/bigtable/data/_cross_sync/_mapping_meta.py +64 -0
  17. google/cloud/bigtable/data/_cross_sync/cross_sync.py +334 -0
  18. google/cloud/bigtable/data/_helpers.py +249 -0
  19. google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py +182 -0
  20. google/cloud/bigtable/data/_sync_autogen/_read_rows.py +304 -0
  21. google/cloud/bigtable/data/_sync_autogen/client.py +1293 -0
  22. google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py +449 -0
  23. google/cloud/bigtable/data/exceptions.py +340 -0
  24. google/cloud/bigtable/data/execute_query/__init__.py +43 -0
  25. google/cloud/bigtable/data/execute_query/_async/__init__.py +13 -0
  26. google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py +291 -0
  27. google/cloud/bigtable/data/execute_query/_byte_cursor.py +123 -0
  28. google/cloud/bigtable/data/execute_query/_checksum.py +43 -0
  29. google/cloud/bigtable/data/execute_query/_parameters_formatting.py +155 -0
  30. google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py +134 -0
  31. google/cloud/bigtable/data/execute_query/_reader.py +122 -0
  32. google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py +239 -0
  33. google/cloud/bigtable/data/execute_query/metadata.py +399 -0
  34. google/cloud/bigtable/data/execute_query/values.py +123 -0
  35. google/cloud/bigtable/data/mutations.py +380 -0
  36. google/cloud/bigtable/data/read_modify_write_rules.py +112 -0
  37. google/cloud/bigtable/data/read_rows_query.py +536 -0
  38. google/cloud/bigtable/data/row.py +535 -0
  39. google/cloud/bigtable/data/row_filters.py +968 -0
  40. google/cloud/bigtable/encryption_info.py +64 -0
  41. google/cloud/bigtable/enums.py +223 -0
  42. google/cloud/bigtable/error.py +64 -0
  43. google/cloud/bigtable/gapic_version.py +16 -0
  44. google/cloud/bigtable/helpers.py +31 -0
  45. google/cloud/bigtable/instance.py +789 -0
  46. google/cloud/bigtable/policy.py +255 -0
  47. google/cloud/bigtable/row.py +1267 -0
  48. google/cloud/bigtable/row_data.py +380 -0
  49. google/cloud/bigtable/row_filters.py +838 -0
  50. google/cloud/bigtable/row_merger.py +250 -0
  51. google/cloud/bigtable/row_set.py +213 -0
  52. google/cloud/bigtable/table.py +1409 -0
  53. google/cloud/bigtable_admin/__init__.py +410 -0
  54. google/cloud/bigtable_admin/gapic_version.py +16 -0
  55. google/cloud/bigtable_admin/py.typed +2 -0
  56. google/cloud/bigtable_admin_v2/__init__.py +250 -0
  57. google/cloud/bigtable_admin_v2/gapic_metadata.json +962 -0
  58. google/cloud/bigtable_admin_v2/gapic_version.py +16 -0
  59. google/cloud/bigtable_admin_v2/py.typed +2 -0
  60. google/cloud/bigtable_admin_v2/services/__init__.py +15 -0
  61. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py +22 -0
  62. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +3465 -0
  63. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +3944 -0
  64. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +681 -0
  65. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py +40 -0
  66. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +751 -0
  67. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +1249 -0
  68. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +1579 -0
  69. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest.py +6820 -0
  70. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py +1746 -0
  71. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py +22 -0
  72. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/async_client.py +3472 -0
  73. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/client.py +3949 -0
  74. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py +669 -0
  75. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py +40 -0
  76. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py +708 -0
  77. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py +1257 -0
  78. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py +1549 -0
  79. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest.py +6609 -0
  80. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py +1714 -0
  81. google/cloud/bigtable_admin_v2/types/__init__.py +248 -0
  82. google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +1364 -0
  83. google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py +1715 -0
  84. google/cloud/bigtable_admin_v2/types/common.py +81 -0
  85. google/cloud/bigtable_admin_v2/types/instance.py +819 -0
  86. google/cloud/bigtable_admin_v2/types/table.py +1028 -0
  87. google/cloud/bigtable_admin_v2/types/types.py +776 -0
  88. google/cloud/bigtable_v2/__init__.py +136 -0
  89. google/cloud/bigtable_v2/gapic_metadata.json +193 -0
  90. google/cloud/bigtable_v2/gapic_version.py +16 -0
  91. google/cloud/bigtable_v2/py.typed +2 -0
  92. google/cloud/bigtable_v2/services/__init__.py +15 -0
  93. google/cloud/bigtable_v2/services/bigtable/__init__.py +22 -0
  94. google/cloud/bigtable_v2/services/bigtable/async_client.py +1741 -0
  95. google/cloud/bigtable_v2/services/bigtable/client.py +2194 -0
  96. google/cloud/bigtable_v2/services/bigtable/transports/__init__.py +38 -0
  97. google/cloud/bigtable_v2/services/bigtable/transports/base.py +343 -0
  98. google/cloud/bigtable_v2/services/bigtable/transports/grpc.py +660 -0
  99. google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py +762 -0
  100. google/cloud/bigtable_v2/services/bigtable/transports/rest.py +2489 -0
  101. google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py +711 -0
  102. google/cloud/bigtable_v2/types/__init__.py +138 -0
  103. google/cloud/bigtable_v2/types/bigtable.py +1531 -0
  104. google/cloud/bigtable_v2/types/data.py +1612 -0
  105. google/cloud/bigtable_v2/types/feature_flags.py +119 -0
  106. google/cloud/bigtable_v2/types/request_stats.py +171 -0
  107. google/cloud/bigtable_v2/types/response_params.py +64 -0
  108. google/cloud/bigtable_v2/types/types.py +579 -0
  109. google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_admin_v2_keywords.py +233 -0
  110. google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_v2_keywords.py +186 -0
  111. google_cloud_bigtable-2.30.0.dist-info/LICENSE +202 -0
  112. google_cloud_bigtable-2.30.0.dist-info/METADATA +154 -0
  113. google_cloud_bigtable-2.30.0.dist-info/RECORD +115 -0
  114. google_cloud_bigtable-2.30.0.dist-info/WHEEL +5 -0
  115. google_cloud_bigtable-2.30.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,334 @@
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ """
16
+ CrossSync provides a toolset for sharing logic between async and sync codebases, including:
17
+ - A set of decorators for annotating async classes and functions
18
+ (@CrossSync.export_sync, @CrossSync.convert, @CrossSync.drop_method, ...)
19
+ - A set of wrappers to wrap common objects and types that have corresponding async and sync implementations
20
+ (CrossSync.Queue, CrossSync.Condition, CrossSync.Future, ...)
21
+ - A set of function implementations for common async operations that can be used in both async and sync codebases
22
+ (CrossSync.gather_partials, CrossSync.wait, CrossSync.condition_wait, ...)
23
+ - CrossSync.rm_aio(), which is used to annotate regions of the code containing async keywords to strip
24
+
25
+ A separate module will use CrossSync annotations to generate a corresponding sync
26
+ class based on a decorated async class.
27
+
28
+ Usage Example:
29
+ ```python
30
+ @CrossSync.export_sync(path="path/to/sync_module.py")
31
+
32
+ @CrossSync.convert
33
+ async def async_func(self, arg: int) -> int:
34
+ await CrossSync.sleep(1)
35
+ return arg
36
+ ```
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ from typing import (
42
+ TypeVar,
43
+ Any,
44
+ Callable,
45
+ Coroutine,
46
+ Sequence,
47
+ Union,
48
+ AsyncIterable,
49
+ AsyncIterator,
50
+ AsyncGenerator,
51
+ TYPE_CHECKING,
52
+ )
53
+ import typing
54
+
55
+ import asyncio
56
+ import sys
57
+ import concurrent.futures
58
+ import google.api_core.retry as retries
59
+ import queue
60
+ import threading
61
+ import time
62
+ from ._decorators import (
63
+ ConvertClass,
64
+ Convert,
65
+ Drop,
66
+ Pytest,
67
+ PytestFixture,
68
+ )
69
+ from ._mapping_meta import MappingMeta
70
+
71
+ if TYPE_CHECKING:
72
+ from typing_extensions import TypeAlias
73
+
74
+ T = TypeVar("T")
75
+
76
+
77
+ class CrossSync(metaclass=MappingMeta):
78
+ # support CrossSync.is_async to check if the current environment is async
79
+ is_async = True
80
+
81
+ # provide aliases for common async functions and types
82
+ sleep = asyncio.sleep
83
+ retry_target = retries.retry_target_async
84
+ retry_target_stream = retries.retry_target_stream_async
85
+ Retry = retries.AsyncRetry
86
+ Queue: TypeAlias = asyncio.Queue
87
+ Condition: TypeAlias = asyncio.Condition
88
+ Future: TypeAlias = asyncio.Future
89
+ Task: TypeAlias = asyncio.Task
90
+ Event: TypeAlias = asyncio.Event
91
+ Semaphore: TypeAlias = asyncio.Semaphore
92
+ StopIteration: TypeAlias = StopAsyncIteration
93
+ # provide aliases for common async type annotations
94
+ Awaitable: TypeAlias = typing.Awaitable
95
+ Iterable: TypeAlias = AsyncIterable
96
+ Iterator: TypeAlias = AsyncIterator
97
+ Generator: TypeAlias = AsyncGenerator
98
+
99
+ # decorators
100
+ convert_class = ConvertClass.decorator # decorate classes to convert
101
+ convert = Convert.decorator # decorate methods to convert from async to sync
102
+ drop = Drop.decorator # decorate methods to remove from sync version
103
+ pytest = Pytest.decorator # decorate test methods to run with pytest-asyncio
104
+ pytest_fixture = (
105
+ PytestFixture.decorator
106
+ ) # decorate test methods to run with pytest fixture
107
+
108
+ @classmethod
109
+ def next(cls, iterable):
110
+ return iterable.__anext__()
111
+
112
+ @classmethod
113
+ def Mock(cls, *args, **kwargs):
114
+ """
115
+ Alias for AsyncMock, importing at runtime to avoid hard dependency on mock
116
+ """
117
+ try:
118
+ from unittest.mock import AsyncMock # type: ignore
119
+ except ImportError: # pragma: NO COVER
120
+ from mock import AsyncMock # type: ignore
121
+ return AsyncMock(*args, **kwargs)
122
+
123
+ @staticmethod
124
+ async def gather_partials(
125
+ partial_list: Sequence[Callable[[], Awaitable[T]]],
126
+ return_exceptions: bool = False,
127
+ sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
128
+ ) -> list[T | BaseException]:
129
+ """
130
+ abstraction over asyncio.gather, but with a set of partial functions instead
131
+ of coroutines, to work with sync functions.
132
+ To use gather with a set of futures instead of partials, use CrpssSync.wait
133
+
134
+ In the async version, the partials are expected to return an awaitable object. Patials
135
+ are unpacked and awaited in the gather call.
136
+
137
+ Sync version implemented with threadpool executor
138
+
139
+ Returns:
140
+ - a list of results (or exceptions, if return_exceptions=True) in the same order as partial_list
141
+ """
142
+ if not partial_list:
143
+ return []
144
+ awaitable_list = [partial() for partial in partial_list]
145
+ return await asyncio.gather(
146
+ *awaitable_list, return_exceptions=return_exceptions
147
+ )
148
+
149
+ @staticmethod
150
+ async def wait(
151
+ futures: Sequence[CrossSync.Future[T]], timeout: float | None = None
152
+ ) -> tuple[set[CrossSync.Future[T]], set[CrossSync.Future[T]]]:
153
+ """
154
+ abstraction over asyncio.wait
155
+
156
+ Return:
157
+ - a tuple of (done, pending) sets of futures
158
+ """
159
+ if not futures:
160
+ return set(), set()
161
+ return await asyncio.wait(futures, timeout=timeout)
162
+
163
+ @staticmethod
164
+ async def event_wait(
165
+ event: CrossSync.Event,
166
+ timeout: float | None = None,
167
+ async_break_early: bool = True,
168
+ ) -> None:
169
+ """
170
+ abstraction over asyncio.Event.wait
171
+
172
+ Args:
173
+ - event: event to wait for
174
+ - timeout: if set, will break out early after `timeout` seconds
175
+ - async_break_early: if False, the async version will wait for
176
+ the full timeout even if the event is set before the timeout.
177
+ This avoids creating a new background task
178
+ """
179
+ if timeout is None:
180
+ await event.wait()
181
+ elif not async_break_early:
182
+ if not event.is_set():
183
+ await asyncio.sleep(timeout)
184
+ else:
185
+ try:
186
+ await asyncio.wait_for(event.wait(), timeout=timeout)
187
+ except asyncio.TimeoutError:
188
+ pass
189
+
190
+ @staticmethod
191
+ def create_task(
192
+ fn: Callable[..., Coroutine[Any, Any, T]],
193
+ *fn_args,
194
+ sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
195
+ task_name: str | None = None,
196
+ **fn_kwargs,
197
+ ) -> CrossSync.Task[T]:
198
+ """
199
+ abstraction over asyncio.create_task. Sync version implemented with threadpool executor
200
+
201
+ sync_executor: ThreadPoolExecutor to use for sync operations. Ignored in async version
202
+ """
203
+ task: CrossSync.Task[T] = asyncio.create_task(fn(*fn_args, **fn_kwargs))
204
+ if task_name and sys.version_info >= (3, 8):
205
+ task.set_name(task_name)
206
+ return task
207
+
208
+ @staticmethod
209
+ async def yield_to_event_loop() -> None:
210
+ """
211
+ Call asyncio.sleep(0) to yield to allow other tasks to run
212
+ """
213
+ await asyncio.sleep(0)
214
+
215
+ @staticmethod
216
+ def verify_async_event_loop() -> None:
217
+ """
218
+ Raises RuntimeError if the event loop is not running
219
+ """
220
+ asyncio.get_running_loop()
221
+
222
+ @staticmethod
223
+ def rm_aio(statement: T) -> T:
224
+ """
225
+ Used to annotate regions of the code containing async keywords to strip
226
+
227
+ All async keywords inside an rm_aio call are removed, along with
228
+ `async with` and `async for` statements containing CrossSync.rm_aio() in the body
229
+ """
230
+ return statement
231
+
232
+ class _Sync_Impl(metaclass=MappingMeta):
233
+ """
234
+ Provide sync versions of the async functions and types in CrossSync
235
+ """
236
+
237
+ is_async = False
238
+
239
+ sleep = time.sleep
240
+ next = next
241
+ retry_target = retries.retry_target
242
+ retry_target_stream = retries.retry_target_stream
243
+ Retry = retries.Retry
244
+ Queue: TypeAlias = queue.Queue
245
+ Condition: TypeAlias = threading.Condition
246
+ Future: TypeAlias = concurrent.futures.Future
247
+ Task: TypeAlias = concurrent.futures.Future
248
+ Event: TypeAlias = threading.Event
249
+ Semaphore: TypeAlias = threading.Semaphore
250
+ StopIteration: TypeAlias = StopIteration
251
+ # type annotations
252
+ Awaitable: TypeAlias = Union[T]
253
+ Iterable: TypeAlias = typing.Iterable
254
+ Iterator: TypeAlias = typing.Iterator
255
+ Generator: TypeAlias = typing.Generator
256
+
257
+ @classmethod
258
+ def Mock(cls, *args, **kwargs):
259
+ from unittest.mock import Mock
260
+
261
+ return Mock(*args, **kwargs)
262
+
263
+ @staticmethod
264
+ def event_wait(
265
+ event: CrossSync._Sync_Impl.Event,
266
+ timeout: float | None = None,
267
+ async_break_early: bool = True,
268
+ ) -> None:
269
+ event.wait(timeout=timeout)
270
+
271
+ @staticmethod
272
+ def gather_partials(
273
+ partial_list: Sequence[Callable[[], T]],
274
+ return_exceptions: bool = False,
275
+ sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
276
+ ) -> list[T | BaseException]:
277
+ if not partial_list:
278
+ return []
279
+ if not sync_executor:
280
+ raise ValueError("sync_executor is required for sync version")
281
+ futures_list = [sync_executor.submit(partial) for partial in partial_list]
282
+ results_list: list[T | BaseException] = []
283
+ for future in futures_list:
284
+ found_exc = future.exception()
285
+ if found_exc is not None:
286
+ if return_exceptions:
287
+ results_list.append(found_exc)
288
+ else:
289
+ raise found_exc
290
+ else:
291
+ results_list.append(future.result())
292
+ return results_list
293
+
294
+ @staticmethod
295
+ def wait(
296
+ futures: Sequence[CrossSync._Sync_Impl.Future[T]],
297
+ timeout: float | None = None,
298
+ ) -> tuple[
299
+ set[CrossSync._Sync_Impl.Future[T]], set[CrossSync._Sync_Impl.Future[T]]
300
+ ]:
301
+ if not futures:
302
+ return set(), set()
303
+ return concurrent.futures.wait(futures, timeout=timeout)
304
+
305
+ @staticmethod
306
+ def create_task(
307
+ fn: Callable[..., T],
308
+ *fn_args,
309
+ sync_executor: concurrent.futures.ThreadPoolExecutor | None = None,
310
+ task_name: str | None = None,
311
+ **fn_kwargs,
312
+ ) -> CrossSync._Sync_Impl.Task[T]:
313
+ """
314
+ abstraction over asyncio.create_task. Sync version implemented with threadpool executor
315
+
316
+ sync_executor: ThreadPoolExecutor to use for sync operations. Ignored in async version
317
+ """
318
+ if not sync_executor:
319
+ raise ValueError("sync_executor is required for sync version")
320
+ return sync_executor.submit(fn, *fn_args, **fn_kwargs)
321
+
322
+ @staticmethod
323
+ def yield_to_event_loop() -> None:
324
+ """
325
+ No-op for sync version
326
+ """
327
+ pass
328
+
329
+ @staticmethod
330
+ def verify_async_event_loop() -> None:
331
+ """
332
+ No-op for sync version
333
+ """
334
+ pass
@@ -0,0 +1,249 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ #
14
+ """
15
+ Helper functions used in various places in the library.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from typing import Sequence, List, Tuple, TYPE_CHECKING, Union
20
+ import time
21
+ import enum
22
+ from collections import namedtuple
23
+ from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery
24
+
25
+ from google.api_core import exceptions as core_exceptions
26
+ from google.api_core.retry import RetryFailureReason
27
+ from google.cloud.bigtable.data.exceptions import RetryExceptionGroup
28
+
29
+ if TYPE_CHECKING:
30
+ import grpc
31
+ from google.cloud.bigtable.data import TableAsync
32
+ from google.cloud.bigtable.data import Table
33
+
34
+ """
35
+ Helper functions used in various places in the library.
36
+ """
37
+
38
+ # Type alias for the output of sample_keys
39
+ RowKeySamples = List[Tuple[bytes, int]]
40
+
41
+ # type alias for the output of query.shard()
42
+ ShardedQuery = List[ReadRowsQuery]
43
+
44
+ # used by read_rows_sharded to limit how many requests are attempted in parallel
45
+ _CONCURRENCY_LIMIT = 10
46
+
47
+ # used to register instance data with the client for channel warming
48
+ _WarmedInstanceKey = namedtuple(
49
+ "_WarmedInstanceKey", ["instance_name", "table_name", "app_profile_id"]
50
+ )
51
+
52
+
53
+ # enum used on method calls when table defaults should be used
54
+ class TABLE_DEFAULT(enum.Enum):
55
+ # default for mutate_row, sample_row_keys, check_and_mutate_row, and read_modify_write_row
56
+ DEFAULT = "DEFAULT"
57
+ # default for read_rows, read_rows_stream, read_rows_sharded, row_exists, and read_row
58
+ READ_ROWS = "READ_ROWS_DEFAULT"
59
+ # default for bulk_mutate_rows and mutations_batcher
60
+ MUTATE_ROWS = "MUTATE_ROWS_DEFAULT"
61
+
62
+
63
+ def _attempt_timeout_generator(
64
+ per_request_timeout: float | None, operation_timeout: float
65
+ ):
66
+ """
67
+ Generator that yields the timeout value for each attempt of a retry loop.
68
+
69
+ Will return per_request_timeout until the operation_timeout is approached,
70
+ at which point it will return the remaining time in the operation_timeout.
71
+
72
+ Args:
73
+ per_request_timeout: The timeout value to use for each request, in seconds.
74
+ If None, the operation_timeout will be used for each request.
75
+ operation_timeout: The timeout value to use for the entire operationm in seconds.
76
+ Yields:
77
+ float: The timeout value to use for the next request, in seonds
78
+ """
79
+ per_request_timeout = (
80
+ per_request_timeout if per_request_timeout is not None else operation_timeout
81
+ )
82
+ deadline = operation_timeout + time.monotonic()
83
+ while True:
84
+ yield max(0, min(per_request_timeout, deadline - time.monotonic()))
85
+
86
+
87
+ def _retry_exception_factory(
88
+ exc_list: list[Exception],
89
+ reason: RetryFailureReason,
90
+ timeout_val: float | None,
91
+ ) -> tuple[Exception, Exception | None]:
92
+ """
93
+ Build retry error based on exceptions encountered during operation
94
+
95
+ Args:
96
+ exc_list: list of exceptions encountered during operation
97
+ is_timeout: whether the operation failed due to timeout
98
+ timeout_val: the operation timeout value in seconds, for constructing
99
+ the error message
100
+ Returns:
101
+ tuple[Exception, Exception|None]:
102
+ tuple of the exception to raise, and a cause exception if applicable
103
+ """
104
+ if reason == RetryFailureReason.TIMEOUT:
105
+ timeout_val_str = f"of {timeout_val:0.1f}s " if timeout_val is not None else ""
106
+ # if failed due to timeout, raise deadline exceeded as primary exception
107
+ source_exc: Exception = core_exceptions.DeadlineExceeded(
108
+ f"operation_timeout{timeout_val_str} exceeded"
109
+ )
110
+ elif exc_list:
111
+ # otherwise, raise non-retryable error as primary exception
112
+ source_exc = exc_list.pop()
113
+ else:
114
+ source_exc = RuntimeError("failed with unspecified exception")
115
+ # use the retry exception group as the cause of the exception
116
+ cause_exc: Exception | None = RetryExceptionGroup(exc_list) if exc_list else None
117
+ source_exc.__cause__ = cause_exc
118
+ return source_exc, cause_exc
119
+
120
+
121
+ def _get_timeouts(
122
+ operation: float | TABLE_DEFAULT,
123
+ attempt: float | None | TABLE_DEFAULT,
124
+ table: "TableAsync" | "Table",
125
+ ) -> tuple[float, float]:
126
+ """
127
+ Convert passed in timeout values to floats, using table defaults if necessary.
128
+
129
+ attempt will use operation value if None, or if larger than operation.
130
+
131
+ Will call _validate_timeouts on the outputs, and raise ValueError if the
132
+ resulting timeouts are invalid.
133
+
134
+ Args:
135
+ operation: The timeout value to use for the entire operation, in seconds.
136
+ attempt: The timeout value to use for each attempt, in seconds.
137
+ table: The table to use for default values.
138
+ Returns:
139
+ tuple[float, float]: A tuple of (operation_timeout, attempt_timeout)
140
+ """
141
+ # load table defaults if necessary
142
+ if operation == TABLE_DEFAULT.DEFAULT:
143
+ final_operation = table.default_operation_timeout
144
+ elif operation == TABLE_DEFAULT.READ_ROWS:
145
+ final_operation = table.default_read_rows_operation_timeout
146
+ elif operation == TABLE_DEFAULT.MUTATE_ROWS:
147
+ final_operation = table.default_mutate_rows_operation_timeout
148
+ else:
149
+ final_operation = operation
150
+ if attempt == TABLE_DEFAULT.DEFAULT:
151
+ attempt = table.default_attempt_timeout
152
+ elif attempt == TABLE_DEFAULT.READ_ROWS:
153
+ attempt = table.default_read_rows_attempt_timeout
154
+ elif attempt == TABLE_DEFAULT.MUTATE_ROWS:
155
+ attempt = table.default_mutate_rows_attempt_timeout
156
+
157
+ return _align_timeouts(final_operation, attempt)
158
+
159
+
160
+ def _align_timeouts(operation: float, attempt: float | None) -> tuple[float, float]:
161
+ """
162
+ Convert passed in timeout values to floats.
163
+
164
+ attempt will use operation value if None, or if larger than operation.
165
+
166
+ Will call _validate_timeouts on the outputs, and raise ValueError if the
167
+ resulting timeouts are invalid.
168
+
169
+ Args:
170
+ operation: The timeout value to use for the entire operation, in seconds.
171
+ attempt: The timeout value to use for each attempt, in seconds.
172
+ Returns:
173
+ tuple[float, float]: A tuple of (operation_timeout, attempt_timeout)
174
+ """
175
+ if attempt is None:
176
+ # no timeout specified, use operation timeout for both
177
+ final_attempt = operation
178
+ else:
179
+ # cap attempt timeout at operation timeout
180
+ final_attempt = min(attempt, operation) if operation else attempt
181
+
182
+ _validate_timeouts(operation, final_attempt, allow_none=False)
183
+ return operation, final_attempt
184
+
185
+
186
+ def _validate_timeouts(
187
+ operation_timeout: float, attempt_timeout: float | None, allow_none: bool = False
188
+ ):
189
+ """
190
+ Helper function that will verify that timeout values are valid, and raise
191
+ an exception if they are not.
192
+
193
+ Args:
194
+ operation_timeout: The timeout value to use for the entire operation, in seconds.
195
+ attempt_timeout: The timeout value to use for each attempt, in seconds.
196
+ allow_none: If True, attempt_timeout can be None. If False, None values will raise an exception.
197
+ Raises:
198
+ ValueError: if operation_timeout or attempt_timeout are invalid.
199
+ """
200
+ if operation_timeout is None:
201
+ raise ValueError("operation_timeout cannot be None")
202
+ if operation_timeout <= 0:
203
+ raise ValueError("operation_timeout must be greater than 0")
204
+ if not allow_none and attempt_timeout is None:
205
+ raise ValueError("attempt_timeout must not be None")
206
+ elif attempt_timeout is not None:
207
+ if attempt_timeout <= 0:
208
+ raise ValueError("attempt_timeout must be greater than 0")
209
+
210
+
211
+ def _get_error_type(
212
+ call_code: Union["grpc.StatusCode", int, type[Exception]]
213
+ ) -> type[Exception]:
214
+ """Helper function for ensuring the object is an exception type.
215
+ If it is not, the proper GoogleAPICallError type is infered from the status
216
+ code.
217
+
218
+ Args:
219
+ - call_code: Exception type or gRPC status code.
220
+ """
221
+ if isinstance(call_code, type):
222
+ return call_code
223
+ else:
224
+ return type(core_exceptions.from_grpc_status(call_code, ""))
225
+
226
+
227
+ def _get_retryable_errors(
228
+ call_codes: Sequence["grpc.StatusCode" | int | type[Exception]] | TABLE_DEFAULT,
229
+ table: "TableAsync" | "Table",
230
+ ) -> list[type[Exception]]:
231
+ """
232
+ Convert passed in retryable error codes to a list of exception types.
233
+
234
+ Args:
235
+ call_codes: The error codes to convert. Can be a list of grpc.StatusCode values,
236
+ int values, or Exception types, or a TABLE_DEFAULT value.
237
+ table: The table to use for default values.
238
+ Returns:
239
+ list[type[Exception]]: A list of exception types to retry on.
240
+ """
241
+ # load table defaults if necessary
242
+ if call_codes == TABLE_DEFAULT.DEFAULT:
243
+ call_codes = table.default_retryable_errors
244
+ elif call_codes == TABLE_DEFAULT.READ_ROWS:
245
+ call_codes = table.default_read_rows_retryable_errors
246
+ elif call_codes == TABLE_DEFAULT.MUTATE_ROWS:
247
+ call_codes = table.default_mutate_rows_retryable_errors
248
+
249
+ return [_get_error_type(e) for e in call_codes]