Skip to content

Commit 963d4df

Browse files
Zac-HDclaude
andauthored
Various small fixes (#445)
* Fix CI running extra tox envs, and ASYNC300 on return In tox 4, `tox -e ""` is treated as "no env specified" and falls back to the default envlist, so the "Run tests without flake8" step was running every `py3xx-flake8` env whose interpreter tox could discover on the runner (including the system Python 3.12), instead of a single no-flake8 test run. Replace the trailing-comma empty env with a proper `[testenv:no_flake8]` section and point CI at it. Also allow `return asyncio.create_task(...)`: treat `Return` like `Assign` in Visitor300, since the caller preserves the task. Fixes #422 Fixes #398 * Address issue #310 points 1 and 4 1. Document using ruff's `lint.external` setting so that `# noqa: ASYNC...` comments survive when both ruff and flake8-async are in use. 4. Apply the `self.novisit = True` fix proposed in #307 at the end of `Visitor102.visit_Try`. The previous code happened to work because the internal `visit_nodes` calls left `self.novisit` True on exit, but relying on that is fragile; making it explicit matches the pattern used elsewhere and documents the intent. * Run compile() against tests/eval_files to catch syntax mistakes ast.parse() accepts some code the bytecode compiler rejects (e.g. `x = lambda: await foo()`, `return` outside a function), so a bad test fixture could silently slip past the plugin's ast-based checks. Add a new parametrized test that runs compile() on every tests/eval_files/*.py. A handful of existing fixtures intentionally contain such constructs to exercise edge cases; mark those with a new `# NOCOMPILE` magic marker so the new test skips them while still leaving the rest of the suite guarding against accidents. Fixes #268 * Drop docs/test-only entries from changelog Docs and test changes aren't user-visible, so they don't need a changelog entry. The ASYNC300 behaviour change stays. * Mark async911.py as # NOCOMPILE for Python 3.10 Python 3.10's compile() rejects nested async comprehensions inside an async function with "asynchronous comprehension outside of an asynchronous function"; 3.11 became permissive here. The file parses fine with ast.parse on every supported version, which is all the plugin needs, so tag it with the existing # NOCOMPILE marker (also in the matching autofix fixture to keep the diff clean). --------- Co-authored-by: Claude <[email protected]>
1 parent 21af102 commit 963d4df

14 files changed

Lines changed: 69 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
- name: Run tests with flake8
4545
run: python -m tox -e flake8
4646
- name: Run tests without flake8
47-
run: python -m tox -e "" -- --no-cov
47+
run: python -m tox -e no_flake8 -- --no-cov
4848

4949
slow_tests:
5050
runs-on: ubuntu-latest

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Unreleased
88
==========
99
- Autofix for :ref:`ASYNC910 <async910>` / :ref:`ASYNC911 <async911>` no longer inserts checkpoints inside ``except`` clauses (which would trigger :ref:`ASYNC120 <async120>`); instead the checkpoint is added at the top of the function or of the enclosing loop. `(issue #403) <https://github.com/python-trio/flake8-async/issues/403>`_
1010
- :ref:`ASYNC910 <async910>` and :ref:`ASYNC911 <async911>` now accept ``__aenter__`` / ``__aexit__`` methods when the partner method provides the checkpoint, or when only one of the two is defined on a class that inherits from another class (charitably assuming the partner is inherited and contains a checkpoint). `(issue #441) <https://github.com/python-trio/flake8-async/issues/441>`_
11+
- :ref:`ASYNC300 <async300>` no longer triggers when the result of ``asyncio.create_task()`` is returned from a function. `(issue #398) <https://github.com/python-trio/flake8-async/issues/398>`_
1112

1213
25.7.1
1314
======

docs/usage.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ Run through ruff
9191

9292
They currently only support a small subset of the ``flake8-async`` rules though, see https://github.com/astral-sh/ruff/issues/8451 for current status and https://docs.astral.sh/ruff/rules/#flake8-async-async for documentation.
9393

94+
Using both ruff and flake8-async
95+
--------------------------------
96+
97+
Ruff will by default strip any ``# noqa`` comments for codes it does not know about, which breaks suppressions for ``ASYNC`` codes that ruff hasn't reimplemented. You can tell ruff to preserve them by listing ``ASYNC`` under `lint.external <https://docs.astral.sh/ruff/settings/#lint_external>`_ in your ruff config:
98+
99+
.. code-block:: toml
100+
101+
# pyproject.toml
102+
[tool.ruff.lint]
103+
external = ["ASYNC"]
104+
105+
With that in place you can continue to run ``flake8-async`` (either via ``flake8``, pre-commit, or as a standalone) alongside ``ruff`` without losing ``# noqa: ASYNC...`` comments.
106+
94107
*************
95108
Configuration
96109
*************

flake8_async/visitors/visitor102_120.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ def visit_Try(self, node: ast.Try | ast.TryStar): # type: ignore[name-defined]
153153
self._critical_scope = Statement("try/finally", node.lineno, node.col_offset)
154154
self.visit_nodes(node.finalbody)
155155

156+
# we've manually visited the Try fields above; stop the runner from doing
157+
# it a second time via generic_visit.
158+
self.novisit = True
159+
156160
visit_TryStar = visit_Try
157161

158162
def visit_ExceptHandler(self, node: ast.ExceptHandler):

flake8_async/visitors/visitors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ def visit_Call(self, node: cst.Call):
560560

561561
visit_NamedExpr = visit_Assign
562562
visit_AugAssign = visit_Assign
563+
visit_Return = visit_Assign
563564
visit_IfExp_test = visit_CompIf
564565

565566
# because this is a Flake8AsyncVisitor_cst, we need to manually call restore_state
@@ -573,6 +574,7 @@ def leave_Assign(
573574
leave_CompIf = leave_Assign
574575
leave_NamedExpr = leave_Assign
575576
leave_AugAssign = leave_Assign
577+
leave_Return = leave_Assign
576578

577579
def leave_IfExp_test(self, node: cst.IfExp):
578580
self.restore_state(node)

tests/autofix_files/async124.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
# ARG --enable=ASYNC124,ASYNC910,ASYNC911
55
# ARG --no-checkpoint-warning-decorator=custom_disabled_decorator
6+
# NOCOMPILE: foo_nested_sync contains `await` in a sync nested function, which is
7+
# a SyntaxError the bytecode compiler catches but ast.parse accepts. It's only here
8+
# to make sure the plugin doesn't crash on such code.
69

710
# 910/911 will also autofix async124, in the sense of adding a checkpoint. This is perhaps
811
# not what the user wants though, so this would be a case in favor of making 910/911 not

tests/autofix_files/async911.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# AUTOFIX
22
# ASYNCIO_NO_AUTOFIX
3+
# NOCOMPILE: contains a nested async comprehension inside an async function,
4+
# which is a SyntaxError under Python 3.10 (compile() got more permissive in 3.11).
5+
# ast.parse accepts it on all supported versions, which is all the plugin needs.
36
from typing import Any
47

58
import pytest

tests/eval_files/async103_104_py311.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
ASYNC104: cancelled-not-raised
55
"""
66

7+
# NOCOMPILE: `return` inside `except*` is a SyntaxError in 3.11+ but we still want
8+
# to exercise the plugin against it via ast.parse.
9+
710
# ARG --enable=ASYNC103,ASYNC104
811

912
try:

tests/eval_files/async104.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# ARG --enable=ASYNC103,ASYNC104
2+
# NOCOMPILE: contains `return` outside a function so the bytecode compiler rejects
3+
# the file, but ast.parse accepts it and the plugin flags it as ASYNC104.
24
try:
35
...
46
# raise different exception

tests/eval_files/async124.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
# ARG --enable=ASYNC124,ASYNC910,ASYNC911
55
# ARG --no-checkpoint-warning-decorator=custom_disabled_decorator
6+
# NOCOMPILE: foo_nested_sync contains `await` in a sync nested function, which is
7+
# a SyntaxError the bytecode compiler catches but ast.parse accepts. It's only here
8+
# to make sure the plugin doesn't crash on such code.
69

710
# 910/911 will also autofix async124, in the sense of adding a checkpoint. This is perhaps
811
# not what the user wants though, so this would be a case in favor of making 910/911 not

0 commit comments

Comments
 (0)