Skip to content

Commit d29a33d

Browse files
authored
ASYNC100 no longer triggers on context managers containing a yield (#228)
* ASYNC100 no longer triggers on context managers containing a `yield` * replace yield with return and enable async910, to fix eval_file noqa
1 parent a8e61dc commit d29a33d

File tree

9 files changed

+70
-29
lines changed

9 files changed

+70
-29
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22
*[CalVer, YY.month.patch](https://calver.org/)*
33

4+
## 24.3.6
5+
- ASYNC100 no longer triggers if a context manager contains a `yield`.
6+
47
## 24.3.5
58
- ASYNC102 (no await inside finally or critical except) no longer raises warnings for calls to `aclose()` on objects in trio/anyio code. See https://github.com/python-trio/flake8-async/issues/156
69

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pip install flake8-async
2525
## List of warnings
2626
- **ASYNC100**: A `with [trio/anyio].fail_after(...):` or `with [trio/anyio].move_on_after(...):`
2727
context does not contain any `await` statements. This makes it pointless, as
28-
the timeout can only be triggered by a checkpoint.
28+
the timeout can only be triggered by a checkpoint. This check also allows `yield` statements, since checkpoints can happen in the caller we yield to.
2929
- **ASYNC101**: `yield` inside a trio/anyio nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
3030
- **ASYNC102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError` unless you use a shielded cancel scope with a timeout. This is currently not able to detect asyncio shields.
3131
- **ASYNC103**: `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError`, or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled`/`anyio.get_cancelled_exc_class()`/`asyncio.exceptions.CancelledError` before.

flake8_async/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838

3939
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
40-
__version__ = "24.3.5"
40+
__version__ = "24.3.6"
4141

4242

4343
# taken from https://github.com/Zac-HD/shed

flake8_async/visitors/visitor100.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ def visit_For(self, node: cst.For):
7474
if node.asynchronous is not None:
7575
self.checkpoint()
7676

77-
def visit_Await(self, node: cst.Await | cst.For | cst.With):
77+
def visit_Await(self, node: cst.Await | cst.Yield):
7878
self.checkpoint()
7979

80+
visit_Yield = visit_Await
81+
8082
def visit_FunctionDef(self, node: cst.FunctionDef):
8183
self.save_state(node, "has_checkpoint_stack", copy=True)
8284
self.has_checkpoint_stack = []

tests/autofix_files/async100.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,9 @@ async def more_nested_tests():
114114
with contextlib.suppress(Exception):
115115
with open("blah") as file:
116116
print("foo")
117+
118+
119+
# Don't trigger for blocks with a yield statement
120+
async def foo():
121+
with trio.fail_after(1):
122+
yield

tests/autofix_files/noqa.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
11
# AUTOFIX
22
# NOASYNCIO - does not trigger on ASYNC100
3-
# ARG --enable=ASYNC100,ASYNC911
3+
# ARG --enable=ASYNC100,ASYNC911,ASYNC910
44
from typing import Any
55

66
import trio
77

88

9+
def foo() -> bool:
10+
return False
11+
12+
913
# fmt: off
1014
async def foo_no_noqa():
11-
await trio.lowlevel.checkpoint()
1215
# ASYNC100: 9, 'trio', 'fail_after'
13-
yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
16+
if foo():
17+
await trio.lowlevel.checkpoint()
18+
return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
1419
await trio.lowlevel.checkpoint()
1520

1621

1722
async def foo_noqa_bare():
1823
with trio.fail_after(5): # noqa
19-
yield # noqa
24+
if foo():
25+
return # noqa
2026
await trio.lowlevel.checkpoint()
2127

2228

2329
async def foo_noqa_100():
2430
with trio.fail_after(5): # noqa: ASYNC100
25-
await trio.lowlevel.checkpoint()
26-
yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
31+
if foo():
32+
await trio.lowlevel.checkpoint()
33+
return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
2734
await trio.lowlevel.checkpoint()
2835

2936

3037
async def foo_noqa_911():
3138
# ASYNC100: 9, 'trio', 'fail_after'
32-
yield # noqa: ASYNC911
39+
if foo():
40+
return # noqa: ASYNC910
3341
await trio.lowlevel.checkpoint()
3442

3543

3644
async def foo_noqa_100_911():
3745
with trio.fail_after(5): # noqa: ASYNC100, ASYNC911
38-
yield # noqa: ASYNC911
46+
if foo():
47+
return # noqa: ASYNC910
3948
await trio.lowlevel.checkpoint()
4049

4150

4251
async def foo_noqa_100_911_500():
4352
with trio.fail_after(5): # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
44-
yield # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
53+
if foo():
54+
return # noqa: ASYNC100, ASYNC910 , ASYNC500,,,
4555
await trio.lowlevel.checkpoint()
4656
# fmt: on
4757

tests/autofix_files/noqa.py.diff

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
11
---
22
+++
3-
@@ x,8 x,9 @@
3+
@@ x,9 x,10 @@
44

55
# fmt: off
66
async def foo_no_noqa():
77
- with trio.fail_after(5): # ASYNC100: 9, 'trio', 'fail_after'
8-
- yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
9-
+ await trio.lowlevel.checkpoint()
8+
- if foo():
9+
- return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
1010
+ # ASYNC100: 9, 'trio', 'fail_after'
11-
+ yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
11+
+ if foo():
12+
+ await trio.lowlevel.checkpoint()
13+
+ return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
1214
await trio.lowlevel.checkpoint()
1315

1416

15-
@@ x,13 x,14 @@
16-
17+
@@ x,14 x,15 @@
1718
async def foo_noqa_100():
1819
with trio.fail_after(5): # noqa: ASYNC100
19-
+ await trio.lowlevel.checkpoint()
20-
yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
20+
if foo():
21+
+ await trio.lowlevel.checkpoint()
22+
return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
2123
await trio.lowlevel.checkpoint()
2224

2325

2426
async def foo_noqa_911():
2527
- with trio.fail_after(5): # ASYNC100: 9, 'trio', 'fail_after'
26-
- yield # noqa: ASYNC911
28+
- if foo():
29+
- return # noqa: ASYNC910
2730
+ # ASYNC100: 9, 'trio', 'fail_after'
28-
+ yield # noqa: ASYNC911
31+
+ if foo():
32+
+ return # noqa: ASYNC910
2933
await trio.lowlevel.checkpoint()
3034

3135

tests/eval_files/async100.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,9 @@ async def more_nested_tests():
114114
with contextlib.suppress(Exception):
115115
with open("blah") as file:
116116
print("foo")
117+
118+
119+
# Don't trigger for blocks with a yield statement
120+
async def foo():
121+
with trio.fail_after(1):
122+
yield

tests/eval_files/noqa.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,55 @@
11
# AUTOFIX
22
# NOASYNCIO - does not trigger on ASYNC100
3-
# ARG --enable=ASYNC100,ASYNC911
3+
# ARG --enable=ASYNC100,ASYNC911,ASYNC910
44
from typing import Any
55

66
import trio
77

88

9+
def foo() -> bool:
10+
return False
11+
12+
913
# fmt: off
1014
async def foo_no_noqa():
1115
with trio.fail_after(5): # ASYNC100: 9, 'trio', 'fail_after'
12-
yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
16+
if foo():
17+
return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
1318
await trio.lowlevel.checkpoint()
1419

1520

1621
async def foo_noqa_bare():
1722
with trio.fail_after(5): # noqa
18-
yield # noqa
23+
if foo():
24+
return # noqa
1925
await trio.lowlevel.checkpoint()
2026

2127

2228
async def foo_noqa_100():
2329
with trio.fail_after(5): # noqa: ASYNC100
24-
yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
30+
if foo():
31+
return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
2532
await trio.lowlevel.checkpoint()
2633

2734

2835
async def foo_noqa_911():
2936
with trio.fail_after(5): # ASYNC100: 9, 'trio', 'fail_after'
30-
yield # noqa: ASYNC911
37+
if foo():
38+
return # noqa: ASYNC910
3139
await trio.lowlevel.checkpoint()
3240

3341

3442
async def foo_noqa_100_911():
3543
with trio.fail_after(5): # noqa: ASYNC100, ASYNC911
36-
yield # noqa: ASYNC911
44+
if foo():
45+
return # noqa: ASYNC910
3746
await trio.lowlevel.checkpoint()
3847

3948

4049
async def foo_noqa_100_911_500():
4150
with trio.fail_after(5): # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
42-
yield # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
51+
if foo():
52+
return # noqa: ASYNC100, ASYNC910 , ASYNC500,,,
4353
await trio.lowlevel.checkpoint()
4454
# fmt: on
4555

0 commit comments

Comments
 (0)