Skip to content

Commit 6992a71

Browse files
committed
Add config option for async900 decorators
1 parent bae7ab7 commit 6992a71

File tree

8 files changed

+43
-7
lines changed

8 files changed

+43
-7
lines changed

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changelog
44

55
*[CalVer, YY.month.patch](https://calver.org/)*
66

7+
24.8.1
8+
======
9+
- Add config option ``transform-async-generator-decorators``, to list decorators which
10+
suppress :ref:`ASYNC900 <async900>`.
11+
712
24.6.1
813
======
914
- Add :ref:`ASYNC120 <async120>` await-in-except.

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"python": ("https://docs.python.org/3", None),
3535
"anyio": ("https://anyio.readthedocs.io/en/latest/", None),
3636
"trio": ("https://trio.readthedocs.io/en/latest/", None),
37+
"trio-util": ("https://trio-util.readthedocs.io/en/latest/", None),
3738
# httpx? Did not seem to work on first try - I think they might not be using
3839
# sphinx at all, so probably can't link through intersphinx?
3940
# see https://github.com/encode/httpx/discussions/1220

docs/rules.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,14 @@ Optional rules disabled by default
161161
Our 9xx rules check for semantics issues, like 1xx rules, but are disabled by default due
162162
to the higher volume of warnings. We encourage you to enable them - without guaranteed
163163
:ref:`checkpoint`\ s timeouts and cancellation can be arbitrarily delayed, and async
164-
generators are prone to the problems described in :pep:`533`.
164+
generators are prone to the problems described in :pep:`789` and :pep:`533`.
165165

166166
_`ASYNC900` : unsafe-async-generator
167167
Async generator without :func:`@asynccontextmanager <contextlib.asynccontextmanager>` not allowed.
168168
You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed.
169-
See `#211 <https://github.com/python-trio/flake8-async/issues/211>`__ and https://discuss.python.org/t/using-exceptiongroup-at-anthropic-experience-report/20888/6 for discussion.
169+
See :pep:`789` for control-flow problems, :pep:`533` for delayed cleanup problems.
170+
Further decorators can be registered with the ``--transform-async-generator-decorators``
171+
config option, e.g. :obj:`trio_util.trio_async_generator`.
170172

171173
_`ASYNC910` : async-function-no-checkpoint
172174
Exit or ``return`` from async function with no guaranteed :ref:`checkpoint` or exception since function definition.

flake8_async/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939

4040
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
41-
__version__ = "24.6.1"
41+
__version__ = "24.8.1"
4242

4343

4444
# taken from https://github.com/Zac-HD/shed
@@ -261,6 +261,18 @@ def add_options(option_manager: OptionManager | ArgumentParser):
261261
"mydecorator,mypackage.mydecorators.*``"
262262
),
263263
)
264+
add_argument(
265+
"--transform-async-generator-decorators",
266+
default="",
267+
required=False,
268+
type=comma_separated_list,
269+
help=(
270+
"Comma-separated list of decorators to disable ASYNC900 warnings for. "
271+
"Decorators can be dotted or not, as well as support * as a wildcard. "
272+
"For example, ``--transform-async-generator-decorators=fastapi.Depends,"
273+
"trio_util.trio_async_generator``"
274+
),
275+
)
264276
add_argument(
265277
"--exception-suppress-context-managers",
266278
default="",
@@ -391,6 +403,7 @@ def get_matching_codes(
391403
autofix_codes=autofix_codes,
392404
error_on_autofix=options.error_on_autofix,
393405
no_checkpoint_warning_decorators=options.no_checkpoint_warning_decorators,
406+
transform_async_generator_decorators=options.transform_async_generator_decorators,
394407
exception_suppress_context_managers=options.exception_suppress_context_managers,
395408
startable_in_context_manager=options.startable_in_context_manager,
396409
async200_blocking_calls=options.async200_blocking_calls,

flake8_async/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Options:
2929
# whether to print an error message even when autofixed
3030
error_on_autofix: bool
3131
no_checkpoint_warning_decorators: Collection[str]
32+
transform_async_generator_decorators: Collection[str]
3233
exception_suppress_context_managers: Collection[str]
3334
startable_in_context_manager: Collection[str]
3435
async200_blocking_calls: dict[str, str]

flake8_async/visitors/visitors.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,27 +401,32 @@ def leave_IfExp_test(self, node: cst.IfExp):
401401
@disabled_by_default
402402
class Visitor900(Flake8AsyncVisitor):
403403
error_codes: Mapping[str, str] = {
404-
"ASYNC900": "Async generator without `@asynccontextmanager` not allowed."
404+
"ASYNC900": "Async generator not allowed, unless transformed by a known decorator (one of: {})."
405405
}
406406

407407
def __init__(self, *args: Any, **kwargs: Any):
408408
super().__init__(*args, **kwargs)
409409
self.unsafe_function: ast.AsyncFunctionDef | None = None
410+
self.transform_decorators = (
411+
"asynccontextmanager",
412+
"fixture",
413+
*self.options.transform_async_generator_decorators,
414+
)
410415

411416
def visit_AsyncFunctionDef(
412417
self, node: ast.AsyncFunctionDef | ast.FunctionDef | ast.Lambda
413418
):
414419
self.save_state(node, "unsafe_function")
415420
if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
416-
node, "asynccontextmanager", "fixture"
421+
node, *self.transform_decorators
417422
):
418423
self.unsafe_function = node
419424
else:
420425
self.unsafe_function = None
421426

422427
def visit_Yield(self, node: ast.Yield):
423428
if self.unsafe_function is not None:
424-
self.error(self.unsafe_function)
429+
self.error(self.unsafe_function, ", ".join(self.transform_decorators))
425430
self.unsafe_function = None
426431

427432
visit_FunctionDef = visit_AsyncFunctionDef

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from pathlib import Path
55

6-
from setuptools import find_packages, setup
6+
from setuptools import find_packages, setup # type: ignore
77

88

99
def local_file(name: str) -> Path:

tests/eval_files/async900.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,12 @@ async def cm():
5454
async def another_non_generator():
5555
def foo():
5656
yield
57+
58+
59+
# ARG --transform-async-generator-decorators=this_is_like_a_context_manager
60+
61+
62+
@this_is_like_a_context_manager() # OK because of the config, issue #277
63+
async def some_generator():
64+
while True:
65+
yield

0 commit comments

Comments
 (0)