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

Commit e239d56

Browse files
committed
adding sync tests
1 parent 2336e64 commit e239d56

3 files changed

Lines changed: 40 additions & 9 deletions

File tree

google/cloud/bigtable/data/_async/metrics_interceptor.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# limitations under the License
1414
from __future__ import annotations
1515

16+
from typing import Sequence
17+
1618
import time
1719
from functools import wraps
1820
from google.cloud.bigtable.data._metrics.data_model import (
@@ -90,15 +92,17 @@ def _end_attempt(operation, exc, metadata):
9092
async def _get_metadata(source) -> dict[str, str|bytes] | None:
9193
"""Helper to extract metadata from a call or RpcError"""
9294
try:
93-
if isinstance(source, AioRpcError) and CrossSync.is_async:
94-
return {
95-
k:v for k,v
96-
in list(source.trailing_metadata()) + list(source.initial_metadata())
97-
}
95+
if CrossSync.is_async:
96+
# grpc.aio returns metadata in Metadata objects
97+
if isinstance(source, AioRpcError):
98+
metadata = list(source.trailing_metadata()) + list(source.initial_metadata())
99+
else:
100+
metadata = list(await source.trailing_metadata()) + list(await source.initial_metadata())
98101
else:
99-
return (await source.trailing_metadata() or {}) + (
100-
await source.initial_metadata() or {}
101-
)
102+
# sync grpc returns metadata as a sequence of tuples
103+
metadata: Sequence[tuple[str. str|bytes]] = source.trailing_metadata() + source.initial_metadata()
104+
# convert metadata to dict format
105+
return {k:v for k,v in metadata}
102106
except Exception:
103107
# ignore errors while fetching metadata
104108
return None

google/cloud/bigtable/data/_metrics/data_model.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from dataclasses import dataclass
2626
from dataclasses import field
2727
from grpc import StatusCode
28+
from grpc import RpcError
2829
from grpc.aio import AioRpcError
2930

3031
import google.cloud.bigtable.data.exceptions as bt_exceptions
@@ -390,7 +391,7 @@ def _exc_to_status(exc: BaseException) -> StatusCode:
390391
and exc.__cause__.grpc_status_code is not None
391392
):
392393
return exc.__cause__.grpc_status_code
393-
if isinstance(exc, AioRpcError):
394+
if isinstance(exc, AioRpcError) or isinstance(exc, RpcError):
394395
return exc.code()
395396
return StatusCode.UNKNOWN
396397

tests/system/data/test_metrics_async.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
else:
4444
from grpc import UnaryUnaryClientInterceptor
4545
from grpc import UnaryStreamClientInterceptor
46+
from grpc import RpcError
47+
from grpc import intercept_channel
4648

4749
__CROSS_SYNC_OUTPUT__ = "tests.system.data.test_metrics_autogen"
4850

@@ -75,6 +77,7 @@ def __repr__(self):
7577
return f"{self.__class__}(completed_operations={len(self.completed_operations)}, cancelled_operations={len(self.cancelled_operations)}, completed_attempts={len(self.completed_attempts)}"
7678

7779

80+
@CrossSync.convert_class
7881
class _ErrorInjectorInterceptor(
7982
UnaryUnaryClientInterceptor, UnaryStreamClientInterceptor
8083
):
@@ -93,11 +96,13 @@ def clear(self):
9396
self._exc_list.clear()
9497
self.fail_mid_stream = False
9598

99+
@CrossSync.convert
96100
async def intercept_unary_unary(self, continuation, client_call_details, request):
97101
if self._exc_list:
98102
raise self._exc_list.pop(0)
99103
return await continuation(client_call_details, request)
100104

105+
@CrossSync.convert
101106
async def intercept_unary_stream(self, continuation, client_call_details, request):
102107
if not self.fail_mid_stream and self._exc_list:
103108
raise self._exc_list.pop(0)
@@ -113,9 +118,12 @@ def __init__(self, call, exc_to_raise):
113118
self._exc = exc_to_raise
114119
self._raised = False
115120

121+
@CrossSync.convert(sync_name="__iter__")
116122
def __aiter__(self):
117123
return self
118124

125+
126+
@CrossSync.convert(sync_name="__next__", replace_symbols={"__anext__": "__next__"})
119127
async def __anext__(self):
120128
if not self._raised:
121129
self._raised = True
@@ -155,6 +163,16 @@ def _make_exception(self, status, cluster_id=None, zone_id=None):
155163
if CrossSync.is_async:
156164
metadata = Metadata(metadata) if metadata else Metadata()
157165
return AioRpcError(status, Metadata(), metadata)
166+
else:
167+
exc = RpcError(status)
168+
exc.trailing_metadata = lambda: [metadata] if metadata else []
169+
exc.initial_metadata = lambda: []
170+
exc.code = lambda: status
171+
exc.details = lambda: None
172+
def _result():
173+
raise exc
174+
exc.result = _result
175+
return exc
158176

159177
@pytest.fixture(scope="session")
160178
def handler(self):
@@ -182,6 +200,10 @@ async def client(self, error_injector):
182200
client.transport.grpc_channel._unary_stream_interceptors.append(
183201
error_injector
184202
)
203+
else:
204+
# inject interceptor after bigtable metrics interceptors
205+
metrics_channel = client.transport._grpc_channel._channel._channel
206+
client.transport._grpc_channel._channel._channel = intercept_channel(metrics_channel, error_injector)
185207
yield client
186208

187209
@CrossSync.convert
@@ -413,6 +435,7 @@ async def test_read_rows_stream(self, table, temp_rows, handler, cluster_config)
413435
assert attempt.grpc_throttling_time_ns == 0 # TODO: confirm
414436

415437
@CrossSync.pytest
438+
@CrossSync.convert(replace_symbols={"__anext__": "__next__", "aclose": "close"})
416439
async def test_read_rows_stream_failure_closed(
417440
self, table, temp_rows, handler, error_injector
418441
):
@@ -1575,6 +1598,7 @@ async def test_sample_row_keys(self, table, temp_rows, handler, cluster_config):
15751598
assert attempt.application_blocking_time_ns == 0
15761599
assert attempt.grpc_throttling_time_ns == 0 # TODO: confirm
15771600

1601+
@CrossSync.drop
15781602
@CrossSync.pytest
15791603
async def test_sample_row_keys_failure_cancelled(
15801604
self, table, temp_rows, handler, error_injector
@@ -1760,6 +1784,7 @@ async def test_read_modify_write(self, table, temp_rows, handler, cluster_config
17601784
assert attempt.application_blocking_time_ns == 0
17611785
assert attempt.grpc_throttling_time_ns == 0 # TODO: confirm
17621786

1787+
@CrossSync.drop
17631788
@CrossSync.pytest
17641789
async def test_read_modify_write_failure_cancelled(
17651790
self, table, temp_rows, handler, error_injector
@@ -1934,6 +1959,7 @@ async def test_check_and_mutate_row(
19341959
assert attempt.application_blocking_time_ns == 0
19351960
assert attempt.grpc_throttling_time_ns == 0 # TODO: confirm
19361961

1962+
@CrossSync.drop
19371963
@CrossSync.pytest
19381964
async def test_check_and_mutate_row_failure_cancelled(
19391965
self, table, temp_rows, handler, error_injector

0 commit comments

Comments
 (0)