Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', 3.13-dev]
python-version: ['3.9', '3.10', '3.11', '3.12', 3.13]
fail-fast: false
steps:
- uses: actions/checkout@v4
Expand Down
3 changes: 2 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Changelog

24.10.2
=======
- :ref:`ASYNC101 <async101>` and :ref:`ASYNC119 <async119>` are now silenced for decorators in :ref:`transform-async-generator-decorators`
- :ref:`ASYNC102 <async102>` now also warns about ``await()`` inside ``__aexit__``.

24.10.1
Expand Down Expand Up @@ -39,7 +40,7 @@ Changelog

24.8.1
======
- Add config option ``transform-async-generator-decorators``, to list decorators which
- Add config option :ref:`transform-async-generator-decorators`, to list decorators which
suppress :ref:`ASYNC900 <async900>`.

24.6.1
Expand Down
2 changes: 1 addition & 1 deletion docs/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _`ASYNC100` : cancel-scope-no-checkpoint
This check also treats ``yield`` as a checkpoint, since checkpoints can happen in the caller we yield to.
See :ref:`ASYNC912 <async912>` which will in addition guarantee checkpoints on every code path.

ASYNC101 : yield-in-cancel-scope
_`ASYNC101` : yield-in-cancel-scope
``yield`` inside a :ref:`taskgroup_nursery` or :ref:`timeout_context` is only safe when implementing a context manager - otherwise, it breaks exception handling.
See `this thread <https://discuss.python.org/t/preventing-yield-inside-certain-context-managers/1091/23>`_ for discussion of a future PEP.
This has substantial overlap with :ref:`ASYNC119 <ASYNC119>`, which will warn on almost all instances of ASYNC101, but ASYNC101 is about a conceptually different problem that will not get resolved by `PEP 533 <https://peps.python.org/pep-0533/>`_.
Expand Down
18 changes: 18 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,21 @@ Specified patterns must not have parentheses, and will only match when the patte
def my_blocking_call(): # it's also safe to use the name in other contexts
...
arbitrary_other_function(my_blocking_call=None)

.. _transform-async-generator-decorators:

``transform-async-generator-decorators``
----------------------------------------
Comma-separated list of decorators that make async generators safe, disabling
:ref:`ASYNC900 <ASYNC900>`, :ref:`ASYNC101 <ASYNC101>`, and :ref:`ASYNC119 <ASYNC119>` warnings for functions decorated with any of them.
``[pytest.]fixture`` and ``[contextlib.]asynccontextmanager`` are always considered safe.
Decorators can be dotted or not, as well as support * as a wildcard.

Example
^^^^^^^

.. code-block:: none

transform-async-generator-decorators =
fastapi.Depends
trio_util.trio_async_generator
6 changes: 5 additions & 1 deletion flake8_async/visitors/visitor101.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ def visit_FunctionDef(self, node: cst.FunctionDef):
self.save_state(node, "_yield_is_error", "_safe_decorator")
self._yield_is_error = False
self._safe_decorator = func_has_decorator(
node, "contextmanager", "asynccontextmanager", "fixture"
node,
"contextmanager",
"asynccontextmanager",
"fixture",
*self.options.transform_async_generator_decorators,
)

# trigger on leaving yield so any comments are parsed for noqas
Expand Down
5 changes: 4 additions & 1 deletion flake8_async/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@ def visit_AsyncFunctionDef(
self.save_state(node, "unsafe_function", "contextmanager")
self.contextmanager = False
if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
node, "asynccontextmanager"
node,
"asynccontextmanager",
"fixture",
*self.options.transform_async_generator_decorators,
):
self.unsafe_function = True
else:
Expand Down
22 changes: 22 additions & 0 deletions tests/eval_files/async101.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# ASYNCIO_NO_ERROR
# ARG --no-checkpoint-warning-decorator=no_checkpoint_warning_decorator
# ARG --transform-async-generator-decorators=transform_async_gen_decorator

# This file contains errors shared between trio and anyio, since they have some
# overlap in naming.
Expand Down Expand Up @@ -127,3 +129,23 @@ def foo_pytest_fixture_paren():
def foo_pytest_fixture_params():
with trio.CancelScope() as _:
yield 1


def no_checkpoint_warning_decorator(_: object): ...


def transform_async_gen_decorator(_: object): ...


# --no-checkpoint-warning-decorator does not mark as safe
@no_checkpoint_warning_decorator
def no_checkpoint_warning_deco_fun():
with trio.CancelScope():
yield 1 # error: 8


# --transform-async-generator-decorators marks as safe
@transform_async_gen_decorator
def transfor_async_gen_deco_fun():
with trio.CancelScope():
yield 1 # safe
20 changes: 20 additions & 0 deletions tests/eval_files/async119.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# ARG --no-checkpoint-warning-decorator=no_checkpoint_warning_decorator
# ARG --transform-async-generator-decorators=transform_async_gen_decorator
import contextlib

from contextlib import asynccontextmanager
Expand Down Expand Up @@ -62,3 +64,21 @@ async def safe_in_contextmanager():
async def safe_in_contextmanager2():
with open(""):
yield


def no_checkpoint_warning_decorator(_: object): ...


def transform_async_gen_decorator(_: object): ...


@no_checkpoint_warning_decorator
async def no_checkpoint_warning_deco_fun():
with open(""):
yield # error: 8


@transform_async_gen_decorator
async def transfor_async_gen_deco_fun():
with open(""):
yield # safe
1 change: 1 addition & 0 deletions tests/eval_files/async900.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# type: ignore
# ARG --no-checkpoint-warning-decorator=asynccontextmanager,other_context_manager
# transform-async-generator-decorators set further down
from contextlib import asynccontextmanager


Expand Down
Loading