Skip to content

Commit f7dcad3

Browse files
committed
make async900 label point directly to the async900 rule. Save bools instead of nodes since we don't need to warn on them or refer to them. Warn on each yield, add test case for that.
1 parent 82895a1 commit f7dcad3

File tree

3 files changed

+20
-12
lines changed

3 files changed

+20
-12
lines changed

docs/rules.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
4545
- **ASYNC250**: Builtin ``input()`` should not be called from async function. Wrap in ``[trio/anyio].to_thread.run_sync()`` or ``asyncio.loop.run_in_executor()``.
4646
- **ASYNC251**: ``time.sleep(...)`` should not be called from async function. Use ``[trio/anyio/asyncio].sleep(...)``.
4747

48-
.. _async900:
4948

5049
Optional rules disabled by default
5150
==================================
5251

52+
.. _async900:
53+
5354
- **ASYNC900**: Async generator without ``@asynccontextmanager`` not allowed. You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed. See https://github.com/python-trio/flake8-async/issues/211 and https://discuss.python.org/t/using-exceptiongroup-at-anthropic-experience-report/20888/6 for discussion.
5455
- **ASYNC910**: Exit or ``return`` from async function with no guaranteed checkpoint or exception since function definition. You might want to enable this on a codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.
5556
- **ASYNC911**: Exit, ``yield`` or ``return`` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)

flake8_async/visitors/visitors.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,35 +290,33 @@ class Visitor119(Flake8AsyncVisitor):
290290

291291
def __init__(self, *args: Any, **kwargs: Any):
292292
super().__init__(*args, **kwargs)
293-
self.unsafe_function: ast.AsyncFunctionDef | None = None
294-
self.contextmanager: ast.With | ast.AsyncWith | None = None
293+
self.unsafe_function: bool = False
294+
self.contextmanager: bool = False
295295

296296
def visit_AsyncFunctionDef(
297297
self, node: ast.AsyncFunctionDef | ast.FunctionDef | ast.Lambda
298298
):
299299
self.save_state(node, "unsafe_function", "contextmanager")
300-
self.contextmanager = None
300+
self.contextmanager = False
301301
if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
302302
node, "asynccontextmanager"
303303
):
304-
self.unsafe_function = node
304+
self.unsafe_function = True
305305
else:
306-
self.unsafe_function = None
306+
self.unsafe_function = False
307307

308308
def visit_With(self, node: ast.With | ast.AsyncWith):
309309
self.save_state(node, "contextmanager")
310-
self.contextmanager = node
310+
self.contextmanager = True
311311

312312
def visit_Yield(self, node: ast.Yield):
313-
if self.unsafe_function is not None and self.contextmanager is not None:
314-
# Decision point: the error could point to the method, or context manager,
315-
# or the yield.
313+
if self.unsafe_function and self.contextmanager:
316314
self.error(node)
317-
# only warn once per method (?)
318-
self.unsafe_function = None
319315

320316
visit_AsyncWith = visit_With
321317
visit_FunctionDef = visit_AsyncFunctionDef
318+
# it's not possible to yield or open context managers in lambda's, so this
319+
# one isn't strictly needed afaik.
322320
visit_Lambda = visit_AsyncFunctionDef
323321

324322

tests/eval_files/async119.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ async def async_with():
1313
yield # error: 8
1414

1515

16+
async def warn_on_yeach_yield():
17+
with open(""):
18+
yield # error: 8
19+
yield # error: 8
20+
with open(""):
21+
yield # error: 8
22+
yield # error: 8
23+
24+
1625
async def yield_not_in_contextmanager():
1726
yield
1827
with open(""):

0 commit comments

Comments
 (0)