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

Commit 21e4a73

Browse files
committed
test: fix async system tests in prerelease deps
1 parent 92aacbb commit 21e4a73

File tree

17 files changed

+140
-169
lines changed

17 files changed

+140
-169
lines changed

.coveragerc

Lines changed: 0 additions & 40 deletions
This file was deleted.

.cross_sync/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Additionally, CrossSync provides method implementations that work equivalently i
3737
- `CrossSync.gather_partials()`
3838
- `CrossSync.wait()`
3939
- `CrossSync.condition_wait()`
40-
- `CrossSync,event_wait()`
40+
- `CrossSync.event_wait()`
4141
- `CrossSync.create_task()`
4242
- `CrossSync.retry_target()`
4343
- `CrossSync.retry_target_stream()`
@@ -63,13 +63,13 @@ CrossSync provides a set of annotations to mark up async classes, to guide the g
6363
### Code Generation
6464

6565
Generation can be initiated using `nox -s generate_sync`
66-
from the root of the project. This will find all classes with the `__CROSS_SYNC_OUTPUT__ = "path/to/output"`
66+
from the root of the project. This will find all classes with the `__CROSS_SYNC_OUTPUT__ = "path/to/output"`
6767
annotation, and generate a sync version of classes marked with `@CrossSync.convert_sync` at the output path.
6868

6969
There is a unit test at `tests/unit/data/test_sync_up_to_date.py` that verifies that the generated code is up to date
7070

7171
## Architecture
7272

7373
CrossSync is made up of two parts:
74-
- the runtime shims and annotations live in `/google/cloud/bigtable/_cross_sync`
74+
- the runtime shims and annotations live in `/google/cloud/aio/_cross_sync`
7575
- the code generation logic lives in `/.cross_sync/` in the repo root

.cross_sync/generate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ def convert_files_in_dir(directory: str) -> set[CrossSyncOutputFile]:
8282
file_transformer = CrossSyncFileProcessor()
8383
# run each file through ast transformation to find all annotated classes
8484
for file_path in files:
85-
ast_tree = ast.parse(open(file_path, encoding="utf-8-sig").read())
85+
with open(file_path, encoding="utf-8-sig") as f:
86+
ast_tree = ast.parse(f.read())
8687
output_path = file_transformer.get_output_path(ast_tree)
8788
if output_path is not None:
8889
# contains __CROSS_SYNC_OUTPUT__ annotation

.cross_sync/transformers.py

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,35 @@
3838
from _decorators import AstDecorator
3939

4040

41-
class SymbolReplacer(ast.NodeTransformer):
41+
from _decorators import AstDecorator
42+
43+
44+
class CrossSyncTransformer(ast.NodeTransformer):
45+
"""
46+
Base class for CrossSync AST transformers that provides shared logic
47+
for rewriting imports and symbol names.
48+
"""
49+
50+
def visit_ImportFrom(self, node):
51+
if node.module:
52+
if "_async" in node.module:
53+
node.module = (
54+
node.module.replace("._async", "")
55+
.replace("_async.", "")
56+
.replace("_async", "")
57+
)
58+
if "async_client" in node.module:
59+
node.module = node.module.replace("async_client", "client")
60+
# Also replace AsyncClient with Client in the names!
61+
for alias in node.names:
62+
if "AsyncClient" in alias.name:
63+
alias.name = alias.name.replace("AsyncClient", "Client")
64+
if alias.name == "AsyncRetry":
65+
alias.name = "Retry"
66+
return self.generic_visit(node)
67+
68+
69+
class SymbolReplacer(CrossSyncTransformer):
4270
"""
4371
Replaces all instances of a symbol in an AST with a replacement
4472
@@ -63,17 +91,6 @@ def visit_Attribute(self, node):
6391
)
6492

6593

66-
def visit_ImportFrom(self, node):
67-
if node.module:
68-
if "_async" in node.module: node.module = node.module.replace("._async", "").replace("_async.", "").replace("_async", "")
69-
if "async_client" in node.module: node.module = node.module.replace("async_client", "client")
70-
# Also replace AsyncClient with Client in the names!
71-
for alias in node.names:
72-
if "AsyncClient" in alias.name:
73-
alias.name = alias.name.replace("AsyncClient", "Client")
74-
if alias.name == "AsyncRetry":
75-
alias.name = "Retry"
76-
return self.generic_visit(node)
7794

7895
def visit_AsyncFunctionDef(self, node):
7996
"""
@@ -106,7 +123,7 @@ def visit_Constant(self, node):
106123
return node
107124

108125

109-
class AsyncToSync(ast.NodeTransformer):
126+
class AsyncToSync(CrossSyncTransformer):
110127
"""
111128
Replaces or strips all async keywords from a given AST
112129
"""
@@ -143,17 +160,6 @@ def visit_AsyncWith(self, node):
143160
)
144161

145162

146-
def visit_ImportFrom(self, node):
147-
if node.module:
148-
if "_async" in node.module: node.module = node.module.replace("._async", "").replace("_async.", "").replace("_async", "")
149-
if "async_client" in node.module: node.module = node.module.replace("async_client", "client")
150-
# Also replace AsyncClient with Client in the names!
151-
for alias in node.names:
152-
if "AsyncClient" in alias.name:
153-
alias.name = alias.name.replace("AsyncClient", "Client")
154-
if alias.name == "AsyncRetry":
155-
alias.name = "Retry"
156-
return self.generic_visit(node)
157163

158164
def visit_AsyncFunctionDef(self, node):
159165
"""
@@ -278,7 +284,7 @@ def _is_async_check(self, node) -> bool:
278284
return False
279285

280286

281-
class CrossSyncFileProcessor(ast.NodeTransformer):
287+
class CrossSyncFileProcessor(CrossSyncTransformer):
282288
"""
283289
Visits a file, looking for __CROSS_SYNC_OUTPUT__ annotations
284290
@@ -360,17 +366,6 @@ def visit_FunctionDef(self, node):
360366
return self.visit_AsyncFunctionDef(node)
361367

362368

363-
def visit_ImportFrom(self, node):
364-
if node.module:
365-
if "_async" in node.module: node.module = node.module.replace("._async", "").replace("_async.", "").replace("_async", "")
366-
if "async_client" in node.module: node.module = node.module.replace("async_client", "client")
367-
# Also replace AsyncClient with Client in the names!
368-
for alias in node.names:
369-
if "AsyncClient" in alias.name:
370-
alias.name = alias.name.replace("AsyncClient", "Client")
371-
if alias.name == "AsyncRetry":
372-
alias.name = "Retry"
373-
return self.generic_visit(node)
374369

375370
def visit_AsyncFunctionDef(self, node):
376371
"""

google/cloud/aio/_cross_sync/_decorators.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def get_for_node(cls, node: ast.Call | ast.Attribute | ast.Name) -> "AstDecorato
127127
if not isinstance(root_attr, ast.Attribute):
128128
raise ValueError("Unexpected decorator format")
129129
# extract the module and decorator names
130-
if "CrossSync" in ast.dump(root_attr):
130+
if cls._is_cross_sync_node(root_attr):
131131
decorator_name = root_attr.attr
132132
got_kwargs: dict[str, Any] = (
133133
{str(kw.arg): cls._convert_ast_to_py(kw.value) for kw in node.keywords}
@@ -183,6 +183,21 @@ def _convert_ast_to_py(cls, ast_node: ast.expr | None) -> Any:
183183
# unsupported node type
184184
return ast_node
185185

186+
@staticmethod
187+
def _is_cross_sync_node(node: ast.AST) -> bool:
188+
"""
189+
Check if an AST node refers to a CrossSync attribute.
190+
"""
191+
import ast
192+
193+
if isinstance(node, ast.Attribute):
194+
if isinstance(node.value, ast.Name) and node.value.id == "CrossSync":
195+
return True
196+
return AstDecorator._is_cross_sync_node(node.value)
197+
if isinstance(node, ast.Call):
198+
return AstDecorator._is_cross_sync_node(node.func)
199+
return False
200+
186201

187202
class ConvertClass(AstDecorator):
188203
"""
@@ -255,7 +270,9 @@ def sync_ast_transform(self, wrapped_node, transformers_globals):
255270
# strip CrossSync decorators
256271
if hasattr(wrapped_node, "decorator_list"):
257272
wrapped_node.decorator_list = [
258-
d for d in wrapped_node.decorator_list if "CrossSync" not in ast.dump(d)
273+
d
274+
for d in wrapped_node.decorator_list
275+
if not self._is_cross_sync_node(d)
259276
]
260277
else:
261278
wrapped_node.decorator_list = []

google/cloud/spanner_v1/batch.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@
2020
import functools
2121
import time
2222
from typing import List, Optional
23-
2423
from google.api_core.exceptions import InternalServerError
25-
24+
from google.cloud.spanner_v1._helpers import _retry, _retry_on_aborted_exception
2625
from google.cloud.spanner_v1._helpers import (
2726
AtomicCounter,
2827
_check_rst_stream_error,
@@ -32,8 +31,6 @@
3231
_merge_Transaction_Options,
3332
_metadata_with_leader_aware_routing,
3433
_metadata_with_prefix,
35-
_retry,
36-
_retry_on_aborted_exception,
3734
_SessionWrapper,
3835
_validate_client_context,
3936
)

google/cloud/spanner_v1/client.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,36 +32,34 @@
3232
import threading
3333
from typing import Optional
3434
import warnings
35-
3635
import google.api_core.client_options
3736
from google.api_core.gapic_v1 import client_info
3837
from google.auth.credentials import AnonymousCredentials
3938
import grpc
40-
4139
from google.cloud.client import ClientWithProject
4240
from google.cloud.spanner_admin_database_v1 import (
4341
DatabaseAdminClient as DatabaseAdminClient,
4442
)
4543
from google.cloud.spanner_admin_database_v1.services.database_admin.transports.grpc import (
4644
DatabaseAdminGrpcTransport,
4745
)
48-
from google.cloud.spanner_admin_instance_v1 import (
49-
ListInstanceConfigsRequest,
50-
ListInstancesRequest,
51-
)
5246
from google.cloud.spanner_admin_instance_v1 import (
5347
InstanceAdminClient as InstanceAdminClient,
5448
)
5549
from google.cloud.spanner_admin_instance_v1.services.instance_admin.transports.grpc import (
5650
InstanceAdminGrpcTransport,
5751
)
52+
from google.cloud.spanner_admin_instance_v1 import (
53+
ListInstanceConfigsRequest,
54+
ListInstancesRequest,
55+
)
56+
from google.cloud.spanner_v1.instance import Instance
5857
from google.cloud.spanner_v1._helpers import (
5958
_merge_query_options,
6059
_metadata_with_prefix,
6160
_validate_client_context,
6261
)
6362
from google.cloud.spanner_v1.gapic_version import __version__
64-
from google.cloud.spanner_v1.instance import Instance
6563
from google.cloud.spanner_v1.metrics.constants import METRIC_EXPORT_INTERVAL_MS
6664
from google.cloud.spanner_v1.metrics.metrics_exporter import (
6765
CloudMonitoringMetricsExporter,

0 commit comments

Comments
 (0)