Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit 71c3000

Browse files
authored
feat: Async consistency polling harness (#1142)
* feat: Async consistency polling harness * Fixed AsyncMock issue in Python 3.7 * Reworked async_consistency and added async client to __init__.py * linting * addressed review feedback * linting
1 parent b7f1f90 commit 71c3000

7 files changed

Lines changed: 695 additions & 5 deletions

File tree

google/cloud/bigtable/admin_v2/overlay/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
)
3535

3636
from .services.bigtable_table_admin import (
37+
BigtableTableAdminAsyncClient,
3738
BigtableTableAdminClient,
3839
)
3940

4041
__all__ = (
4142
"RestoreTableOperation",
43+
"BigtableTableAdminAsyncClient",
4244
"BigtableTableAdminClient",
4345
)

google/cloud/bigtable/admin_v2/overlay/services/bigtable_table_admin/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
# TODO: Add the async client after owlbot changes.
1616

17+
from .async_client import BigtableTableAdminAsyncClient
1718
from .client import BigtableTableAdminClient
1819

19-
__all__ = ("BigtableTableAdminClient",)
20+
__all__ = (
21+
"BigtableTableAdminAsyncClient",
22+
"BigtableTableAdminClient",
23+
)
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
# Copyright 2025 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+
# -*- coding: utf-8 -*-
16+
# Copyright 2025 Google LLC
17+
#
18+
# Licensed under the Apache License, Version 2.0 (the "License");
19+
# you may not use this file except in compliance with the License.
20+
# You may obtain a copy of the License at
21+
#
22+
# http://www.apache.org/licenses/LICENSE-2.0
23+
#
24+
# Unless required by applicable law or agreed to in writing, software
25+
# distributed under the License is distributed on an "AS IS" BASIS,
26+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27+
# See the License for the specific language governing permissions and
28+
# limitations under the License.
29+
30+
import copy
31+
import functools
32+
33+
from typing import Callable, Optional, Sequence, Tuple, Union
34+
from google.api_core import gapic_v1
35+
from google.api_core import retry as retries
36+
37+
try:
38+
OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None]
39+
except AttributeError: # pragma: NO COVER
40+
OptionalRetry = Union[retries.AsyncRetry, object, None] # type: ignore
41+
42+
from google.api_core import client_options as client_options_lib
43+
from google.auth import credentials as ga_credentials # type: ignore
44+
45+
from google.cloud.bigtable.admin_v2.types import bigtable_table_admin
46+
47+
from google.cloud.bigtable.admin_v2.services.bigtable_table_admin import (
48+
async_client as base_client,
49+
)
50+
from google.cloud.bigtable.admin_v2.services.bigtable_table_admin.transports.base import (
51+
BigtableTableAdminTransport,
52+
)
53+
from google.cloud.bigtable.admin_v2.overlay.types import (
54+
async_consistency,
55+
wait_for_consistency_request,
56+
)
57+
58+
from google.cloud.bigtable.gapic_version import __version__ as bigtable_version
59+
60+
61+
DEFAULT_CLIENT_INFO = copy.copy(base_client.DEFAULT_CLIENT_INFO)
62+
DEFAULT_CLIENT_INFO.client_library_version = f"{bigtable_version}-admin-overlay-async"
63+
64+
65+
class BigtableTableAdminAsyncClient(base_client.BaseBigtableTableAdminAsyncClient):
66+
def __init__(
67+
self,
68+
*,
69+
credentials: Optional[ga_credentials.Credentials] = None,
70+
transport: Optional[
71+
Union[
72+
str,
73+
BigtableTableAdminTransport,
74+
Callable[..., BigtableTableAdminTransport],
75+
]
76+
] = "grpc_asyncio",
77+
client_options: Optional[client_options_lib.ClientOptions] = None,
78+
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
79+
) -> None:
80+
"""Instantiates the Bigtable table admin async client.
81+
82+
Args:
83+
credentials (Optional[google.auth.credentials.Credentials]): The
84+
authorization credentials to attach to requests. These
85+
credentials identify the application to the service; if none
86+
are specified, the client will attempt to ascertain the
87+
credentials from the environment.
88+
transport (Optional[Union[str,BigtableTableAdminTransport,Callable[..., BigtableTableAdminTransport]]]):
89+
The transport to use, or a Callable that constructs and returns a new transport to use.
90+
If a Callable is given, it will be called with the same set of initialization
91+
arguments as used in the BigtableTableAdminTransport constructor.
92+
If set to None, a transport is chosen automatically.
93+
client_options (Optional[Union[google.api_core.client_options.ClientOptions, dict]]):
94+
Custom options for the client.
95+
96+
1. The ``api_endpoint`` property can be used to override the
97+
default endpoint provided by the client when ``transport`` is
98+
not explicitly provided. Only if this property is not set and
99+
``transport`` was not explicitly provided, the endpoint is
100+
determined by the GOOGLE_API_USE_MTLS_ENDPOINT environment
101+
variable, which have one of the following values:
102+
"always" (always use the default mTLS endpoint), "never" (always
103+
use the default regular endpoint) and "auto" (auto-switch to the
104+
default mTLS endpoint if client certificate is present; this is
105+
the default value).
106+
107+
2. If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
108+
is "true", then the ``client_cert_source`` property can be used
109+
to provide a client certificate for mTLS transport. If
110+
not provided, the default SSL client certificate will be used if
111+
present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
112+
set, no client certificate will be used.
113+
114+
3. The ``universe_domain`` property can be used to override the
115+
default "googleapis.com" universe. Note that ``api_endpoint``
116+
property still takes precedence; and ``universe_domain`` is
117+
currently not supported for mTLS.
118+
119+
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
120+
The client info used to send a user-agent string along with
121+
API requests. If ``None``, then default info will be used.
122+
Generally, you only need to set this if you're developing
123+
your own client library.
124+
125+
Raises:
126+
google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
127+
creation failed for any reason.
128+
"""
129+
super(BigtableTableAdminAsyncClient, self).__init__(
130+
credentials=credentials,
131+
transport=transport,
132+
client_options=client_options,
133+
client_info=client_info,
134+
)
135+
136+
async def wait_for_consistency(
137+
self,
138+
request: Optional[
139+
Union[wait_for_consistency_request.WaitForConsistencyRequest, dict]
140+
] = None,
141+
*,
142+
name: Optional[str] = None,
143+
retry: OptionalRetry = gapic_v1.method.DEFAULT,
144+
timeout: Union[float, object] = gapic_v1.method.DEFAULT,
145+
metadata: Sequence[Tuple[str, Union[str, bytes]]] = (),
146+
) -> bool:
147+
r"""Blocks until the mutations for the specified Table that have been
148+
made before the call have been replicated or reads using an app profile with `DataBoostIsolationReadOnly`
149+
can see all writes committed before the token was created. This is done by generating
150+
a consistency token for the Table, then polling :meth:`check_consistency`
151+
for the specified table until the call returns True.
152+
153+
.. code-block:: python
154+
155+
# This snippet should be regarded as a code template only.
156+
# It will require modifications to work:
157+
# - It may require correct/in-range values for request initialization.
158+
# - It may require specifying regional endpoints when creating the service
159+
# client as shown in:
160+
# https://googleapis.dev/python/google-api-core/latest/client_options.html
161+
from google.cloud.bigtable import admin_v2
162+
163+
async def sample_wait_for_consistency():
164+
# Create a client
165+
client = admin_v2.BigtableTableAdminAsyncClient()
166+
167+
# Initialize request argument(s)
168+
request = admin_v2.WaitForConsistencyRequest(
169+
name="name_value",
170+
)
171+
172+
# Make the request
173+
print("Waiting for operation to complete...")
174+
175+
response = await client.wait_for_replication(request=request)
176+
177+
# Handle the response
178+
print(response)
179+
180+
Args:
181+
request (Union[google.cloud.bigtable.admin_v2.overlay.types.WaitForConsistencyRequest, dict]):
182+
The request object.
183+
name (str):
184+
Required. The unique name of the Table for which to
185+
create a consistency token. Values are of the form
186+
``projects/{project}/instances/{instance}/tables/{table}``.
187+
188+
This corresponds to the ``name`` field
189+
on the ``request`` instance; if ``request`` is provided, this
190+
should not be set.
191+
retry (google.api_core.retry.Retry): Designation of what errors, if any,
192+
should be retried.
193+
timeout (float): The timeout for this request.
194+
metadata (Sequence[Tuple[str, Union[str, bytes]]]): Key/value pairs which should be
195+
sent along with the request as metadata. Normally, each value must be of type `str`,
196+
but for metadata keys ending with the suffix `-bin`, the corresponding values must
197+
be of type `bytes`.
198+
199+
Returns:
200+
bool:
201+
If the `standard_read_remote_writes` mode is specified in the request object, returns
202+
`True` after the mutations of the specified table have been fully replicated. If the
203+
`data_boost_read_local_writes` mode is specified in the request object, returns `True`
204+
after reads using an app profile with `DataBoostIsolationReadOnly` can see all writes
205+
committed before the token was created.
206+
207+
Raises:
208+
google.api_core.GoogleAPICallError: If the operation errors or if
209+
the timeout is reached before the operation completes.
210+
"""
211+
# Create or coerce a protobuf request object.
212+
# - Quick check: If we got a request object, we should *not* have
213+
# gotten any keyword arguments that map to the request.
214+
flattened_params = [name]
215+
has_flattened_params = (
216+
len([param for param in flattened_params if param is not None]) > 0
217+
)
218+
if request is not None and has_flattened_params:
219+
raise ValueError(
220+
"If the `request` argument is set, then none of "
221+
"the individual field arguments should be set."
222+
)
223+
224+
# - Use the request object if provided (there's no risk of modifying the input as
225+
# there are no flattened fields), or create one.
226+
if not isinstance(
227+
request, wait_for_consistency_request.WaitForConsistencyRequest
228+
):
229+
request = wait_for_consistency_request.WaitForConsistencyRequest(request)
230+
# If we have keyword arguments corresponding to fields on the
231+
# request, apply these.
232+
if name is not None:
233+
request.name = name
234+
235+
# Generate the consistency token.
236+
generate_consistency_token_request = (
237+
bigtable_table_admin.GenerateConsistencyTokenRequest(
238+
name=request.name,
239+
)
240+
)
241+
242+
generate_consistency_response = await self.generate_consistency_token(
243+
generate_consistency_token_request,
244+
retry=retry,
245+
timeout=timeout,
246+
metadata=metadata,
247+
)
248+
249+
# Create the CheckConsistencyRequest object.
250+
check_consistency_request = bigtable_table_admin.CheckConsistencyRequest(
251+
name=request.name,
252+
consistency_token=generate_consistency_response.consistency_token,
253+
)
254+
255+
# Since the default values of StandardReadRemoteWrites and DataBoostReadLocalWrites evaluate to
256+
# False in proto plus, we cannot do a simple "if request.standard_read_remote_writes" to check
257+
# whether or not that field is defined in the original request object.
258+
mode_oneof_field = request._pb.WhichOneof("mode")
259+
if mode_oneof_field:
260+
setattr(
261+
check_consistency_request,
262+
mode_oneof_field,
263+
getattr(request, mode_oneof_field),
264+
)
265+
266+
check_consistency_call = functools.partial(
267+
self.check_consistency,
268+
check_consistency_request,
269+
retry=retry,
270+
timeout=timeout,
271+
metadata=metadata,
272+
)
273+
274+
# Block and wait until the polling harness returns True.
275+
check_consistency_future = (
276+
async_consistency._AsyncCheckConsistencyPollingFuture(
277+
check_consistency_call
278+
)
279+
)
280+
return await check_consistency_future.result()

0 commit comments

Comments
 (0)