diff --git a/docs/changelog.rst b/docs/changelog.rst
index e45308e..568319d 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,10 @@ Changelog
`CalVer, YY.month.patch `_
+25.4.4
+======
+- :ref:`ASYNC900 ` now accepts and recommends :func:`trio.as_safe_channel`.
+
25.4.3
======
- :ref:`ASYNC100 ` can now autofix ``with`` statements with multiple items.
diff --git a/docs/rules.rst b/docs/rules.rst
index d662fcf..405249a 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -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(...) `_, or better yet avoid async generators entirely (see `ASYNC900`_ ) in favor of context managers which return an iterable :ref:`channel/stream/queue `.
+ We strongly encourage you to read :pep:`533` and use `async with 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 ` and safely runs the generator in a background task.
_`ASYNC120` : await-in-except
Dangerous :ref:`checkpoint` inside an ``except`` block.
diff --git a/docs/usage.rst b/docs/usage.rst
index c93834c..b76bbf8 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -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"]
diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py
index 862a0d1..b9a1d72 100644
--- a/flake8_async/__init__.py
+++ b/flake8_async/__init__.py
@@ -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
diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py
index ace5b07..30cb237 100644
--- a/flake8_async/visitors/visitors.py
+++ b/flake8_async/visitors/visitors.py
@@ -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:
diff --git a/tests/eval_files/async900.py b/tests/eval_files/async900.py
index c4df99b..35e03f6 100644
--- a/tests/eval_files/async900.py
+++ b/tests/eval_files/async900.py
@@ -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
@@ -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
@@ -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