Skip to content

Commit

Permalink
enable rule for asyncio, add more details to rule explanation. Extend…
Browse files Browse the repository at this point in the history
… tests to be more thorough with state management.
  • Loading branch information
jakkdl committed Aug 29, 2024
1 parent 402993e commit 3435f63
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ _`ASYNC120` : await-in-except
This is currently not able to detect asyncio shields.

_`ASYNC121`: control-flow-in-taskgroup
`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. See `Trio issue #1493 <https://github.com/python-trio/trio/issues/1493>`.
`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.


Blocking sync calls in async functions
Expand Down
2 changes: 1 addition & 1 deletion flake8_async/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ def visit_AsyncWith(self, node: ast.AsyncWith):
self.unsafe_stack.append("nursery")
elif get_matching_call(
item.context_expr, "create_task_group", base="anyio"
):
) or get_matching_call(item.context_expr, "TaskGroup", base="asyncio"):
self.unsafe_stack.append("task group")

def visit_While(self, node: ast.While | ast.For):
Expand Down
43 changes: 37 additions & 6 deletions tests/eval_files/async121.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
# ASYNCIO_NO_ERROR # not a problem in asyncio
# ASYNCIO_NO_ERROR # checked in async121_asyncio.py
# ANYIO_NO_ERROR # checked in async121_anyio.py

import trio


def condition() -> bool:
return False


async def foo_return():
async with trio.open_nursery():
return # ASYNC121: 8, "return", "nursery"
if condition():
return # ASYNC121: 12, "return", "nursery"
while condition():
return # ASYNC121: 12, "return", "nursery"

return # safe


async def foo_return_nested():
Expand All @@ -26,7 +35,9 @@ async def foo_while_continue_safe():
async def foo_while_continue_unsafe():
while True:
async with trio.open_nursery():
continue # ASYNC121: 12, "continue", "nursery"
if condition():
continue # ASYNC121: 16, "continue", "nursery"
continue # safe


async def foo_for_continue_safe():
Expand All @@ -38,7 +49,9 @@ async def foo_for_continue_safe():
async def foo_for_continue_unsafe():
for _ in range(5):
async with trio.open_nursery():
continue # ASYNC121: 12, "continue", "nursery"
if condition():
continue # ASYNC121: 16, "continue", "nursery"
continue # safe


# break
Expand All @@ -51,7 +64,9 @@ async def foo_while_break_safe():
async def foo_while_break_unsafe():
while True:
async with trio.open_nursery():
break # ASYNC121: 12, "break", "nursery"
if condition():
break # ASYNC121: 16, "break", "nursery"
continue # safe


async def foo_for_break_safe():
Expand All @@ -63,4 +78,20 @@ async def foo_for_break_safe():
async def foo_for_break_unsafe():
for _ in range(5):
async with trio.open_nursery():
break # ASYNC121: 12, "break", "nursery"
if condition():
break # ASYNC121: 16, "break", "nursery"
continue # safe


# nested nursery
async def foo_nested_nursery():
async with trio.open_nursery():
if condition():
return # ASYNC121: 12, "return", "nursery"
async with trio.open_nursery():
if condition():
return # ASYNC121: 16, "return", "nursery"
if condition():
return # ASYNC121: 12, "return", "nursery"
if condition():
return # safe
2 changes: 1 addition & 1 deletion tests/eval_files/async121_anyio.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ASYNCIO_NO_ERROR # not a problem in asyncio
# ASYNCIO_NO_ERROR # checked in async121_asyncio.py
# TRIO_NO_ERROR # checked in async121.py
# BASE_LIBRARY anyio

Expand Down
69 changes: 69 additions & 0 deletions tests/eval_files/async121_asyncio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# ANYIO_NO_ERROR
# TRIO_NO_ERROR # checked in async121.py
# BASE_LIBRARY asyncio
# TaskGroup was added in 3.11, we run type checking with 3.9
# mypy: disable-error-code=attr-defined

import asyncio


async def foo_return():
async with asyncio.TaskGroup():
return # ASYNC121: 8, "return", "task group"


async def foo_return_nested():
async with asyncio.TaskGroup():

def bar():
return # safe


# continue
async def foo_while_continue_safe():
async with asyncio.TaskGroup():
while True:
continue # safe


async def foo_while_continue_unsafe():
while True:
async with asyncio.TaskGroup():
continue # ASYNC121: 12, "continue", "task group"


async def foo_for_continue_safe():
async with asyncio.TaskGroup():
for _ in range(5):
continue # safe


async def foo_for_continue_unsafe():
for _ in range(5):
async with asyncio.TaskGroup():
continue # ASYNC121: 12, "continue", "task group"


# break
async def foo_while_break_safe():
async with asyncio.TaskGroup():
while True:
break # safe


async def foo_while_break_unsafe():
while True:
async with asyncio.TaskGroup():
break # ASYNC121: 12, "break", "task group"


async def foo_for_break_safe():
async with asyncio.TaskGroup():
for _ in range(5):
break # safe


async def foo_for_break_unsafe():
for _ in range(5):
async with asyncio.TaskGroup():
break # ASYNC121: 12, "break", "task group"

0 comments on commit 3435f63

Please sign in to comment.