Skip to content

add trio.as_safe_channel to async900 safe decorators #374

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

Merged
merged 1 commit into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.4.4
======
- :ref:`ASYNC900 <async900>` now accepts and recommends :func:`trio.as_safe_channel`.

25.4.3
======
- :ref:`ASYNC100 <async100>` can now autofix ``with`` statements with multiple items.
Expand Down
4 changes: 3 additions & 1 deletion docs/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ ASYNC118 : cancelled-class-saved

_`ASYNC119` : yield-in-cm-in-async-gen
``yield`` in context manager in async generator is unsafe, the cleanup may be delayed until ``await`` is no longer allowed.
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>`.
We strongly encourage you to read :pep:`533` and use `async with aclosing(...) <https://docs.python.org/3/library/contextlib.html#contextlib.aclosing>`_.
:func:`trio.as_safe_channel` has been designed to be a drop-in replacement to transform
any unsafe async generator into a context manager that uses :ref:`streams <channel_stream_queue>` and safely runs the generator in a background task.

_`ASYNC120` : await-in-except
Dangerous :ref:`checkpoint` inside an ``except`` block.
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.4.3
rev: 25.4.4
hooks:
- id: flake8-async
# args: ["--enable=ASYNC100,ASYNC112", "--disable=", "--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.4.3"
__version__ = "25.4.4"


# taken from https://github.com/Zac-HD/shed
Expand Down
13 changes: 8 additions & 5 deletions flake8_async/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,18 +555,21 @@ class Visitor900(Flake8AsyncVisitor):
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.unsafe_function: ast.AsyncFunctionDef | None = None
self.transform_decorators = (
"asynccontextmanager",
"fixture",
self.transform_decorators = [
"contextlib.asynccontextmanager",
"pytest.fixture",
*self.options.transform_async_generator_decorators,
)
]
# only recommend if using trio
if "trio" in self.library:
self.transform_decorators.insert(0, "trio.as_safe_channel")

def visit_AsyncFunctionDef(
self, node: ast.AsyncFunctionDef | ast.FunctionDef | ast.Lambda
):
self.save_state(node, "unsafe_function")
if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
node, *self.transform_decorators
node, *(d.split(".")[-1] for d in self.transform_decorators)
):
self.unsafe_function = node
else:
Expand Down
9 changes: 6 additions & 3 deletions tests/eval_files/async900.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# type: ignore
# ARG --no-checkpoint-warning-decorator=asynccontextmanager,other_context_manager
# transform-async-generator-decorators set further down

# trio will also recommend trio.as_safe_channel, see async900_trio
# NOTRIO
from contextlib import asynccontextmanager


async def foo1(): # ASYNC900: 0, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
async def foo1(): # ASYNC900: 0, 'contextlib.asynccontextmanager, pytest.fixture, this_is_like_a_context_manager'
yield
yield

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

@asynccontextmanager
async def foo3():
async def bar(): # ASYNC900: 4, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
async def bar(): # ASYNC900: 4, 'contextlib.asynccontextmanager, pytest.fixture, this_is_like_a_context_manager'
yield

yield
Expand All @@ -38,7 +41,7 @@ async def async_fixtures_can_take_arguments():

# no-checkpoint-warning-decorator now ignored
@other_context_manager
async def foo5(): # ASYNC900: 0, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
async def foo5(): # ASYNC900: 0, 'contextlib.asynccontextmanager, pytest.fixture, this_is_like_a_context_manager'
yield


Expand Down
Loading