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,1522 @@
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
+ from __future__ import annotations
17
+
18
+ from typing import (
19
+ cast,
20
+ Any,
21
+ AsyncIterable,
22
+ Optional,
23
+ Set,
24
+ Sequence,
25
+ TYPE_CHECKING,
26
+ )
27
+
28
+ import time
29
+ import warnings
30
+ import random
31
+ import os
32
+ import concurrent.futures
33
+
34
+ from functools import partial
35
+ from grpc import Channel
36
+
37
+ from google.cloud.bigtable.data.execute_query.values import ExecuteQueryValueType
38
+ from google.cloud.bigtable.data.execute_query.metadata import (
39
+ SqlType,
40
+ _pb_metadata_to_metadata_types,
41
+ )
42
+ from google.cloud.bigtable.data.execute_query._parameters_formatting import (
43
+ _format_execute_query_params,
44
+ _to_param_types,
45
+ )
46
+ from google.cloud.bigtable_v2.services.bigtable.transports.base import (
47
+ DEFAULT_CLIENT_INFO,
48
+ )
49
+ from google.cloud.bigtable_v2.types.bigtable import PingAndWarmRequest
50
+ from google.cloud.client import ClientWithProject
51
+ from google.cloud.environment_vars import BIGTABLE_EMULATOR # type: ignore
52
+ from google.api_core import retry as retries
53
+ from google.api_core.exceptions import DeadlineExceeded
54
+ from google.api_core.exceptions import ServiceUnavailable
55
+ from google.api_core.exceptions import Aborted
56
+
57
+ import google.auth.credentials
58
+ import google.auth._default
59
+ from google.api_core import client_options as client_options_lib
60
+ from google.cloud.bigtable.client import _DEFAULT_BIGTABLE_EMULATOR_CLIENT
61
+ from google.cloud.bigtable.data.row import Row
62
+ from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery
63
+ from google.cloud.bigtable.data.exceptions import FailedQueryShardError
64
+ from google.cloud.bigtable.data.exceptions import ShardedReadRowsExceptionGroup
65
+
66
+ from google.cloud.bigtable.data._helpers import TABLE_DEFAULT, _align_timeouts
67
+ from google.cloud.bigtable.data._helpers import _WarmedInstanceKey
68
+ from google.cloud.bigtable.data._helpers import _CONCURRENCY_LIMIT
69
+ from google.cloud.bigtable.data._helpers import _retry_exception_factory
70
+ from google.cloud.bigtable.data._helpers import _validate_timeouts
71
+ from google.cloud.bigtable.data._helpers import _get_error_type
72
+ from google.cloud.bigtable.data._helpers import _get_retryable_errors
73
+ from google.cloud.bigtable.data._helpers import _get_timeouts
74
+ from google.cloud.bigtable.data._helpers import _attempt_timeout_generator
75
+ from google.cloud.bigtable.data.mutations import Mutation, RowMutationEntry
76
+
77
+ from google.cloud.bigtable.data.read_modify_write_rules import ReadModifyWriteRule
78
+ from google.cloud.bigtable.data.row_filters import RowFilter
79
+ from google.cloud.bigtable.data.row_filters import StripValueTransformerFilter
80
+ from google.cloud.bigtable.data.row_filters import CellsRowLimitFilter
81
+ from google.cloud.bigtable.data.row_filters import RowFilterChain
82
+
83
+ from google.cloud.bigtable.data._cross_sync import CrossSync
84
+
85
+ if CrossSync.is_async:
86
+ from grpc.aio import insecure_channel
87
+ from google.cloud.bigtable_v2.services.bigtable.transports import (
88
+ BigtableGrpcAsyncIOTransport as TransportType,
89
+ )
90
+ from google.cloud.bigtable.data._async.mutations_batcher import _MB_SIZE
91
+ else:
92
+ from typing import Iterable # noqa: F401
93
+ from grpc import insecure_channel
94
+ from grpc import intercept_channel
95
+ from google.cloud.bigtable_v2.services.bigtable.transports import BigtableGrpcTransport as TransportType # type: ignore
96
+ from google.cloud.bigtable.data._sync_autogen.mutations_batcher import _MB_SIZE
97
+
98
+
99
+ if TYPE_CHECKING:
100
+ from google.cloud.bigtable.data._helpers import RowKeySamples
101
+ from google.cloud.bigtable.data._helpers import ShardedQuery
102
+
103
+ if CrossSync.is_async:
104
+ from google.cloud.bigtable.data._async.mutations_batcher import (
105
+ MutationsBatcherAsync,
106
+ )
107
+ from google.cloud.bigtable.data.execute_query._async.execute_query_iterator import (
108
+ ExecuteQueryIteratorAsync,
109
+ )
110
+ else:
111
+ from google.cloud.bigtable.data._sync_autogen.mutations_batcher import ( # noqa: F401
112
+ MutationsBatcher,
113
+ )
114
+ from google.cloud.bigtable.data.execute_query._sync_autogen.execute_query_iterator import ( # noqa: F401
115
+ ExecuteQueryIterator,
116
+ )
117
+
118
+
119
+ __CROSS_SYNC_OUTPUT__ = "google.cloud.bigtable.data._sync_autogen.client"
120
+
121
+
122
+ @CrossSync.convert_class(
123
+ sync_name="BigtableDataClient",
124
+ add_mapping_for_name="DataClient",
125
+ )
126
+ class BigtableDataClientAsync(ClientWithProject):
127
+ @CrossSync.convert(
128
+ docstring_format_vars={
129
+ "LOOP_MESSAGE": (
130
+ "Client should be created within an async context (running event loop)",
131
+ None,
132
+ ),
133
+ "RAISE_NO_LOOP": (
134
+ "RuntimeError: if called outside of an async context (no running event loop)",
135
+ None,
136
+ ),
137
+ }
138
+ )
139
+ def __init__(
140
+ self,
141
+ *,
142
+ project: str | None = None,
143
+ credentials: google.auth.credentials.Credentials | None = None,
144
+ client_options: dict[str, Any]
145
+ | "google.api_core.client_options.ClientOptions"
146
+ | None = None,
147
+ **kwargs,
148
+ ):
149
+ """
150
+ Create a client instance for the Bigtable Data API
151
+
152
+ {LOOP_MESSAGE}
153
+
154
+ Args:
155
+ project: the project which the client acts on behalf of.
156
+ If not passed, falls back to the default inferred
157
+ from the environment.
158
+ credentials:
159
+ Thehe OAuth2 Credentials to use for this
160
+ client. If not passed (and if no ``_http`` object is
161
+ passed), falls back to the default inferred from the
162
+ environment.
163
+ client_options:
164
+ Client options used to set user options
165
+ on the client. API Endpoint should be set through client_options.
166
+ Raises:
167
+ {RAISE_NO_LOOP}
168
+ """
169
+ if "pool_size" in kwargs:
170
+ warnings.warn("pool_size no longer supported")
171
+ # set up client info headers for veneer library
172
+ self.client_info = DEFAULT_CLIENT_INFO
173
+ self.client_info.client_library_version = self._client_version()
174
+ # parse client options
175
+ if type(client_options) is dict:
176
+ client_options = client_options_lib.from_dict(client_options)
177
+ client_options = cast(
178
+ Optional[client_options_lib.ClientOptions], client_options
179
+ )
180
+ custom_channel = None
181
+ self._emulator_host = os.getenv(BIGTABLE_EMULATOR)
182
+ if self._emulator_host is not None:
183
+ warnings.warn(
184
+ "Connecting to Bigtable emulator at {}".format(self._emulator_host),
185
+ RuntimeWarning,
186
+ stacklevel=2,
187
+ )
188
+ # use insecure channel if emulator is set
189
+ custom_channel = insecure_channel(self._emulator_host)
190
+ if credentials is None:
191
+ credentials = google.auth.credentials.AnonymousCredentials()
192
+ if project is None:
193
+ project = _DEFAULT_BIGTABLE_EMULATOR_CLIENT
194
+ # initialize client
195
+ ClientWithProject.__init__(
196
+ self,
197
+ credentials=credentials,
198
+ project=project,
199
+ client_options=client_options,
200
+ )
201
+ self._gapic_client = CrossSync.GapicClient(
202
+ credentials=credentials,
203
+ client_options=client_options,
204
+ client_info=self.client_info,
205
+ transport=lambda *args, **kwargs: TransportType(
206
+ *args, **kwargs, channel=custom_channel
207
+ ),
208
+ )
209
+ self._is_closed = CrossSync.Event()
210
+ self.transport = cast(TransportType, self._gapic_client.transport)
211
+ # keep track of active instances to for warmup on channel refresh
212
+ self._active_instances: Set[_WarmedInstanceKey] = set()
213
+ # keep track of table objects associated with each instance
214
+ # only remove instance from _active_instances when all associated tables remove it
215
+ self._instance_owners: dict[_WarmedInstanceKey, Set[int]] = {}
216
+ self._channel_init_time = time.monotonic()
217
+ self._channel_refresh_task: CrossSync.Task[None] | None = None
218
+ self._executor = (
219
+ concurrent.futures.ThreadPoolExecutor() if not CrossSync.is_async else None
220
+ )
221
+ if self._emulator_host is None:
222
+ # attempt to start background channel refresh tasks
223
+ try:
224
+ self._start_background_channel_refresh()
225
+ except RuntimeError:
226
+ warnings.warn(
227
+ f"{self.__class__.__name__} should be started in an "
228
+ "asyncio event loop. Channel refresh will not be started",
229
+ RuntimeWarning,
230
+ stacklevel=2,
231
+ )
232
+
233
+ @staticmethod
234
+ def _client_version() -> str:
235
+ """
236
+ Helper function to return the client version string for this client
237
+ """
238
+ version_str = f"{google.cloud.bigtable.__version__}-data"
239
+ if CrossSync.is_async:
240
+ version_str += "-async"
241
+ return version_str
242
+
243
+ @CrossSync.convert(
244
+ docstring_format_vars={
245
+ "RAISE_NO_LOOP": (
246
+ "RuntimeError: if not called in an asyncio event loop",
247
+ "None",
248
+ )
249
+ }
250
+ )
251
+ def _start_background_channel_refresh(self) -> None:
252
+ """
253
+ Starts a background task to ping and warm grpc channel
254
+
255
+ Raises:
256
+ {RAISE_NO_LOOP}
257
+ """
258
+ if (
259
+ not self._channel_refresh_task
260
+ and not self._emulator_host
261
+ and not self._is_closed.is_set()
262
+ ):
263
+ # raise error if not in an event loop in async client
264
+ CrossSync.verify_async_event_loop()
265
+ self._channel_refresh_task = CrossSync.create_task(
266
+ self._manage_channel,
267
+ sync_executor=self._executor,
268
+ task_name=f"{self.__class__.__name__} channel refresh",
269
+ )
270
+
271
+ @CrossSync.convert
272
+ async def close(self, timeout: float | None = 2.0):
273
+ """
274
+ Cancel all background tasks
275
+ """
276
+ self._is_closed.set()
277
+ if self._channel_refresh_task is not None:
278
+ self._channel_refresh_task.cancel()
279
+ await CrossSync.wait([self._channel_refresh_task], timeout=timeout)
280
+ await self.transport.close()
281
+ if self._executor:
282
+ self._executor.shutdown(wait=False)
283
+ self._channel_refresh_task = None
284
+
285
+ @CrossSync.convert
286
+ async def _ping_and_warm_instances(
287
+ self,
288
+ instance_key: _WarmedInstanceKey | None = None,
289
+ channel: Channel | None = None,
290
+ ) -> list[BaseException | None]:
291
+ """
292
+ Prepares the backend for requests on a channel
293
+
294
+ Pings each Bigtable instance registered in `_active_instances` on the client
295
+
296
+ Args:
297
+ instance_key: if provided, only warm the instance associated with the key
298
+ channel: grpc channel to warm. If none, warms `self.transport.grpc_channel`
299
+ Returns:
300
+ list[BaseException | None]: sequence of results or exceptions from the ping requests
301
+ """
302
+ channel = channel or self.transport.grpc_channel
303
+ instance_list = (
304
+ [instance_key] if instance_key is not None else self._active_instances
305
+ )
306
+ ping_rpc = channel.unary_unary(
307
+ "/google.bigtable.v2.Bigtable/PingAndWarm",
308
+ request_serializer=PingAndWarmRequest.serialize,
309
+ )
310
+ # prepare list of coroutines to run
311
+ partial_list = [
312
+ partial(
313
+ ping_rpc,
314
+ request={"name": instance_name, "app_profile_id": app_profile_id},
315
+ metadata=[
316
+ (
317
+ "x-goog-request-params",
318
+ f"name={instance_name}&app_profile_id={app_profile_id}",
319
+ )
320
+ ],
321
+ wait_for_ready=True,
322
+ )
323
+ for (instance_name, table_name, app_profile_id) in instance_list
324
+ ]
325
+ result_list = await CrossSync.gather_partials(
326
+ partial_list, return_exceptions=True, sync_executor=self._executor
327
+ )
328
+ return [r or None for r in result_list]
329
+
330
+ @CrossSync.convert
331
+ async def _manage_channel(
332
+ self,
333
+ refresh_interval_min: float = 60 * 35,
334
+ refresh_interval_max: float = 60 * 45,
335
+ grace_period: float = 60 * 10,
336
+ ) -> None:
337
+ """
338
+ Background task that periodically refreshes and warms a grpc channel
339
+
340
+ The backend will automatically close channels after 60 minutes, so
341
+ `refresh_interval` + `grace_period` should be < 60 minutes
342
+
343
+ Runs continuously until the client is closed
344
+
345
+ Args:
346
+ refresh_interval_min: minimum interval before initiating refresh
347
+ process in seconds. Actual interval will be a random value
348
+ between `refresh_interval_min` and `refresh_interval_max`
349
+ refresh_interval_max: maximum interval before initiating refresh
350
+ process in seconds. Actual interval will be a random value
351
+ between `refresh_interval_min` and `refresh_interval_max`
352
+ grace_period: time to allow previous channel to serve existing
353
+ requests before closing, in seconds
354
+ """
355
+ first_refresh = self._channel_init_time + random.uniform(
356
+ refresh_interval_min, refresh_interval_max
357
+ )
358
+ next_sleep = max(first_refresh - time.monotonic(), 0)
359
+ if next_sleep > 0:
360
+ # warm the current channel immediately
361
+ await self._ping_and_warm_instances(channel=self.transport.grpc_channel)
362
+ # continuously refresh the channel every `refresh_interval` seconds
363
+ while not self._is_closed.is_set():
364
+ await CrossSync.event_wait(
365
+ self._is_closed,
366
+ next_sleep,
367
+ async_break_early=False, # no need to interrupt sleep. Task will be cancelled on close
368
+ )
369
+ if self._is_closed.is_set():
370
+ # don't refresh if client is closed
371
+ break
372
+ start_timestamp = time.monotonic()
373
+ # prepare new channel for use
374
+ # TODO: refactor to avoid using internal references: https://github.com/googleapis/python-bigtable/issues/1094
375
+ old_channel = self.transport.grpc_channel
376
+ new_channel = self.transport.create_channel()
377
+ if CrossSync.is_async:
378
+ new_channel._unary_unary_interceptors.append(
379
+ self.transport._interceptor
380
+ )
381
+ else:
382
+ new_channel = intercept_channel(
383
+ new_channel, self.transport._interceptor
384
+ )
385
+ await self._ping_and_warm_instances(channel=new_channel)
386
+ # cycle channel out of use, with long grace window before closure
387
+ self.transport._grpc_channel = new_channel
388
+ self.transport._logged_channel = new_channel
389
+ # invalidate caches
390
+ self.transport._stubs = {}
391
+ self.transport._prep_wrapped_messages(self.client_info)
392
+ # give old_channel a chance to complete existing rpcs
393
+ if CrossSync.is_async:
394
+ await old_channel.close(grace_period)
395
+ else:
396
+ if grace_period:
397
+ self._is_closed.wait(grace_period) # type: ignore
398
+ old_channel.close() # type: ignore
399
+ # subtract thed time spent waiting for the channel to be replaced
400
+ next_refresh = random.uniform(refresh_interval_min, refresh_interval_max)
401
+ next_sleep = max(next_refresh - (time.monotonic() - start_timestamp), 0)
402
+
403
+ @CrossSync.convert(
404
+ replace_symbols={
405
+ "TableAsync": "Table",
406
+ "ExecuteQueryIteratorAsync": "ExecuteQueryIterator",
407
+ }
408
+ )
409
+ async def _register_instance(
410
+ self, instance_id: str, owner: TableAsync | ExecuteQueryIteratorAsync
411
+ ) -> None:
412
+ """
413
+ Registers an instance with the client, and warms the channel for the instance
414
+ The client will periodically refresh grpc channel used to make
415
+ requests, and new channels will be warmed for each registered instance
416
+ Channels will not be refreshed unless at least one instance is registered
417
+
418
+ Args:
419
+ instance_id: id of the instance to register.
420
+ owner: table that owns the instance. Owners will be tracked in
421
+ _instance_owners, and instances will only be unregistered when all
422
+ owners call _remove_instance_registration
423
+ """
424
+ instance_name = self._gapic_client.instance_path(self.project, instance_id)
425
+ instance_key = _WarmedInstanceKey(
426
+ instance_name, owner.table_name, owner.app_profile_id
427
+ )
428
+ self._instance_owners.setdefault(instance_key, set()).add(id(owner))
429
+ if instance_key not in self._active_instances:
430
+ self._active_instances.add(instance_key)
431
+ if self._channel_refresh_task:
432
+ # refresh tasks already running
433
+ # call ping and warm on all existing channels
434
+ await self._ping_and_warm_instances(instance_key)
435
+ else:
436
+ # refresh tasks aren't active. start them as background tasks
437
+ self._start_background_channel_refresh()
438
+
439
+ @CrossSync.convert(
440
+ replace_symbols={
441
+ "TableAsync": "Table",
442
+ "ExecuteQueryIteratorAsync": "ExecuteQueryIterator",
443
+ }
444
+ )
445
+ async def _remove_instance_registration(
446
+ self, instance_id: str, owner: TableAsync | "ExecuteQueryIteratorAsync"
447
+ ) -> bool:
448
+ """
449
+ Removes an instance from the client's registered instances, to prevent
450
+ warming new channels for the instance
451
+
452
+ If instance_id is not registered, or is still in use by other tables, returns False
453
+
454
+ Args:
455
+ instance_id: id of the instance to remove
456
+ owner: table that owns the instance. Owners will be tracked in
457
+ _instance_owners, and instances will only be unregistered when all
458
+ owners call _remove_instance_registration
459
+ Returns:
460
+ bool: True if instance was removed, else False
461
+ """
462
+ instance_name = self._gapic_client.instance_path(self.project, instance_id)
463
+ instance_key = _WarmedInstanceKey(
464
+ instance_name, owner.table_name, owner.app_profile_id
465
+ )
466
+ owner_list = self._instance_owners.get(instance_key, set())
467
+ try:
468
+ owner_list.remove(id(owner))
469
+ if len(owner_list) == 0:
470
+ self._active_instances.remove(instance_key)
471
+ return True
472
+ except KeyError:
473
+ return False
474
+
475
+ @CrossSync.convert(
476
+ replace_symbols={"TableAsync": "Table"},
477
+ docstring_format_vars={
478
+ "LOOP_MESSAGE": (
479
+ "Must be created within an async context (running event loop)",
480
+ "",
481
+ ),
482
+ "RAISE_NO_LOOP": (
483
+ "RuntimeError: if called outside of an async context (no running event loop)",
484
+ "None",
485
+ ),
486
+ },
487
+ )
488
+ def get_table(self, instance_id: str, table_id: str, *args, **kwargs) -> TableAsync:
489
+ """
490
+ Returns a table instance for making data API requests. All arguments are passed
491
+ directly to the TableAsync constructor.
492
+
493
+ {LOOP_MESSAGE}
494
+
495
+ Args:
496
+ instance_id: The Bigtable instance ID to associate with this client.
497
+ instance_id is combined with the client's project to fully
498
+ specify the instance
499
+ table_id: The ID of the table. table_id is combined with the
500
+ instance_id and the client's project to fully specify the table
501
+ app_profile_id: The app profile to associate with requests.
502
+ https://cloud.google.com/bigtable/docs/app-profiles
503
+ default_read_rows_operation_timeout: The default timeout for read rows
504
+ operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
505
+ default_read_rows_attempt_timeout: The default timeout for individual
506
+ read rows rpc requests, in seconds. If not set, defaults to 20 seconds
507
+ default_mutate_rows_operation_timeout: The default timeout for mutate rows
508
+ operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
509
+ default_mutate_rows_attempt_timeout: The default timeout for individual
510
+ mutate rows rpc requests, in seconds. If not set, defaults to 60 seconds
511
+ default_operation_timeout: The default timeout for all other operations, in
512
+ seconds. If not set, defaults to 60 seconds
513
+ default_attempt_timeout: The default timeout for all other individual rpc
514
+ requests, in seconds. If not set, defaults to 20 seconds
515
+ default_read_rows_retryable_errors: a list of errors that will be retried
516
+ if encountered during read_rows and related operations.
517
+ Defaults to 4 (DeadlineExceeded), 14 (ServiceUnavailable), and 10 (Aborted)
518
+ default_mutate_rows_retryable_errors: a list of errors that will be retried
519
+ if encountered during mutate_rows and related operations.
520
+ Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
521
+ default_retryable_errors: a list of errors that will be retried if
522
+ encountered during all other operations.
523
+ Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
524
+ Returns:
525
+ TableAsync: a table instance for making data API requests
526
+ Raises:
527
+ {RAISE_NO_LOOP}
528
+ """
529
+ return TableAsync(self, instance_id, table_id, *args, **kwargs)
530
+
531
+ @CrossSync.convert(
532
+ replace_symbols={"ExecuteQueryIteratorAsync": "ExecuteQueryIterator"}
533
+ )
534
+ async def execute_query(
535
+ self,
536
+ query: str,
537
+ instance_id: str,
538
+ *,
539
+ parameters: dict[str, ExecuteQueryValueType] | None = None,
540
+ parameter_types: dict[str, SqlType.Type] | None = None,
541
+ app_profile_id: str | None = None,
542
+ operation_timeout: float = 600,
543
+ attempt_timeout: float | None = 20,
544
+ retryable_errors: Sequence[type[Exception]] = (
545
+ DeadlineExceeded,
546
+ ServiceUnavailable,
547
+ Aborted,
548
+ ),
549
+ prepare_operation_timeout: float = 60,
550
+ prepare_attempt_timeout: float | None = 20,
551
+ prepare_retryable_errors: Sequence[type[Exception]] = (
552
+ DeadlineExceeded,
553
+ ServiceUnavailable,
554
+ ),
555
+ ) -> "ExecuteQueryIteratorAsync":
556
+ """
557
+ Executes an SQL query on an instance.
558
+ Returns an iterator to asynchronously stream back columns from selected rows.
559
+
560
+ Failed requests within operation_timeout will be retried based on the
561
+ retryable_errors list until operation_timeout is reached.
562
+
563
+ Note that this makes two requests, one to ``PrepareQuery`` and one to ``ExecuteQuery``.
564
+ These have separate retry configurations. ``ExecuteQuery`` is where the bulk of the
565
+ work happens.
566
+
567
+ Args:
568
+ query: Query to be run on Bigtable instance. The query can use ``@param``
569
+ placeholders to use parameter interpolation on the server. Values for all
570
+ parameters should be provided in ``parameters``. Types of parameters are
571
+ inferred but should be provided in ``parameter_types`` if the inference is
572
+ not possible (i.e. when value can be None, an empty list or an empty dict).
573
+ instance_id: The Bigtable instance ID to perform the query on.
574
+ instance_id is combined with the client's project to fully
575
+ specify the instance.
576
+ parameters: Dictionary with values for all parameters used in the ``query``.
577
+ parameter_types: Dictionary with types of parameters used in the ``query``.
578
+ Required to contain entries only for parameters whose type cannot be
579
+ detected automatically (i.e. the value can be None, an empty list or
580
+ an empty dict).
581
+ app_profile_id: The app profile to associate with requests.
582
+ https://cloud.google.com/bigtable/docs/app-profiles
583
+ operation_timeout: the time budget for the entire executeQuery operation, in seconds.
584
+ Failed requests will be retried within the budget.
585
+ Defaults to 600 seconds.
586
+ attempt_timeout: the time budget for an individual executeQuery network request, in seconds.
587
+ If it takes longer than this time to complete, the request will be cancelled with
588
+ a DeadlineExceeded exception, and a retry will be attempted.
589
+ Defaults to the 20 seconds.
590
+ If None, defaults to operation_timeout.
591
+ retryable_errors: a list of errors that will be retried if encountered during executeQuery.
592
+ Defaults to 4 (DeadlineExceeded), 14 (ServiceUnavailable), and 10 (Aborted)
593
+ prepare_operation_timeout: the time budget for the entire prepareQuery operation, in seconds.
594
+ Failed requests will be retried within the budget.
595
+ Defaults to 60 seconds.
596
+ prepare_attempt_timeout: the time budget for an individual prepareQuery network request, in seconds.
597
+ If it takes longer than this time to complete, the request will be cancelled with
598
+ a DeadlineExceeded exception, and a retry will be attempted.
599
+ Defaults to the 20 seconds.
600
+ If None, defaults to prepare_operation_timeout.
601
+ prepare_retryable_errors: a list of errors that will be retried if encountered during prepareQuery.
602
+ Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
603
+ Returns:
604
+ ExecuteQueryIteratorAsync: an asynchronous iterator that yields rows returned by the query
605
+ Raises:
606
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
607
+ will be chained with a RetryExceptionGroup containing GoogleAPIError exceptions
608
+ from any retries that failed
609
+ google.api_core.exceptions.GoogleAPIError: raised if the request encounters an unrecoverable error
610
+ google.cloud.bigtable.data.exceptions.ParameterTypeInferenceFailed: Raised if
611
+ a parameter is passed without an explicit type, and the type cannot be infered
612
+ """
613
+ instance_name = self._gapic_client.instance_path(self.project, instance_id)
614
+ converted_param_types = _to_param_types(parameters, parameter_types)
615
+ prepare_request = {
616
+ "instance_name": instance_name,
617
+ "query": query,
618
+ "app_profile_id": app_profile_id,
619
+ "param_types": converted_param_types,
620
+ "proto_format": {},
621
+ }
622
+ prepare_predicate = retries.if_exception_type(
623
+ *[_get_error_type(e) for e in prepare_retryable_errors]
624
+ )
625
+ prepare_operation_timeout, prepare_attempt_timeout = _align_timeouts(
626
+ prepare_operation_timeout, prepare_attempt_timeout
627
+ )
628
+ prepare_sleep_generator = retries.exponential_sleep_generator(0.01, 2, 60)
629
+
630
+ target = partial(
631
+ self._gapic_client.prepare_query,
632
+ request=prepare_request,
633
+ timeout=prepare_attempt_timeout,
634
+ retry=None,
635
+ )
636
+ prepare_result = await CrossSync.retry_target(
637
+ target,
638
+ prepare_predicate,
639
+ prepare_sleep_generator,
640
+ prepare_operation_timeout,
641
+ exception_factory=_retry_exception_factory,
642
+ )
643
+
644
+ prepare_metadata = _pb_metadata_to_metadata_types(prepare_result.metadata)
645
+
646
+ retryable_excs = [_get_error_type(e) for e in retryable_errors]
647
+
648
+ pb_params = _format_execute_query_params(parameters, parameter_types)
649
+
650
+ request_body = {
651
+ "instance_name": instance_name,
652
+ "app_profile_id": app_profile_id,
653
+ "prepared_query": prepare_result.prepared_query,
654
+ "params": pb_params,
655
+ }
656
+ operation_timeout, attempt_timeout = _align_timeouts(
657
+ operation_timeout, attempt_timeout
658
+ )
659
+
660
+ return CrossSync.ExecuteQueryIterator(
661
+ self,
662
+ instance_id,
663
+ app_profile_id,
664
+ request_body,
665
+ prepare_metadata,
666
+ attempt_timeout,
667
+ operation_timeout,
668
+ retryable_excs=retryable_excs,
669
+ )
670
+
671
+ @CrossSync.convert(sync_name="__enter__")
672
+ async def __aenter__(self):
673
+ self._start_background_channel_refresh()
674
+ return self
675
+
676
+ @CrossSync.convert(sync_name="__exit__", replace_symbols={"__aexit__": "__exit__"})
677
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
678
+ await self.close()
679
+ await self._gapic_client.__aexit__(exc_type, exc_val, exc_tb)
680
+
681
+
682
+ @CrossSync.convert_class(sync_name="Table", add_mapping_for_name="Table")
683
+ class TableAsync:
684
+ """
685
+ Main Data API surface
686
+
687
+ Table object maintains table_id, and app_profile_id context, and passes them with
688
+ each call
689
+ """
690
+
691
+ @CrossSync.convert(
692
+ replace_symbols={"BigtableDataClientAsync": "BigtableDataClient"},
693
+ docstring_format_vars={
694
+ "LOOP_MESSAGE": (
695
+ "Must be created within an async context (running event loop)",
696
+ "",
697
+ ),
698
+ "RAISE_NO_LOOP": (
699
+ "RuntimeError: if called outside of an async context (no running event loop)",
700
+ "None",
701
+ ),
702
+ },
703
+ )
704
+ def __init__(
705
+ self,
706
+ client: BigtableDataClientAsync,
707
+ instance_id: str,
708
+ table_id: str,
709
+ app_profile_id: str | None = None,
710
+ *,
711
+ default_read_rows_operation_timeout: float = 600,
712
+ default_read_rows_attempt_timeout: float | None = 20,
713
+ default_mutate_rows_operation_timeout: float = 600,
714
+ default_mutate_rows_attempt_timeout: float | None = 60,
715
+ default_operation_timeout: float = 60,
716
+ default_attempt_timeout: float | None = 20,
717
+ default_read_rows_retryable_errors: Sequence[type[Exception]] = (
718
+ DeadlineExceeded,
719
+ ServiceUnavailable,
720
+ Aborted,
721
+ ),
722
+ default_mutate_rows_retryable_errors: Sequence[type[Exception]] = (
723
+ DeadlineExceeded,
724
+ ServiceUnavailable,
725
+ ),
726
+ default_retryable_errors: Sequence[type[Exception]] = (
727
+ DeadlineExceeded,
728
+ ServiceUnavailable,
729
+ ),
730
+ ):
731
+ """
732
+ Initialize a Table instance
733
+
734
+ {LOOP_MESSAGE}
735
+
736
+ Args:
737
+ instance_id: The Bigtable instance ID to associate with this client.
738
+ instance_id is combined with the client's project to fully
739
+ specify the instance
740
+ table_id: The ID of the table. table_id is combined with the
741
+ instance_id and the client's project to fully specify the table
742
+ app_profile_id: The app profile to associate with requests.
743
+ https://cloud.google.com/bigtable/docs/app-profiles
744
+ default_read_rows_operation_timeout: The default timeout for read rows
745
+ operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
746
+ default_read_rows_attempt_timeout: The default timeout for individual
747
+ read rows rpc requests, in seconds. If not set, defaults to 20 seconds
748
+ default_mutate_rows_operation_timeout: The default timeout for mutate rows
749
+ operations, in seconds. If not set, defaults to 600 seconds (10 minutes)
750
+ default_mutate_rows_attempt_timeout: The default timeout for individual
751
+ mutate rows rpc requests, in seconds. If not set, defaults to 60 seconds
752
+ default_operation_timeout: The default timeout for all other operations, in
753
+ seconds. If not set, defaults to 60 seconds
754
+ default_attempt_timeout: The default timeout for all other individual rpc
755
+ requests, in seconds. If not set, defaults to 20 seconds
756
+ default_read_rows_retryable_errors: a list of errors that will be retried
757
+ if encountered during read_rows and related operations.
758
+ Defaults to 4 (DeadlineExceeded), 14 (ServiceUnavailable), and 10 (Aborted)
759
+ default_mutate_rows_retryable_errors: a list of errors that will be retried
760
+ if encountered during mutate_rows and related operations.
761
+ Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
762
+ default_retryable_errors: a list of errors that will be retried if
763
+ encountered during all other operations.
764
+ Defaults to 4 (DeadlineExceeded) and 14 (ServiceUnavailable)
765
+ Raises:
766
+ {RAISE_NO_LOOP}
767
+ """
768
+ # NOTE: any changes to the signature of this method should also be reflected
769
+ # in client.get_table()
770
+ # validate timeouts
771
+ _validate_timeouts(
772
+ default_operation_timeout, default_attempt_timeout, allow_none=True
773
+ )
774
+ _validate_timeouts(
775
+ default_read_rows_operation_timeout,
776
+ default_read_rows_attempt_timeout,
777
+ allow_none=True,
778
+ )
779
+ _validate_timeouts(
780
+ default_mutate_rows_operation_timeout,
781
+ default_mutate_rows_attempt_timeout,
782
+ allow_none=True,
783
+ )
784
+
785
+ self.client = client
786
+ self.instance_id = instance_id
787
+ self.instance_name = self.client._gapic_client.instance_path(
788
+ self.client.project, instance_id
789
+ )
790
+ self.table_id = table_id
791
+ self.table_name = self.client._gapic_client.table_path(
792
+ self.client.project, instance_id, table_id
793
+ )
794
+ self.app_profile_id = app_profile_id
795
+
796
+ self.default_operation_timeout = default_operation_timeout
797
+ self.default_attempt_timeout = default_attempt_timeout
798
+ self.default_read_rows_operation_timeout = default_read_rows_operation_timeout
799
+ self.default_read_rows_attempt_timeout = default_read_rows_attempt_timeout
800
+ self.default_mutate_rows_operation_timeout = (
801
+ default_mutate_rows_operation_timeout
802
+ )
803
+ self.default_mutate_rows_attempt_timeout = default_mutate_rows_attempt_timeout
804
+
805
+ self.default_read_rows_retryable_errors = (
806
+ default_read_rows_retryable_errors or ()
807
+ )
808
+ self.default_mutate_rows_retryable_errors = (
809
+ default_mutate_rows_retryable_errors or ()
810
+ )
811
+ self.default_retryable_errors = default_retryable_errors or ()
812
+ try:
813
+ self._register_instance_future = CrossSync.create_task(
814
+ self.client._register_instance,
815
+ self.instance_id,
816
+ self,
817
+ sync_executor=self.client._executor,
818
+ )
819
+ except RuntimeError as e:
820
+ raise RuntimeError(
821
+ f"{self.__class__.__name__} must be created within an async event loop context."
822
+ ) from e
823
+
824
+ @CrossSync.convert(replace_symbols={"AsyncIterable": "Iterable"})
825
+ async def read_rows_stream(
826
+ self,
827
+ query: ReadRowsQuery,
828
+ *,
829
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
830
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
831
+ retryable_errors: Sequence[type[Exception]]
832
+ | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
833
+ ) -> AsyncIterable[Row]:
834
+ """
835
+ Read a set of rows from the table, based on the specified query.
836
+ Returns an iterator to asynchronously stream back row data.
837
+
838
+ Failed requests within operation_timeout will be retried based on the
839
+ retryable_errors list until operation_timeout is reached.
840
+
841
+ Args:
842
+ query: contains details about which rows to return
843
+ operation_timeout: the time budget for the entire operation, in seconds.
844
+ Failed requests will be retried within the budget.
845
+ Defaults to the Table's default_read_rows_operation_timeout
846
+ attempt_timeout: the time budget for an individual network request, in seconds.
847
+ If it takes longer than this time to complete, the request will be cancelled with
848
+ a DeadlineExceeded exception, and a retry will be attempted.
849
+ Defaults to the Table's default_read_rows_attempt_timeout.
850
+ If None, defaults to operation_timeout.
851
+ retryable_errors: a list of errors that will be retried if encountered.
852
+ Defaults to the Table's default_read_rows_retryable_errors
853
+ Returns:
854
+ AsyncIterable[Row]: an asynchronous iterator that yields rows returned by the query
855
+ Raises:
856
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
857
+ will be chained with a RetryExceptionGroup containing GoogleAPIError exceptions
858
+ from any retries that failed
859
+ google.api_core.exceptions.GoogleAPIError: raised if the request encounters an unrecoverable error
860
+ """
861
+ operation_timeout, attempt_timeout = _get_timeouts(
862
+ operation_timeout, attempt_timeout, self
863
+ )
864
+ retryable_excs = _get_retryable_errors(retryable_errors, self)
865
+
866
+ row_merger = CrossSync._ReadRowsOperation(
867
+ query,
868
+ self,
869
+ operation_timeout=operation_timeout,
870
+ attempt_timeout=attempt_timeout,
871
+ retryable_exceptions=retryable_excs,
872
+ )
873
+ return row_merger.start_operation()
874
+
875
+ @CrossSync.convert
876
+ async def read_rows(
877
+ self,
878
+ query: ReadRowsQuery,
879
+ *,
880
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
881
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
882
+ retryable_errors: Sequence[type[Exception]]
883
+ | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
884
+ ) -> list[Row]:
885
+ """
886
+ Read a set of rows from the table, based on the specified query.
887
+ Retruns results as a list of Row objects when the request is complete.
888
+ For streamed results, use read_rows_stream.
889
+
890
+ Failed requests within operation_timeout will be retried based on the
891
+ retryable_errors list until operation_timeout is reached.
892
+
893
+ Args:
894
+ query: contains details about which rows to return
895
+ operation_timeout: the time budget for the entire operation, in seconds.
896
+ Failed requests will be retried within the budget.
897
+ Defaults to the Table's default_read_rows_operation_timeout
898
+ attempt_timeout: the time budget for an individual network request, in seconds.
899
+ If it takes longer than this time to complete, the request will be cancelled with
900
+ a DeadlineExceeded exception, and a retry will be attempted.
901
+ Defaults to the Table's default_read_rows_attempt_timeout.
902
+ If None, defaults to operation_timeout.
903
+ If None, defaults to the Table's default_read_rows_attempt_timeout,
904
+ or the operation_timeout if that is also None.
905
+ retryable_errors: a list of errors that will be retried if encountered.
906
+ Defaults to the Table's default_read_rows_retryable_errors.
907
+ Returns:
908
+ list[Row]: a list of Rows returned by the query
909
+ Raises:
910
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
911
+ will be chained with a RetryExceptionGroup containing GoogleAPIError exceptions
912
+ from any retries that failed
913
+ google.api_core.exceptions.GoogleAPIError: raised if the request encounters an unrecoverable error
914
+ """
915
+ row_generator = await self.read_rows_stream(
916
+ query,
917
+ operation_timeout=operation_timeout,
918
+ attempt_timeout=attempt_timeout,
919
+ retryable_errors=retryable_errors,
920
+ )
921
+ return [row async for row in row_generator]
922
+
923
+ @CrossSync.convert
924
+ async def read_row(
925
+ self,
926
+ row_key: str | bytes,
927
+ *,
928
+ row_filter: RowFilter | None = None,
929
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
930
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
931
+ retryable_errors: Sequence[type[Exception]]
932
+ | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
933
+ ) -> Row | None:
934
+ """
935
+ Read a single row from the table, based on the specified key.
936
+
937
+ Failed requests within operation_timeout will be retried based on the
938
+ retryable_errors list until operation_timeout is reached.
939
+
940
+ Args:
941
+ query: contains details about which rows to return
942
+ operation_timeout: the time budget for the entire operation, in seconds.
943
+ Failed requests will be retried within the budget.
944
+ Defaults to the Table's default_read_rows_operation_timeout
945
+ attempt_timeout: the time budget for an individual network request, in seconds.
946
+ If it takes longer than this time to complete, the request will be cancelled with
947
+ a DeadlineExceeded exception, and a retry will be attempted.
948
+ Defaults to the Table's default_read_rows_attempt_timeout.
949
+ If None, defaults to operation_timeout.
950
+ retryable_errors: a list of errors that will be retried if encountered.
951
+ Defaults to the Table's default_read_rows_retryable_errors.
952
+ Returns:
953
+ Row | None: a Row object if the row exists, otherwise None
954
+ Raises:
955
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
956
+ will be chained with a RetryExceptionGroup containing GoogleAPIError exceptions
957
+ from any retries that failed
958
+ google.api_core.exceptions.GoogleAPIError: raised if the request encounters an unrecoverable error
959
+ """
960
+ if row_key is None:
961
+ raise ValueError("row_key must be string or bytes")
962
+ query = ReadRowsQuery(row_keys=row_key, row_filter=row_filter, limit=1)
963
+ results = await self.read_rows(
964
+ query,
965
+ operation_timeout=operation_timeout,
966
+ attempt_timeout=attempt_timeout,
967
+ retryable_errors=retryable_errors,
968
+ )
969
+ if len(results) == 0:
970
+ return None
971
+ return results[0]
972
+
973
+ @CrossSync.convert
974
+ async def read_rows_sharded(
975
+ self,
976
+ sharded_query: ShardedQuery,
977
+ *,
978
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
979
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
980
+ retryable_errors: Sequence[type[Exception]]
981
+ | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
982
+ ) -> list[Row]:
983
+ """
984
+ Runs a sharded query in parallel, then return the results in a single list.
985
+ Results will be returned in the order of the input queries.
986
+
987
+ This function is intended to be run on the results on a query.shard() call.
988
+ For example::
989
+
990
+ table_shard_keys = await table.sample_row_keys()
991
+ query = ReadRowsQuery(...)
992
+ shard_queries = query.shard(table_shard_keys)
993
+ results = await table.read_rows_sharded(shard_queries)
994
+
995
+ Args:
996
+ sharded_query: a sharded query to execute
997
+ operation_timeout: the time budget for the entire operation, in seconds.
998
+ Failed requests will be retried within the budget.
999
+ Defaults to the Table's default_read_rows_operation_timeout
1000
+ attempt_timeout: the time budget for an individual network request, in seconds.
1001
+ If it takes longer than this time to complete, the request will be cancelled with
1002
+ a DeadlineExceeded exception, and a retry will be attempted.
1003
+ Defaults to the Table's default_read_rows_attempt_timeout.
1004
+ If None, defaults to operation_timeout.
1005
+ retryable_errors: a list of errors that will be retried if encountered.
1006
+ Defaults to the Table's default_read_rows_retryable_errors.
1007
+ Returns:
1008
+ list[Row]: a list of Rows returned by the query
1009
+ Raises:
1010
+ ShardedReadRowsExceptionGroup: if any of the queries failed
1011
+ ValueError: if the query_list is empty
1012
+ """
1013
+ if not sharded_query:
1014
+ raise ValueError("empty sharded_query")
1015
+ operation_timeout, attempt_timeout = _get_timeouts(
1016
+ operation_timeout, attempt_timeout, self
1017
+ )
1018
+ # make sure each rpc stays within overall operation timeout
1019
+ rpc_timeout_generator = _attempt_timeout_generator(
1020
+ operation_timeout, operation_timeout
1021
+ )
1022
+
1023
+ # limit the number of concurrent requests using a semaphore
1024
+ concurrency_sem = CrossSync.Semaphore(_CONCURRENCY_LIMIT)
1025
+
1026
+ @CrossSync.convert
1027
+ async def read_rows_with_semaphore(query):
1028
+ async with concurrency_sem:
1029
+ # calculate new timeout based on time left in overall operation
1030
+ shard_timeout = next(rpc_timeout_generator)
1031
+ if shard_timeout <= 0:
1032
+ raise DeadlineExceeded(
1033
+ "Operation timeout exceeded before starting query"
1034
+ )
1035
+ return await self.read_rows(
1036
+ query,
1037
+ operation_timeout=shard_timeout,
1038
+ attempt_timeout=min(attempt_timeout, shard_timeout),
1039
+ retryable_errors=retryable_errors,
1040
+ )
1041
+
1042
+ routine_list = [
1043
+ partial(read_rows_with_semaphore, query) for query in sharded_query
1044
+ ]
1045
+ batch_result = await CrossSync.gather_partials(
1046
+ routine_list,
1047
+ return_exceptions=True,
1048
+ sync_executor=self.client._executor,
1049
+ )
1050
+
1051
+ # collect results and errors
1052
+ error_dict = {}
1053
+ shard_idx = 0
1054
+ results_list = []
1055
+ for result in batch_result:
1056
+ if isinstance(result, Exception):
1057
+ error_dict[shard_idx] = result
1058
+ elif isinstance(result, BaseException):
1059
+ # BaseException not expected; raise immediately
1060
+ raise result
1061
+ else:
1062
+ results_list.extend(result)
1063
+ shard_idx += 1
1064
+ if error_dict:
1065
+ # if any sub-request failed, raise an exception instead of returning results
1066
+ raise ShardedReadRowsExceptionGroup(
1067
+ [
1068
+ FailedQueryShardError(idx, sharded_query[idx], e)
1069
+ for idx, e in error_dict.items()
1070
+ ],
1071
+ results_list,
1072
+ len(sharded_query),
1073
+ )
1074
+ return results_list
1075
+
1076
+ @CrossSync.convert
1077
+ async def row_exists(
1078
+ self,
1079
+ row_key: str | bytes,
1080
+ *,
1081
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
1082
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
1083
+ retryable_errors: Sequence[type[Exception]]
1084
+ | TABLE_DEFAULT = TABLE_DEFAULT.READ_ROWS,
1085
+ ) -> bool:
1086
+ """
1087
+ Return a boolean indicating whether the specified row exists in the table.
1088
+ uses the filters: chain(limit cells per row = 1, strip value)
1089
+
1090
+ Args:
1091
+ row_key: the key of the row to check
1092
+ operation_timeout: the time budget for the entire operation, in seconds.
1093
+ Failed requests will be retried within the budget.
1094
+ Defaults to the Table's default_read_rows_operation_timeout
1095
+ attempt_timeout: the time budget for an individual network request, in seconds.
1096
+ If it takes longer than this time to complete, the request will be cancelled with
1097
+ a DeadlineExceeded exception, and a retry will be attempted.
1098
+ Defaults to the Table's default_read_rows_attempt_timeout.
1099
+ If None, defaults to operation_timeout.
1100
+ retryable_errors: a list of errors that will be retried if encountered.
1101
+ Defaults to the Table's default_read_rows_retryable_errors.
1102
+ Returns:
1103
+ bool: a bool indicating whether the row exists
1104
+ Raises:
1105
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
1106
+ will be chained with a RetryExceptionGroup containing GoogleAPIError exceptions
1107
+ from any retries that failed
1108
+ google.api_core.exceptions.GoogleAPIError: raised if the request encounters an unrecoverable error
1109
+ """
1110
+ if row_key is None:
1111
+ raise ValueError("row_key must be string or bytes")
1112
+
1113
+ strip_filter = StripValueTransformerFilter(flag=True)
1114
+ limit_filter = CellsRowLimitFilter(1)
1115
+ chain_filter = RowFilterChain(filters=[limit_filter, strip_filter])
1116
+ query = ReadRowsQuery(row_keys=row_key, limit=1, row_filter=chain_filter)
1117
+ results = await self.read_rows(
1118
+ query,
1119
+ operation_timeout=operation_timeout,
1120
+ attempt_timeout=attempt_timeout,
1121
+ retryable_errors=retryable_errors,
1122
+ )
1123
+ return len(results) > 0
1124
+
1125
+ @CrossSync.convert
1126
+ async def sample_row_keys(
1127
+ self,
1128
+ *,
1129
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1130
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1131
+ retryable_errors: Sequence[type[Exception]]
1132
+ | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1133
+ ) -> RowKeySamples:
1134
+ """
1135
+ Return a set of RowKeySamples that delimit contiguous sections of the table of
1136
+ approximately equal size
1137
+
1138
+ RowKeySamples output can be used with ReadRowsQuery.shard() to create a sharded query that
1139
+ can be parallelized across multiple backend nodes read_rows and read_rows_stream
1140
+ requests will call sample_row_keys internally for this purpose when sharding is enabled
1141
+
1142
+ RowKeySamples is simply a type alias for list[tuple[bytes, int]]; a list of
1143
+ row_keys, along with offset positions in the table
1144
+
1145
+ Args:
1146
+ operation_timeout: the time budget for the entire operation, in seconds.
1147
+ Failed requests will be retried within the budget.i
1148
+ Defaults to the Table's default_operation_timeout
1149
+ attempt_timeout: the time budget for an individual network request, in seconds.
1150
+ If it takes longer than this time to complete, the request will be cancelled with
1151
+ a DeadlineExceeded exception, and a retry will be attempted.
1152
+ Defaults to the Table's default_attempt_timeout.
1153
+ If None, defaults to operation_timeout.
1154
+ retryable_errors: a list of errors that will be retried if encountered.
1155
+ Defaults to the Table's default_retryable_errors.
1156
+ Returns:
1157
+ RowKeySamples: a set of RowKeySamples the delimit contiguous sections of the table
1158
+ Raises:
1159
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
1160
+ will be chained with a RetryExceptionGroup containing GoogleAPIError exceptions
1161
+ from any retries that failed
1162
+ google.api_core.exceptions.GoogleAPIError: raised if the request encounters an unrecoverable error
1163
+ """
1164
+ # prepare timeouts
1165
+ operation_timeout, attempt_timeout = _get_timeouts(
1166
+ operation_timeout, attempt_timeout, self
1167
+ )
1168
+ attempt_timeout_gen = _attempt_timeout_generator(
1169
+ attempt_timeout, operation_timeout
1170
+ )
1171
+ # prepare retryable
1172
+ retryable_excs = _get_retryable_errors(retryable_errors, self)
1173
+ predicate = retries.if_exception_type(*retryable_excs)
1174
+
1175
+ sleep_generator = retries.exponential_sleep_generator(0.01, 2, 60)
1176
+
1177
+ @CrossSync.convert
1178
+ async def execute_rpc():
1179
+ results = await self.client._gapic_client.sample_row_keys(
1180
+ table_name=self.table_name,
1181
+ app_profile_id=self.app_profile_id,
1182
+ timeout=next(attempt_timeout_gen),
1183
+ retry=None,
1184
+ )
1185
+ return [(s.row_key, s.offset_bytes) async for s in results]
1186
+
1187
+ return await CrossSync.retry_target(
1188
+ execute_rpc,
1189
+ predicate,
1190
+ sleep_generator,
1191
+ operation_timeout,
1192
+ exception_factory=_retry_exception_factory,
1193
+ )
1194
+
1195
+ @CrossSync.convert(replace_symbols={"MutationsBatcherAsync": "MutationsBatcher"})
1196
+ def mutations_batcher(
1197
+ self,
1198
+ *,
1199
+ flush_interval: float | None = 5,
1200
+ flush_limit_mutation_count: int | None = 1000,
1201
+ flush_limit_bytes: int = 20 * _MB_SIZE,
1202
+ flow_control_max_mutation_count: int = 100_000,
1203
+ flow_control_max_bytes: int = 100 * _MB_SIZE,
1204
+ batch_operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
1205
+ batch_attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
1206
+ batch_retryable_errors: Sequence[type[Exception]]
1207
+ | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
1208
+ ) -> "MutationsBatcherAsync":
1209
+ """
1210
+ Returns a new mutations batcher instance.
1211
+
1212
+ Can be used to iteratively add mutations that are flushed as a group,
1213
+ to avoid excess network calls
1214
+
1215
+ Args:
1216
+ flush_interval: Automatically flush every flush_interval seconds. If None,
1217
+ a table default will be used
1218
+ flush_limit_mutation_count: Flush immediately after flush_limit_mutation_count
1219
+ mutations are added across all entries. If None, this limit is ignored.
1220
+ flush_limit_bytes: Flush immediately after flush_limit_bytes bytes are added.
1221
+ flow_control_max_mutation_count: Maximum number of inflight mutations.
1222
+ flow_control_max_bytes: Maximum number of inflight bytes.
1223
+ batch_operation_timeout: timeout for each mutate_rows operation, in seconds.
1224
+ Defaults to the Table's default_mutate_rows_operation_timeout
1225
+ batch_attempt_timeout: timeout for each individual request, in seconds.
1226
+ Defaults to the Table's default_mutate_rows_attempt_timeout.
1227
+ If None, defaults to batch_operation_timeout.
1228
+ batch_retryable_errors: a list of errors that will be retried if encountered.
1229
+ Defaults to the Table's default_mutate_rows_retryable_errors.
1230
+ Returns:
1231
+ MutationsBatcherAsync: a MutationsBatcherAsync context manager that can batch requests
1232
+ """
1233
+ return CrossSync.MutationsBatcher(
1234
+ self,
1235
+ flush_interval=flush_interval,
1236
+ flush_limit_mutation_count=flush_limit_mutation_count,
1237
+ flush_limit_bytes=flush_limit_bytes,
1238
+ flow_control_max_mutation_count=flow_control_max_mutation_count,
1239
+ flow_control_max_bytes=flow_control_max_bytes,
1240
+ batch_operation_timeout=batch_operation_timeout,
1241
+ batch_attempt_timeout=batch_attempt_timeout,
1242
+ batch_retryable_errors=batch_retryable_errors,
1243
+ )
1244
+
1245
+ @CrossSync.convert
1246
+ async def mutate_row(
1247
+ self,
1248
+ row_key: str | bytes,
1249
+ mutations: list[Mutation] | Mutation,
1250
+ *,
1251
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1252
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1253
+ retryable_errors: Sequence[type[Exception]]
1254
+ | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1255
+ ):
1256
+ """
1257
+ Mutates a row atomically.
1258
+
1259
+ Cells already present in the row are left unchanged unless explicitly changed
1260
+ by ``mutation``.
1261
+
1262
+ Idempotent operations (i.e, all mutations have an explicit timestamp) will be
1263
+ retried on server failure. Non-idempotent operations will not.
1264
+
1265
+ Args:
1266
+ row_key: the row to apply mutations to
1267
+ mutations: the set of mutations to apply to the row
1268
+ operation_timeout: the time budget for the entire operation, in seconds.
1269
+ Failed requests will be retried within the budget.
1270
+ Defaults to the Table's default_operation_timeout
1271
+ attempt_timeout: the time budget for an individual network request, in seconds.
1272
+ If it takes longer than this time to complete, the request will be cancelled with
1273
+ a DeadlineExceeded exception, and a retry will be attempted.
1274
+ Defaults to the Table's default_attempt_timeout.
1275
+ If None, defaults to operation_timeout.
1276
+ retryable_errors: a list of errors that will be retried if encountered.
1277
+ Only idempotent mutations will be retried. Defaults to the Table's
1278
+ default_retryable_errors.
1279
+ Raises:
1280
+ google.api_core.exceptions.DeadlineExceeded: raised after operation timeout
1281
+ will be chained with a RetryExceptionGroup containing all
1282
+ GoogleAPIError exceptions from any retries that failed
1283
+ google.api_core.exceptions.GoogleAPIError: raised on non-idempotent operations that cannot be
1284
+ safely retried.
1285
+ ValueError: if invalid arguments are provided
1286
+ """
1287
+ operation_timeout, attempt_timeout = _get_timeouts(
1288
+ operation_timeout, attempt_timeout, self
1289
+ )
1290
+
1291
+ if not mutations:
1292
+ raise ValueError("No mutations provided")
1293
+ mutations_list = mutations if isinstance(mutations, list) else [mutations]
1294
+
1295
+ if all(mutation.is_idempotent() for mutation in mutations_list):
1296
+ # mutations are all idempotent and safe to retry
1297
+ predicate = retries.if_exception_type(
1298
+ *_get_retryable_errors(retryable_errors, self)
1299
+ )
1300
+ else:
1301
+ # mutations should not be retried
1302
+ predicate = retries.if_exception_type()
1303
+
1304
+ sleep_generator = retries.exponential_sleep_generator(0.01, 2, 60)
1305
+
1306
+ target = partial(
1307
+ self.client._gapic_client.mutate_row,
1308
+ row_key=row_key.encode("utf-8") if isinstance(row_key, str) else row_key,
1309
+ mutations=[mutation._to_pb() for mutation in mutations_list],
1310
+ table_name=self.table_name,
1311
+ app_profile_id=self.app_profile_id,
1312
+ timeout=attempt_timeout,
1313
+ retry=None,
1314
+ )
1315
+ return await CrossSync.retry_target(
1316
+ target,
1317
+ predicate,
1318
+ sleep_generator,
1319
+ operation_timeout,
1320
+ exception_factory=_retry_exception_factory,
1321
+ )
1322
+
1323
+ @CrossSync.convert
1324
+ async def bulk_mutate_rows(
1325
+ self,
1326
+ mutation_entries: list[RowMutationEntry],
1327
+ *,
1328
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
1329
+ attempt_timeout: float | None | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
1330
+ retryable_errors: Sequence[type[Exception]]
1331
+ | TABLE_DEFAULT = TABLE_DEFAULT.MUTATE_ROWS,
1332
+ ):
1333
+ """
1334
+ Applies mutations for multiple rows in a single batched request.
1335
+
1336
+ Each individual RowMutationEntry is applied atomically, but separate entries
1337
+ may be applied in arbitrary order (even for entries targetting the same row)
1338
+ In total, the row_mutations can contain at most 100000 individual mutations
1339
+ across all entries
1340
+
1341
+ Idempotent entries (i.e., entries with mutations with explicit timestamps)
1342
+ will be retried on failure. Non-idempotent will not, and will reported in a
1343
+ raised exception group
1344
+
1345
+ Args:
1346
+ mutation_entries: the batches of mutations to apply
1347
+ Each entry will be applied atomically, but entries will be applied
1348
+ in arbitrary order
1349
+ operation_timeout: the time budget for the entire operation, in seconds.
1350
+ Failed requests will be retried within the budget.
1351
+ Defaults to the Table's default_mutate_rows_operation_timeout
1352
+ attempt_timeout: the time budget for an individual network request, in seconds.
1353
+ If it takes longer than this time to complete, the request will be cancelled with
1354
+ a DeadlineExceeded exception, and a retry will be attempted.
1355
+ Defaults to the Table's default_mutate_rows_attempt_timeout.
1356
+ If None, defaults to operation_timeout.
1357
+ retryable_errors: a list of errors that will be retried if encountered.
1358
+ Defaults to the Table's default_mutate_rows_retryable_errors
1359
+ Raises:
1360
+ MutationsExceptionGroup: if one or more mutations fails
1361
+ Contains details about any failed entries in .exceptions
1362
+ ValueError: if invalid arguments are provided
1363
+ """
1364
+ operation_timeout, attempt_timeout = _get_timeouts(
1365
+ operation_timeout, attempt_timeout, self
1366
+ )
1367
+ retryable_excs = _get_retryable_errors(retryable_errors, self)
1368
+
1369
+ operation = CrossSync._MutateRowsOperation(
1370
+ self.client._gapic_client,
1371
+ self,
1372
+ mutation_entries,
1373
+ operation_timeout,
1374
+ attempt_timeout,
1375
+ retryable_exceptions=retryable_excs,
1376
+ )
1377
+ await operation.start()
1378
+
1379
+ @CrossSync.convert
1380
+ async def check_and_mutate_row(
1381
+ self,
1382
+ row_key: str | bytes,
1383
+ predicate: RowFilter | None,
1384
+ *,
1385
+ true_case_mutations: Mutation | list[Mutation] | None = None,
1386
+ false_case_mutations: Mutation | list[Mutation] | None = None,
1387
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1388
+ ) -> bool:
1389
+ """
1390
+ Mutates a row atomically based on the output of a predicate filter
1391
+
1392
+ Non-idempotent operation: will not be retried
1393
+
1394
+ Args:
1395
+ row_key: the key of the row to mutate
1396
+ predicate: the filter to be applied to the contents of the specified row.
1397
+ Depending on whether or not any results are yielded,
1398
+ either true_case_mutations or false_case_mutations will be executed.
1399
+ If None, checks that the row contains any values at all.
1400
+ true_case_mutations:
1401
+ Changes to be atomically applied to the specified row if
1402
+ predicate yields at least one cell when
1403
+ applied to row_key. Entries are applied in order,
1404
+ meaning that earlier mutations can be masked by later
1405
+ ones. Must contain at least one entry if
1406
+ false_case_mutations is empty, and at most 100000.
1407
+ false_case_mutations:
1408
+ Changes to be atomically applied to the specified row if
1409
+ predicate_filter does not yield any cells when
1410
+ applied to row_key. Entries are applied in order,
1411
+ meaning that earlier mutations can be masked by later
1412
+ ones. Must contain at least one entry if
1413
+ `true_case_mutations` is empty, and at most 100000.
1414
+ operation_timeout: the time budget for the entire operation, in seconds.
1415
+ Failed requests will not be retried. Defaults to the Table's default_operation_timeout
1416
+ Returns:
1417
+ bool indicating whether the predicate was true or false
1418
+ Raises:
1419
+ google.api_core.exceptions.GoogleAPIError: exceptions from grpc call
1420
+ """
1421
+ operation_timeout, _ = _get_timeouts(operation_timeout, None, self)
1422
+ if true_case_mutations is not None and not isinstance(
1423
+ true_case_mutations, list
1424
+ ):
1425
+ true_case_mutations = [true_case_mutations]
1426
+ true_case_list = [m._to_pb() for m in true_case_mutations or []]
1427
+ if false_case_mutations is not None and not isinstance(
1428
+ false_case_mutations, list
1429
+ ):
1430
+ false_case_mutations = [false_case_mutations]
1431
+ false_case_list = [m._to_pb() for m in false_case_mutations or []]
1432
+ result = await self.client._gapic_client.check_and_mutate_row(
1433
+ true_mutations=true_case_list,
1434
+ false_mutations=false_case_list,
1435
+ predicate_filter=predicate._to_pb() if predicate is not None else None,
1436
+ row_key=row_key.encode("utf-8") if isinstance(row_key, str) else row_key,
1437
+ table_name=self.table_name,
1438
+ app_profile_id=self.app_profile_id,
1439
+ timeout=operation_timeout,
1440
+ retry=None,
1441
+ )
1442
+ return result.predicate_matched
1443
+
1444
+ @CrossSync.convert
1445
+ async def read_modify_write_row(
1446
+ self,
1447
+ row_key: str | bytes,
1448
+ rules: ReadModifyWriteRule | list[ReadModifyWriteRule],
1449
+ *,
1450
+ operation_timeout: float | TABLE_DEFAULT = TABLE_DEFAULT.DEFAULT,
1451
+ ) -> Row:
1452
+ """
1453
+ Reads and modifies a row atomically according to input ReadModifyWriteRules,
1454
+ and returns the contents of all modified cells
1455
+
1456
+ The new value for the timestamp is the greater of the existing timestamp or
1457
+ the current server time.
1458
+
1459
+ Non-idempotent operation: will not be retried
1460
+
1461
+ Args:
1462
+ row_key: the key of the row to apply read/modify/write rules to
1463
+ rules: A rule or set of rules to apply to the row.
1464
+ Rules are applied in order, meaning that earlier rules will affect the
1465
+ results of later ones.
1466
+ operation_timeout: the time budget for the entire operation, in seconds.
1467
+ Failed requests will not be retried.
1468
+ Defaults to the Table's default_operation_timeout.
1469
+ Returns:
1470
+ Row: a Row containing cell data that was modified as part of the operation
1471
+ Raises:
1472
+ google.api_core.exceptions.GoogleAPIError: exceptions from grpc call
1473
+ ValueError: if invalid arguments are provided
1474
+ """
1475
+ operation_timeout, _ = _get_timeouts(operation_timeout, None, self)
1476
+ if operation_timeout <= 0:
1477
+ raise ValueError("operation_timeout must be greater than 0")
1478
+ if rules is not None and not isinstance(rules, list):
1479
+ rules = [rules]
1480
+ if not rules:
1481
+ raise ValueError("rules must contain at least one item")
1482
+ result = await self.client._gapic_client.read_modify_write_row(
1483
+ rules=[rule._to_pb() for rule in rules],
1484
+ row_key=row_key.encode("utf-8") if isinstance(row_key, str) else row_key,
1485
+ table_name=self.table_name,
1486
+ app_profile_id=self.app_profile_id,
1487
+ timeout=operation_timeout,
1488
+ retry=None,
1489
+ )
1490
+ # construct Row from result
1491
+ return Row._from_pb(result.row)
1492
+
1493
+ @CrossSync.convert
1494
+ async def close(self):
1495
+ """
1496
+ Called to close the Table instance and release any resources held by it.
1497
+ """
1498
+ if self._register_instance_future:
1499
+ self._register_instance_future.cancel()
1500
+ await self.client._remove_instance_registration(self.instance_id, self)
1501
+
1502
+ @CrossSync.convert(sync_name="__enter__")
1503
+ async def __aenter__(self):
1504
+ """
1505
+ Implement async context manager protocol
1506
+
1507
+ Ensure registration task has time to run, so that
1508
+ grpc channels will be warmed for the specified instance
1509
+ """
1510
+ if self._register_instance_future:
1511
+ await self._register_instance_future
1512
+ return self
1513
+
1514
+ @CrossSync.convert(sync_name="__exit__")
1515
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
1516
+ """
1517
+ Implement async context manager protocol
1518
+
1519
+ Unregister this instance with the client, so that
1520
+ grpc channels will no longer be warmed
1521
+ """
1522
+ await self.close()