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