Skip to content

Commit 2fdaff1

Browse files
authored
add trio.as_safe_channel to async900 safe decorators (#374)
1 parent 11f2bf2 commit 2fdaff1

File tree

6 files changed

+23
-11
lines changed

6 files changed

+23
-11
lines changed

docs/changelog.rst

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Changelog
44

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

7+
25.4.4
8+
======
9+
- :ref:`ASYNC900 <async900>` now accepts and recommends :func:`trio.as_safe_channel`.
10+
711
25.4.3
812
======
913
- :ref:`ASYNC100 <async100>` can now autofix ``with`` statements with multiple items.

docs/rules.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ ASYNC118 : cancelled-class-saved
7676

7777
_`ASYNC119` : yield-in-cm-in-async-gen
7878
``yield`` in context manager in async generator is unsafe, the cleanup may be delayed until ``await`` is no longer allowed.
79-
We strongly encourage you to read :pep:`533` and use `async with aclosing(...) <https://docs.python.org/3/library/contextlib.html#contextlib.aclosing>`_, or better yet avoid async generators entirely (see `ASYNC900`_ ) in favor of context managers which return an iterable :ref:`channel/stream/queue <channel_stream_queue>`.
79+
We strongly encourage you to read :pep:`533` and use `async with aclosing(...) <https://docs.python.org/3/library/contextlib.html#contextlib.aclosing>`_.
80+
:func:`trio.as_safe_channel` has been designed to be a drop-in replacement to transform
81+
any unsafe async generator into a context manager that uses :ref:`streams <channel_stream_queue>` and safely runs the generator in a background task.
8082

8183
_`ASYNC120` : await-in-except
8284
Dangerous :ref:`checkpoint` inside an ``except`` block.

docs/usage.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ adding the following to your ``.pre-commit-config.yaml``:
3333
minimum_pre_commit_version: '2.9.0'
3434
repos:
3535
- repo: https://github.com/python-trio/flake8-async
36-
rev: 25.4.3
36+
rev: 25.4.4
3737
hooks:
3838
- id: flake8-async
3939
# args: ["--enable=ASYNC100,ASYNC112", "--disable=", "--autofix=ASYNC"]

flake8_async/__init__.py

+1-1
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__ = "25.4.3"
41+
__version__ = "25.4.4"
4242

4343

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

flake8_async/visitors/visitors.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -555,18 +555,21 @@ class Visitor900(Flake8AsyncVisitor):
555555
def __init__(self, *args: Any, **kwargs: Any):
556556
super().__init__(*args, **kwargs)
557557
self.unsafe_function: ast.AsyncFunctionDef | None = None
558-
self.transform_decorators = (
559-
"asynccontextmanager",
560-
"fixture",
558+
self.transform_decorators = [
559+
"contextlib.asynccontextmanager",
560+
"pytest.fixture",
561561
*self.options.transform_async_generator_decorators,
562-
)
562+
]
563+
# only recommend if using trio
564+
if "trio" in self.library:
565+
self.transform_decorators.insert(0, "trio.as_safe_channel")
563566

564567
def visit_AsyncFunctionDef(
565568
self, node: ast.AsyncFunctionDef | ast.FunctionDef | ast.Lambda
566569
):
567570
self.save_state(node, "unsafe_function")
568571
if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
569-
node, *self.transform_decorators
572+
node, *(d.split(".")[-1] for d in self.transform_decorators)
570573
):
571574
self.unsafe_function = node
572575
else:

tests/eval_files/async900.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# type: ignore
22
# ARG --no-checkpoint-warning-decorator=asynccontextmanager,other_context_manager
33
# transform-async-generator-decorators set further down
4+
5+
# trio will also recommend trio.as_safe_channel, see async900_trio
6+
# NOTRIO
47
from contextlib import asynccontextmanager
58

69

7-
async def foo1(): # ASYNC900: 0, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
10+
async def foo1(): # ASYNC900: 0, 'contextlib.asynccontextmanager, pytest.fixture, this_is_like_a_context_manager'
811
yield
912
yield
1013

@@ -16,7 +19,7 @@ async def foo2():
1619

1720
@asynccontextmanager
1821
async def foo3():
19-
async def bar(): # ASYNC900: 4, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
22+
async def bar(): # ASYNC900: 4, 'contextlib.asynccontextmanager, pytest.fixture, this_is_like_a_context_manager'
2023
yield
2124

2225
yield
@@ -38,7 +41,7 @@ async def async_fixtures_can_take_arguments():
3841

3942
# no-checkpoint-warning-decorator now ignored
4043
@other_context_manager
41-
async def foo5(): # ASYNC900: 0, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
44+
async def foo5(): # ASYNC900: 0, 'contextlib.asynccontextmanager, pytest.fixture, this_is_like_a_context_manager'
4245
yield
4346

4447

0 commit comments

Comments
 (0)