Skip to content

Commit 990d7fc

Browse files
authored
add ASYNC122 delayed-entry-of-relative-cancelscope (#292)
1 parent 7be7cac commit 990d7fc

File tree

6 files changed

+72
-3
lines changed

6 files changed

+72
-3
lines changed

docs/changelog.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Changelog
44

55
`CalVer, YY.month.patch <https://calver.org/>`_
66

7+
24.9.4
8+
======
9+
- Add :ref:`ASYNC122 <async122>` delayed-entry-of-relative-cancelscope.
10+
711
24.9.3
812
======
913
- :ref:`ASYNC102 <async102>` and :ref:`ASYNC120 <async120>`:
@@ -19,7 +23,7 @@ Changelog
1923

2024
24.9.1
2125
======
22-
- Add :ref:`ASYNC121 <async121>` control-flow-in-taskgroup
26+
- Add :ref:`ASYNC121 <async121>` control-flow-in-taskgroup.
2327

2428
24.8.1
2529
======
@@ -37,7 +41,7 @@ Changelog
3741

3842
24.5.5
3943
======
40-
- Add :ref:`ASYNC300 <async300>` create-task-no-reference
44+
- Add :ref:`ASYNC300 <async300>` create-task-no-reference.
4145

4246
24.5.4
4347
======

docs/rules.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ _`ASYNC120` : await-in-except
8686
_`ASYNC121`: control-flow-in-taskgroup
8787
`return`, `continue`, and `break` inside a :ref:`taskgroup_nursery` can lead to counterintuitive behaviour. Refactor the code to instead cancel the :ref:`cancel_scope` inside the TaskGroup/Nursery and place the statement outside of the TaskGroup/Nursery block. In asyncio a user might expect the statement to have an immediate effect, but it will wait for all tasks to finish before having an effect. See `Trio issue #1493 <https://github.com/python-trio/trio/issues/1493>`_ for further issues specific to trio/anyio.
8888

89+
_`ASYNC122`: delayed-entry-of-relative-cancelscope
90+
:func:`trio.move_on_after`, :func:`trio.fail_after`, :func:`anyio.move_on_after` and :func:`anyio.fail_after` behaves unintuitively if initialization and entry are separated, with the timeout starting on initialization. Trio>=0.27 changes this behaviour, so if you don't support older versions you should disable this check. See `Trio issue #2512 <https://github.com/python-trio/trio/issues/2512>`_.
8991

9092
Blocking sync calls in async functions
9193
======================================

flake8_async/__init__.py

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

3939

4040
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
41-
__version__ = "24.9.3"
41+
__version__ = "24.9.4"
4242

4343

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

flake8_async/visitors/visitors.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,33 @@ def visit_FunctionDef(self, node: ast.FunctionDef | ast.AsyncFunctionDef):
410410
visit_AsyncFunctionDef = visit_FunctionDef
411411

412412

413+
@error_class
414+
class Visitor122(Flake8AsyncVisitor):
415+
error_codes: Mapping[str, str] = {
416+
"ASYNC122": (
417+
"Separating initialization from entry of {} changed behavior in Trio"
418+
" 0.27, and was unintuitive before then. If you only support"
419+
" trio>=0.27 you should disable this check."
420+
)
421+
}
422+
423+
def __init__(self, *args: Any, **kwargs: Any):
424+
super().__init__(*args, **kwargs)
425+
self.in_withitem = False
426+
427+
def visit_withitem(self, node: ast.withitem):
428+
self.save_state(node, "in_withitem")
429+
self.in_withitem = True
430+
431+
def visit_Call(self, node: ast.Call):
432+
if not self.in_withitem and (
433+
match := get_matching_call(
434+
node, "fail_after", "move_on_after", base=("trio", "anyio")
435+
)
436+
):
437+
self.error(node, f"{match[2]}.{match[1]}")
438+
439+
413440
@error_class_cst
414441
class Visitor300(Flake8AsyncVisitor_cst):
415442
error_codes: Mapping[str, str] = {

tests/eval_files/async122.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# ASYNCIO_NO_ERROR
2+
3+
import trio
4+
5+
6+
def safe():
7+
with trio.move_on_after(5):
8+
...
9+
with open("hello"), trio.move_on_after(5):
10+
...
11+
12+
13+
def separated():
14+
k = trio.move_on_after(5) # ASYNC122: 8, "trio.move_on_after"
15+
16+
with k:
17+
...
18+
19+
l = trio.fail_after(5) # ASYNC122: 8, "trio.fail_after"
20+
with l:
21+
...
22+
23+
24+
def fancy_thing_we_dont_cover():
25+
# it's hard to distinguish this bad case
26+
kk = trio.fail_after
27+
28+
ll = kk(5)
29+
30+
with ll:
31+
...
32+
# from this good case
33+
with kk(5):
34+
...
35+
# so we don't bother

tests/test_flake8_async.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ def _parse_eval_file(
481481
# opening nurseries & taskgroups can only be done in async context, so ASYNC121
482482
# doesn't check for it
483483
"ASYNC121",
484+
"ASYNC122",
484485
"ASYNC300",
485486
"ASYNC912",
486487
}

0 commit comments

Comments
 (0)