Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions 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/>`_

25.7.1
======
- :ref:`ASYNC102 <async102>` no longer triggered for asyncio due to different cancellation semantics it uses.

25.5.3
======
- :ref:`ASYNC115 <async115>` and :ref:`ASYNC116 <async116>` now also checks kwargs.
Expand Down
2 changes: 1 addition & 1 deletion docs/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ _`ASYNC101` : yield-in-cancel-scope
_`ASYNC102` : await-in-finally-or-cancelled
``await`` inside ``finally``, :ref:`cancelled-catching <cancelled>` ``except:``, or ``__aexit__`` must have shielded :ref:`cancel scope <cancel_scope>` with timeout.
If not, the async call will immediately raise a new cancellation, suppressing any cancellation that was caught.
Not applicable to asyncio due to edge-based cancellation semantics it uses as opposed to level-based used by trio and anyio.
See :ref:`ASYNC120 <async120>` for the general case where other exceptions might get suppressed.
This is currently not able to detect asyncio shields.

ASYNC103 : no-reraise-cancelled
:ref:`cancelled`-catching exception that does not reraise the exception.
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.5.3
rev: 25.7.1
hooks:
- id: flake8-async
# args: ["--enable=ASYNC100,ASYNC112", "--disable=", "--autofix=ASYNC"]
Expand Down
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__ = "25.5.3"
__version__ = "25.7.1"


# taken from https://github.com/Zac-HD/shed
Expand Down
8 changes: 3 additions & 5 deletions flake8_async/visitors/visitor102_120.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ def async_call_checker(
# non-critical exception handlers have the statement name set to "except"
if self._critical_scope.name == "except":
self._potential_120.append((node, self._critical_scope))
else:
# not applicable to asyncio due to different cancellation semantics it uses
elif self.library != ("asyncio",):
self.error(node, self._critical_scope, error_code="ASYNC102")
Comment on lines 75 to 79
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ASYNC120 has the same issue as ASYNC102 wrt asyncio, so if you don't mind you can expand the scope of this PR to also cover async120 with a small logic change here.

Copy link
Copy Markdown
Contributor Author

@RomanValov RomanValov Jul 28, 2025

Choose a reason for hiding this comment

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

as per my understanding async120 is still relevant to asyncio.
in case of async102 -- if cancellation occured in try block, it remains in except/finally blocks with trio/anyio
in case of async120 -- it only checks cancellation occurs during except ... block which is still valid for asyncio
please correct me if I'm missing something

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sorry no, you're totally right. Ignore me


def visit_Raise(self, node: ast.Raise):
Expand All @@ -84,10 +85,7 @@ def visit_Raise(self, node: ast.Raise):

def is_safe_aclose_call(self, node: ast.Await) -> bool:
return (
# don't mark calls safe in asyncio-only files
# a more defensive option would be `asyncio not in self.library`
self.library != ("asyncio",)
and isinstance(node.value, ast.Call)
isinstance(node.value, ast.Call)
# only known safe if no arguments
and not node.value.args
and not node.value.keywords
Expand Down
2 changes: 1 addition & 1 deletion tests/eval_files/async102.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# type: ignore
# ARG --enable=ASYNC102,ASYNC120
# NOASYNCIO # TODO: support asyncio shields
# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio
from contextlib import asynccontextmanager

import trio
Expand Down
2 changes: 2 additions & 0 deletions tests/eval_files/async102_aclose.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# ANYIO_NO_ERROR
# TRIO_NO_ERROR

# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio

# See also async102_aclose_args.py - which makes sure trio/anyio raises errors if there
# are arguments to aclose().
Comment thread
RomanValov marked this conversation as resolved.
Outdated

Expand Down
2 changes: 2 additions & 0 deletions tests/eval_files/async102_aclose_args.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# type: ignore

# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio

# trio/anyio should still raise errors if there's args
# asyncio will always raise errors
Comment thread
RomanValov marked this conversation as resolved.
Outdated

Expand Down
2 changes: 1 addition & 1 deletion tests/eval_files/async102_anyio.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# type: ignore
# NOTRIO
# NOASYNCIO
# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio
# BASE_LIBRARY anyio
# this test will raise the same errors with trio/asyncio, despite [trio|asyncio].get_cancelled_exc_class not existing
Comment thread
RomanValov marked this conversation as resolved.
Outdated
# marked not to run the tests though as error messages will only refer to anyio
Expand Down
1 change: 1 addition & 0 deletions tests/eval_files/async102_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# NOANYIO
# NOTRIO
# BASE_LIBRARY asyncio
# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio
from contextlib import asynccontextmanager

Comment thread
RomanValov marked this conversation as resolved.
Outdated
import asyncio
Expand Down
2 changes: 1 addition & 1 deletion tests/eval_files/async102_trio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# NOASYNCIO
# NOANYIO - since anyio.Cancelled does not exist
# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio
import trio


Expand Down
1 change: 1 addition & 0 deletions tests/eval_files/noqa_no_autofix.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# ARG --enable=ASYNC102
# ASYNCIO_NO_ERROR # ASYNC102 not applicable to asyncio

import trio
from typing import Any
Expand Down
Loading