Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 5 additions & 1 deletion 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/>`_

24.11.1
=======
- :ref:`ASYNC101` and :ref:`ASYNC119` are now silenced for decorators in :ref:`transform-async-generator-decorators`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably remove the changelog from the first of this and #317 to merge, so we do a single release?


24.10.2
=======
- :ref:`ASYNC102 <async102>` now also warns about ``await()`` inside ``__aexit__``.
Expand Down Expand Up @@ -39,7 +43,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`, :ref:`ASYNC101`, and :ref:`ASYNC119` warnings for.
``[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
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__ = "24.10.2"
__version__ = "24.11.1"


# taken from https://github.com/Zac-HD/shed
Expand Down
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