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

Commit 1d2f042

Browse files
committed
test: fix async system tests in prerelease deps
1 parent 4335fbf commit 1d2f042

File tree

6 files changed

+57
-84
lines changed

6 files changed

+57
-84
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/services/spanner/transports/grpc_asyncio.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ async def intercept_unary_unary(self, continuation, client_call_details, request
8787
"metadata": grpc_request["metadata"],
8888
},
8989
)
90-
response = continuation(client_call_details, request)
90+
response = await continuation(client_call_details, request)
9191
if logging_enabled: # pragma: NO COVER
9292
response_metadata = await response.trailing_metadata()
9393
# Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
@@ -323,7 +323,7 @@ def __init__(
323323
)
324324

325325
self._interceptor = _LoggingClientAIOInterceptor()
326-
# self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
326+
self._grpc_channel._unary_unary_interceptors.append(self._interceptor)
327327
self._logged_channel = self._grpc_channel
328328
self._wrap_with_kind = (
329329
"kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters

0 commit comments

Comments
 (0)