Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve replacement #345

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changelog

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

25.2.2
=======
- :ref:`ASYNC113 <async113>` now only triggers on ``trio.[serve_tcp, serve_ssl_over_tcp, serve_listeners, run_process]``, instead of accepting anything as the attribute base. (e.g. :func:`anyio.run_process` is not startable).

25.2.1
=======
- :ref:`ASYNC912 <async912>` and :ref:`ASYNC913 <async913>` will now trigger if there's no *cancel* points. This means that :func:`trio.open_nursery`/`anyio.create_task_group` will not silence them on their own, unless they're guaranteed to start tasks.
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ adding the following to your ``.pre-commit-config.yaml``:
minimum_pre_commit_version: '2.9.0'
repos:
- repo: https://github.com/python-trio/flake8-async
rev: 25.2.1
rev: 25.2.2
hooks:
- id: flake8-async
# args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
Expand Down
2 changes: 1 addition & 1 deletion flake8_async/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
__version__ = "25.2.1"
__version__ = "25.2.2"


# taken from https://github.com/Zac-HD/shed
Expand Down
37 changes: 25 additions & 12 deletions flake8_async/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ def visit_With(self, node: ast.With | ast.AsyncWith):
elif get_matching_call(
item.context_expr, "create_task_group", base="anyio"
):
nursery_type = "taskgroup"
nursery_type = "task group"
# check for asyncio.TaskGroup
elif get_matching_call(item.context_expr, "TaskGroup", base="asyncio"):
nursery_type = "taskgroup"
nursery_type = "task group"
start_methods = ("create_task",)
else:
# incorrectly marked as not covered on py39
Expand All @@ -151,12 +151,12 @@ def visit_With(self, node: ast.With | ast.AsyncWith):


# used in 113 and 114
STARTABLE_CALLS = (
STARTABLE_CALLS = ("serve",)
TRIO_STARTABLE_CALLS = (
"run_process",
"serve_ssl_over_tcp",
"serve_tcp",
"serve_listeners",
"serve",
)


Expand Down Expand Up @@ -201,7 +201,11 @@ def is_startable(n: ast.expr, *startable_list: str) -> bool:
if isinstance(n, ast.Name):
return n.id in startable_list
if isinstance(n, ast.Attribute):
return n.attr in startable_list
return n.attr in startable_list and not (
n.attr in TRIO_STARTABLE_CALLS
and isinstance(n.value, ast.Name)
and n.value.id != "trio"
)
if isinstance(n, ast.Call):
return any(is_startable(nn, *startable_list) for nn in n.args)
return False
Expand All @@ -213,12 +217,16 @@ def is_nursery_call(node: ast.expr):
):
return False
var = ast.unparse(node.value)
return ("trio" in self.library and var.endswith("nursery")) or (
self.variables.get(var, "")
in (
"trio.Nursery",
"anyio.TaskGroup",
"asyncio.TaskGroup",
return (
("trio" in self.library and var.endswith("nursery"))
or ("anyio" in self.library and var.endswith("task_group"))
or (
self.variables.get(var, "")
in (
"trio.Nursery",
"anyio.TaskGroup",
"asyncio.TaskGroup",
)
)
)

Expand All @@ -229,6 +237,7 @@ def is_nursery_call(node: ast.expr):
and is_startable(
node.args[0],
*STARTABLE_CALLS,
*TRIO_STARTABLE_CALLS,
*self.options.startable_in_context_manager,
)
):
Expand All @@ -254,7 +263,11 @@ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
for n in self.walk(*node.args.args, *node.args.kwonlyargs)
) and not any(
node.name == opt
for opt in (*self.options.startable_in_context_manager, *STARTABLE_CALLS)
for opt in (
*self.options.startable_in_context_manager,
*STARTABLE_CALLS,
*TRIO_STARTABLE_CALLS,
)
):
self.error(node, node.name)

Expand Down
74 changes: 74 additions & 0 deletions tests/autofix_files/async100.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,77 @@ async def dont_crash_on_non_name_or_attr_call():
async def another_weird_with_call():
async with a().b():
...


# ---open_nursery / create_task_group stuff---


async def nursery_no_cancel_point():
# error: 9, "trio", "CancelScope"
async with trio.open_nursery():
...


# but it is a cancel point if the nursery contains a call to start_soon()


async def nursery_start_soon():
with trio.CancelScope():
async with trio.open_nursery() as n:
n.start_soon(trio.sleep, 0)


async def nursery_start_soon_misnested():
async with trio.open_nursery() as n:
# error: 13, "trio", "CancelScope"
n.start_soon(trio.sleep, 0)


async def nested_scope():
with trio.CancelScope():
with trio.CancelScope():
async with trio.open_nursery() as n:
n.start_soon(trio.sleep, 0)


async def nested_nursery():
with trio.CancelScope():
async with trio.open_nursery() as n:
async with trio.open_nursery() as n2:
n2.start_soon(trio.sleep, 0)


async def nested_function_call():

# error: 9, "trio", "CancelScope"
async with trio.open_nursery() as n:

def foo():
n.start_soon(trio.sleep, 0)

# a false alarm in case we call foo()... but we can't check if they do
foo()


# insert cancel point on nursery exit, not at the start_soon call
async def cancel_point_on_nursery_exit():
with trio.CancelScope():
async with trio.open_nursery() as n:
# error: 17, "trio", "CancelScope"
n.start_soon(trio.sleep, 0)


# async100 does not consider *redundant* cancel scopes
async def redundant_cancel_scope():
with trio.CancelScope():
with trio.CancelScope():
await trio.lowlevel.checkpoint()


# but if it did then none of these scopes should be marked redundant
# The inner checks task startup, the outer checks task exit
async def nursery_exit_blocks_with_start():
with trio.CancelScope():
async with trio.open_nursery() as n:
with trio.CancelScope():
await n.start(trio.sleep, 0)
69 changes: 64 additions & 5 deletions tests/autofix_files/async100.py.diff
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,15 @@
await trio.sleep_forever()
- with trio.CancelScope(): # error: 13, "trio", "CancelScope"
- ...
+ # error: 13, "trio", "CancelScope"
+ ...

-
- with trio.fail_after(1): # error: 9, "trio", "fail_after"
- with trio.CancelScope(): # error: 13, "trio", "CancelScope"
- ...
- with trio.CancelScope(): # error: 13, "trio", "CancelScope"
- ...
+ # error: 13, "trio", "CancelScope"
+ ...
+
+ # error: 9, "trio", "fail_after"
+ # error: 13, "trio", "CancelScope"
+ ...
Expand All @@ -119,14 +120,72 @@
- with trio.fail_after(1): # error: 9, "trio", "fail_after"
- with contextlib.suppress(Exception):
- print("foo")
+ # error: 9, "trio", "fail_after"
with contextlib.suppress(Exception):
- with contextlib.suppress(Exception):
- with trio.fail_after(1): # error: 13, "trio", "fail_after"
- print("foo")
+ # error: 9, "trio", "fail_after"
+ with contextlib.suppress(Exception):
+ print("foo")
+ with contextlib.suppress(Exception):
+ # error: 13, "trio", "fail_after"
+ print("foo")

with contextlib.suppress(Exception):
with open("blah") as file:
@@ x,9 x,9 @@


async def nursery_no_cancel_point():
- with trio.CancelScope(): # error: 9, "trio", "CancelScope"
- async with trio.open_nursery():
- ...
+ # error: 9, "trio", "CancelScope"
+ async with trio.open_nursery():
+ ...


# but it is a cancel point if the nursery contains a call to start_soon()
@@ x,8 x,8 @@

async def nursery_start_soon_misnested():
async with trio.open_nursery() as n:
- with trio.CancelScope(): # error: 13, "trio", "CancelScope"
- n.start_soon(trio.sleep, 0)
+ # error: 13, "trio", "CancelScope"
+ n.start_soon(trio.sleep, 0)


async def nested_scope():
@@ x,22 x,22 @@

async def nested_function_call():

- with trio.CancelScope(): # error: 9, "trio", "CancelScope"
- async with trio.open_nursery() as n:
-
- def foo():
- n.start_soon(trio.sleep, 0)
-
- # a false alarm in case we call foo()... but we can't check if they do
- foo()
+ # error: 9, "trio", "CancelScope"
+ async with trio.open_nursery() as n:
+
+ def foo():
+ n.start_soon(trio.sleep, 0)
+
+ # a false alarm in case we call foo()... but we can't check if they do
+ foo()


# insert cancel point on nursery exit, not at the start_soon call
async def cancel_point_on_nursery_exit():
with trio.CancelScope():
async with trio.open_nursery() as n:
- with trio.CancelScope(): # error: 17, "trio", "CancelScope"
- n.start_soon(trio.sleep, 0)
+ # error: 17, "trio", "CancelScope"
+ n.start_soon(trio.sleep, 0)


# async100 does not consider *redundant* cancel scopes
26 changes: 0 additions & 26 deletions tests/autofix_files/async100_anyio.py

This file was deleted.

23 changes: 0 additions & 23 deletions tests/autofix_files/async100_anyio.py.diff

This file was deleted.

Loading
Loading