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.
- google/cloud/bigtable/__init__.py +25 -0
- google/cloud/bigtable/app_profile.py +377 -0
- google/cloud/bigtable/backup.py +490 -0
- google/cloud/bigtable/batcher.py +414 -0
- google/cloud/bigtable/client.py +475 -0
- google/cloud/bigtable/cluster.py +541 -0
- google/cloud/bigtable/column_family.py +362 -0
- google/cloud/bigtable/data/__init__.py +103 -0
- google/cloud/bigtable/data/_async/__init__.py +25 -0
- google/cloud/bigtable/data/_async/_mutate_rows.py +226 -0
- google/cloud/bigtable/data/_async/_read_rows.py +363 -0
- google/cloud/bigtable/data/_async/client.py +1522 -0
- google/cloud/bigtable/data/_async/mutations_batcher.py +534 -0
- google/cloud/bigtable/data/_cross_sync/__init__.py +20 -0
- google/cloud/bigtable/data/_cross_sync/_decorators.py +441 -0
- google/cloud/bigtable/data/_cross_sync/_mapping_meta.py +64 -0
- google/cloud/bigtable/data/_cross_sync/cross_sync.py +334 -0
- google/cloud/bigtable/data/_helpers.py +249 -0
- google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py +182 -0
- google/cloud/bigtable/data/_sync_autogen/_read_rows.py +304 -0
- google/cloud/bigtable/data/_sync_autogen/client.py +1293 -0
- google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py +449 -0
- google/cloud/bigtable/data/exceptions.py +340 -0
- google/cloud/bigtable/data/execute_query/__init__.py +43 -0
- google/cloud/bigtable/data/execute_query/_async/__init__.py +13 -0
- google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py +291 -0
- google/cloud/bigtable/data/execute_query/_byte_cursor.py +123 -0
- google/cloud/bigtable/data/execute_query/_checksum.py +43 -0
- google/cloud/bigtable/data/execute_query/_parameters_formatting.py +155 -0
- google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py +134 -0
- google/cloud/bigtable/data/execute_query/_reader.py +122 -0
- google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py +239 -0
- google/cloud/bigtable/data/execute_query/metadata.py +399 -0
- google/cloud/bigtable/data/execute_query/values.py +123 -0
- google/cloud/bigtable/data/mutations.py +380 -0
- google/cloud/bigtable/data/read_modify_write_rules.py +112 -0
- google/cloud/bigtable/data/read_rows_query.py +536 -0
- google/cloud/bigtable/data/row.py +535 -0
- google/cloud/bigtable/data/row_filters.py +968 -0
- google/cloud/bigtable/encryption_info.py +64 -0
- google/cloud/bigtable/enums.py +223 -0
- google/cloud/bigtable/error.py +64 -0
- google/cloud/bigtable/gapic_version.py +16 -0
- google/cloud/bigtable/helpers.py +31 -0
- google/cloud/bigtable/instance.py +789 -0
- google/cloud/bigtable/policy.py +255 -0
- google/cloud/bigtable/row.py +1267 -0
- google/cloud/bigtable/row_data.py +380 -0
- google/cloud/bigtable/row_filters.py +838 -0
- google/cloud/bigtable/row_merger.py +250 -0
- google/cloud/bigtable/row_set.py +213 -0
- google/cloud/bigtable/table.py +1409 -0
- google/cloud/bigtable_admin/__init__.py +410 -0
- google/cloud/bigtable_admin/gapic_version.py +16 -0
- google/cloud/bigtable_admin/py.typed +2 -0
- google/cloud/bigtable_admin_v2/__init__.py +250 -0
- google/cloud/bigtable_admin_v2/gapic_metadata.json +962 -0
- google/cloud/bigtable_admin_v2/gapic_version.py +16 -0
- google/cloud/bigtable_admin_v2/py.typed +2 -0
- google/cloud/bigtable_admin_v2/services/__init__.py +15 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py +22 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +3465 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +3944 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +681 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py +40 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +751 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +1249 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +1579 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest.py +6820 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py +1746 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py +22 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/async_client.py +3472 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/client.py +3949 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py +669 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py +40 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py +708 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py +1257 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py +1549 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest.py +6609 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py +1714 -0
- google/cloud/bigtable_admin_v2/types/__init__.py +248 -0
- google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +1364 -0
- google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py +1715 -0
- google/cloud/bigtable_admin_v2/types/common.py +81 -0
- google/cloud/bigtable_admin_v2/types/instance.py +819 -0
- google/cloud/bigtable_admin_v2/types/table.py +1028 -0
- google/cloud/bigtable_admin_v2/types/types.py +776 -0
- google/cloud/bigtable_v2/__init__.py +136 -0
- google/cloud/bigtable_v2/gapic_metadata.json +193 -0
- google/cloud/bigtable_v2/gapic_version.py +16 -0
- google/cloud/bigtable_v2/py.typed +2 -0
- google/cloud/bigtable_v2/services/__init__.py +15 -0
- google/cloud/bigtable_v2/services/bigtable/__init__.py +22 -0
- google/cloud/bigtable_v2/services/bigtable/async_client.py +1741 -0
- google/cloud/bigtable_v2/services/bigtable/client.py +2194 -0
- google/cloud/bigtable_v2/services/bigtable/transports/__init__.py +38 -0
- google/cloud/bigtable_v2/services/bigtable/transports/base.py +343 -0
- google/cloud/bigtable_v2/services/bigtable/transports/grpc.py +660 -0
- google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py +762 -0
- google/cloud/bigtable_v2/services/bigtable/transports/rest.py +2489 -0
- google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py +711 -0
- google/cloud/bigtable_v2/types/__init__.py +138 -0
- google/cloud/bigtable_v2/types/bigtable.py +1531 -0
- google/cloud/bigtable_v2/types/data.py +1612 -0
- google/cloud/bigtable_v2/types/feature_flags.py +119 -0
- google/cloud/bigtable_v2/types/request_stats.py +171 -0
- google/cloud/bigtable_v2/types/response_params.py +64 -0
- google/cloud/bigtable_v2/types/types.py +579 -0
- google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_admin_v2_keywords.py +233 -0
- google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_v2_keywords.py +186 -0
- google_cloud_bigtable-2.30.0.dist-info/LICENSE +202 -0
- google_cloud_bigtable-2.30.0.dist-info/METADATA +154 -0
- google_cloud_bigtable-2.30.0.dist-info/RECORD +115 -0
- google_cloud_bigtable-2.30.0.dist-info/WHEEL +5 -0
- 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]
|