@@ -128,66 +128,8 @@ def _compile_property(
128128 # lambda passed to a thread pool executor is pure synchronous.
129129 pre_statements : list [ast .stmt ] = []
130130
131- # Helper to extract `await self.<name>()` occurrences from AST
132- # expressions and replace them with local variables. This lets us
133- # run a synchronous lambda in `run_in_executor` without `await`
134- # nodes embedded inside it.
135- counter = 0
136-
137- def _replace_awaits_with_locals (node : ast .expr ) -> ast .expr :
138- nonlocal counter
139-
140- if isinstance (node , ast .Await ):
141- inner = node .value
142- if (
143- isinstance (inner , ast .Call )
144- and isinstance (inner .func , ast .Attribute )
145- and isinstance (inner .func .value , ast .Name )
146- and inner .func .value .id == "self"
147- ):
148- # produce a unique variable name and precompute the await
149- counter += 1
150- var_name = f"{ COMPILED_VAR_PREFIX } { counter } "
151- assign = ast .Assign (
152- targets = [ast .Name (id = var_name , ctx = ast .Store ())],
153- value = node ,
154- )
155- pre_statements .append (assign )
156- return ast .Name (id = var_name , ctx = ast .Load ())
157- return node
158-
159- # Recurse into common composite nodes
160- if isinstance (node , ast .Call ):
161- new_args = [_replace_awaits_with_locals (a ) for a in node .args ]
162- new_keywords = [
163- ast .keyword (
164- arg = k .arg , value = _replace_awaits_with_locals (k .value )
165- )
166- for k in node .keywords
167- ]
168- return ast .Call (
169- func = node .func , args = new_args , keywords = new_keywords
170- )
171- if isinstance (node , ast .Dict ):
172- new_keys = [
173- _replace_awaits_with_locals (k ) if k is not None else None
174- for k in node .keys
175- ]
176- new_values = [
177- _replace_awaits_with_locals (v ) for v in node .values
178- ]
179- return ast .Dict (keys = new_keys , values = new_values )
180- if isinstance (node , ast .List ):
181- return ast .List (
182- elts = [_replace_awaits_with_locals (e ) for e in node .elts ],
183- ctx = node .ctx ,
184- )
185- if isinstance (node , ast .Tuple ):
186- return ast .Tuple (
187- elts = [_replace_awaits_with_locals (e ) for e in node .elts ],
188- ctx = node .ctx ,
189- )
190- return node
131+ # Counter for generating unique variable names (mutable list)
132+ counter = [0 ]
191133
192134 args : list [ast .expr ] = []
193135
@@ -210,7 +152,9 @@ def _replace_awaits_with_locals(node: ast.expr) -> ast.expr:
210152 for i , value in enumerate (args_data ):
211153 raw_val_ast = self ._astify (value , aio = aio )
212154 if aio :
213- val_ast = _replace_awaits_with_locals (raw_val_ast )
155+ val_ast = self ._replace_awaits_with_locals (
156+ raw_val_ast , pre_statements , counter
157+ )
214158 var_name = f"{ COMPILED_ARG_PREFIX } { i } "
215159 assign = ast .Assign (
216160 targets = [ast .Name (id = var_name , ctx = ast .Store ())],
@@ -229,7 +173,9 @@ def _replace_awaits_with_locals(node: ast.expr) -> ast.expr:
229173 # Replace any awaited accessors with local precomputed
230174 # variables and assign all values to local variables so
231175 # that the executor lambda can be synchronous.
232- val_ast = _replace_awaits_with_locals (raw_val_ast )
176+ val_ast = self ._replace_awaits_with_locals (
177+ raw_val_ast , pre_statements , counter
178+ )
233179 # Use named variables for top-level keyword args to make
234180 # the generated code more readable and deterministic.
235181 var_name = f"{ COMPILED_VAR_PREFIX } { key } "
@@ -494,6 +440,101 @@ def _replace_awaits_with_locals(node: ast.expr) -> ast.expr:
494440 )
495441 return func_def
496442
443+ def _replace_awaits_with_locals (
444+ self ,
445+ node : ast .expr ,
446+ pre_statements : list [ast .stmt ],
447+ counter : list [int ],
448+ ) -> ast .expr :
449+ """Replace await expressions with local variable assignments.
450+
451+ Recursively processes AST nodes to replace `ast.Await` expressions
452+ with local variable assignments. This ensures that synchronous lambdas
453+ executed in a thread pool do not contain `await` nodes.
454+
455+ Args:
456+ node: AST expression node to process
457+ pre_statements: List to accumulate pre-computation statements
458+ counter: Single-element list used as mutable counter for var naming
459+
460+ Returns:
461+ Transformed AST expression node
462+ """
463+ if isinstance (node , ast .Await ):
464+ inner = node .value
465+ if (
466+ isinstance (inner , ast .Call )
467+ and isinstance (inner .func , ast .Attribute )
468+ and isinstance (inner .func .value , ast .Name )
469+ and inner .func .value .id == "self"
470+ ):
471+ # produce a unique variable name and precompute the await
472+ counter [0 ] += 1
473+ var_name = f"{ COMPILED_VAR_PREFIX } { counter [0 ]} "
474+ assign = ast .Assign (
475+ targets = [ast .Name (id = var_name , ctx = ast .Store ())],
476+ value = node ,
477+ )
478+ pre_statements .append (assign )
479+ return ast .Name (id = var_name , ctx = ast .Load ())
480+ return node
481+
482+ # Recurse into common composite nodes
483+ if isinstance (node , ast .Call ):
484+ new_args = [
485+ self ._replace_awaits_with_locals (a , pre_statements , counter )
486+ for a in node .args
487+ ]
488+ new_keywords = [
489+ ast .keyword (
490+ arg = k .arg ,
491+ value = self ._replace_awaits_with_locals (
492+ k .value , pre_statements , counter
493+ ),
494+ )
495+ for k in node .keywords
496+ ]
497+ return ast .Call (
498+ func = node .func , args = new_args , keywords = new_keywords
499+ )
500+ if isinstance (node , ast .Dict ):
501+ new_keys = [
502+ (
503+ self ._replace_awaits_with_locals (
504+ k , pre_statements , counter
505+ )
506+ if k is not None
507+ else None
508+ )
509+ for k in node .keys
510+ ]
511+ new_values = [
512+ self ._replace_awaits_with_locals (v , pre_statements , counter )
513+ for v in node .values
514+ ]
515+ return ast .Dict (keys = new_keys , values = new_values )
516+ if isinstance (node , ast .List ):
517+ return ast .List (
518+ elts = [
519+ self ._replace_awaits_with_locals (
520+ e , pre_statements , counter
521+ )
522+ for e in node .elts
523+ ],
524+ ctx = node .ctx ,
525+ )
526+ if isinstance (node , ast .Tuple ):
527+ return ast .Tuple (
528+ elts = [
529+ self ._replace_awaits_with_locals (
530+ e , pre_statements , counter
531+ )
532+ for e in node .elts
533+ ],
534+ ctx = node .ctx ,
535+ )
536+ return node
537+
497538 def _compile_constant_property (
498539 self ,
499540 name : str ,
0 commit comments