Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config option for async900 decorators #279

Merged
merged 1 commit into from
Aug 7, 2024
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
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Changelog

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

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

24.6.1
======
- Add :ref:`ASYNC120 <async120>` await-in-except.
Expand Down
7 changes: 5 additions & 2 deletions docs/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,15 @@ Optional rules disabled by default
Our 9xx rules check for semantics issues, like 1xx rules, but are disabled by default due
to the higher volume of warnings. We encourage you to enable them - without guaranteed
:ref:`checkpoint`\ s timeouts and cancellation can be arbitrarily delayed, and async
generators are prone to the problems described in :pep:`533`.
generators are prone to the problems described in :pep:`789` and :pep:`533`.

_`ASYNC900` : unsafe-async-generator
Async generator without :func:`@asynccontextmanager <contextlib.asynccontextmanager>` not allowed.
You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed.
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.
See :pep:`789` for control-flow problems, :pep:`533` for delayed cleanup problems.
Further decorators can be registered with the ``--transform-async-generator-decorators``
config option, e.g. `@trio_util.trio_async_generator
<https://trio-util.readthedocs.io/en/latest/index.html#trio_util.trio_async_generator>`_.

_`ASYNC910` : async-function-no-checkpoint
Exit or ``return`` from async function with no guaranteed :ref:`checkpoint` or exception since function definition.
Expand Down
15 changes: 14 additions & 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.6.1"
__version__ = "24.8.1"


# taken from https://github.com/Zac-HD/shed
Expand Down Expand Up @@ -261,6 +261,18 @@ def add_options(option_manager: OptionManager | ArgumentParser):
"mydecorator,mypackage.mydecorators.*``"
),
)
add_argument(
"--transform-async-generator-decorators",
default="",
required=False,
type=comma_separated_list,
help=(
"Comma-separated list of decorators to disable ASYNC900 warnings for. "
"Decorators can be dotted or not, as well as support * as a wildcard. "
"For example, ``--transform-async-generator-decorators=fastapi.Depends,"
"trio_util.trio_async_generator``"
),
)
add_argument(
"--exception-suppress-context-managers",
default="",
Expand Down Expand Up @@ -391,6 +403,7 @@ def get_matching_codes(
autofix_codes=autofix_codes,
error_on_autofix=options.error_on_autofix,
no_checkpoint_warning_decorators=options.no_checkpoint_warning_decorators,
transform_async_generator_decorators=options.transform_async_generator_decorators,
exception_suppress_context_managers=options.exception_suppress_context_managers,
startable_in_context_manager=options.startable_in_context_manager,
async200_blocking_calls=options.async200_blocking_calls,
Expand Down
1 change: 1 addition & 0 deletions flake8_async/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Options:
# whether to print an error message even when autofixed
error_on_autofix: bool
no_checkpoint_warning_decorators: Collection[str]
transform_async_generator_decorators: Collection[str]
exception_suppress_context_managers: Collection[str]
startable_in_context_manager: Collection[str]
async200_blocking_calls: dict[str, str]
Expand Down
12 changes: 9 additions & 3 deletions flake8_async/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,27 +401,33 @@ def leave_IfExp_test(self, node: cst.IfExp):
@disabled_by_default
class Visitor900(Flake8AsyncVisitor):
error_codes: Mapping[str, str] = {
"ASYNC900": "Async generator without `@asynccontextmanager` not allowed."
"ASYNC900": "Async generator not allowed, unless transformed "
"by a known decorator (one of: {})."
}

def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.unsafe_function: ast.AsyncFunctionDef | None = None
self.transform_decorators = (
"asynccontextmanager",
"fixture",
*self.options.transform_async_generator_decorators,
)

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, "asynccontextmanager", "fixture"
node, *self.transform_decorators
):
self.unsafe_function = node
else:
self.unsafe_function = None

def visit_Yield(self, node: ast.Yield):
if self.unsafe_function is not None:
self.error(self.unsafe_function)
self.error(self.unsafe_function, ", ".join(self.transform_decorators))
self.unsafe_function = None

visit_FunctionDef = visit_AsyncFunctionDef
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from pathlib import Path

from setuptools import find_packages, setup
from setuptools import find_packages, setup # type: ignore


def local_file(name: str) -> Path:
Expand Down
15 changes: 12 additions & 3 deletions tests/eval_files/async900.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from contextlib import asynccontextmanager


async def foo1(): # ASYNC900: 0
async def foo1(): # ASYNC900: 0, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
yield
yield

Expand All @@ -15,7 +15,7 @@ async def foo2():

@asynccontextmanager
async def foo3():
async def bar(): # ASYNC900: 4
async def bar(): # ASYNC900: 4, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
yield

yield
Expand All @@ -37,7 +37,7 @@ async def async_fixtures_can_take_arguments():

# no-checkpoint-warning-decorator now ignored
@other_context_manager
async def foo5(): # ASYNC900: 0
async def foo5(): # ASYNC900: 0, 'asynccontextmanager, fixture, this_is_like_a_context_manager'
yield


Expand All @@ -54,3 +54,12 @@ async def cm():
async def another_non_generator():
def foo():
yield


# ARG --transform-async-generator-decorators=this_is_like_a_context_manager
Copy link
Member

Choose a reason for hiding this comment

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

Minor: I'd probably have this at the top of the file for readability (and in all other files I've had them at the top), esp as it pops up in the other error messages.



@this_is_like_a_context_manager() # OK because of the config, issue #277
async def some_generator():
while True:
yield
Loading