Skip to content
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
25 changes: 13 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
cache: pip
- name: Install typing dependencies
Expand All @@ -30,14 +30,15 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies
run: python -m pip install --upgrade pip setuptools tox
- name: Run tests with flake8
Expand All @@ -50,11 +51,11 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
- uses: actions/checkout@v5
- name: Set up Python 3.14
uses: actions/setup-python@v6
with:
python-version: 3.13
python-version: 3.14
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools tox
Expand All @@ -67,9 +68,9 @@ jobs:
needs: [pyright, test]
if: github.repository == 'python-trio/flake8-async' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Python 3
uses: actions/setup-python@v5
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
- name: Install tools
run: python -m pip install --upgrade build pip setuptools wheel twine gitpython
- name: Upload new release
Expand Down
9 changes: 5 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ repos:
rev: v3.21.0
hooks:
- id: pyupgrade
args: [--py39-plus]
exclude: tests/eval_files/async103.py
args: [--py310-plus]
# async232 explicitly tests Optional[]
exclude: tests/eval_files/async(103|232|232_asyncio).py

- repo: https://github.com/pycqa/isort
rev: 7.0.0
Expand All @@ -41,8 +42,8 @@ repos:
rev: v1.18.2
hooks:
- id: mypy
# uses py311 syntax, mypy configured for py39
exclude: tests/(eval|autofix)_files/.*_py(310|311).py
# uses py311 syntax, mypy configured for py310
exclude: tests/(eval|autofix)_files/.*_py311.py

- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.407
Expand Down
4 changes: 3 additions & 1 deletion flake8_async/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ def from_source(
) -> Plugin:
plugin = Plugin.__new__(cls)
super(Plugin, plugin).__init__()
plugin._tree = ast.parse(source)
plugin._tree = ast.parse(
source, filename=str(filename) if filename is not None else "<unknown>"
)
plugin.filename = str(filename) if filename else None
plugin.module = cst_parse_module_native(source)
return plugin
Expand Down
4 changes: 2 additions & 2 deletions flake8_async/visitors/flake8asyncvisitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import ast
from abc import ABC
from typing import TYPE_CHECKING, Any, Union
from typing import TYPE_CHECKING, Any

import libcst as cst
from libcst.metadata import PositionProvider
Expand All @@ -16,7 +16,7 @@

from ..runner import SharedState

HasLineCol = Union[ast.expr, ast.stmt, ast.arg, ast.excepthandler, Statement]
HasLineCol = ast.expr | ast.stmt | ast.arg | ast.excepthandler | Statement


class Flake8AsyncVisitor(ast.NodeVisitor, ABC):
Expand Down
8 changes: 3 additions & 5 deletions flake8_async/visitors/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from collections.abc import Sized
from dataclasses import dataclass
from fnmatch import fnmatch
from typing import TYPE_CHECKING, Generic, TypeVar, Union
from typing import TYPE_CHECKING, Generic, TypeVar

import libcst as cst
import libcst.matchers as m
Expand All @@ -35,11 +35,9 @@

T = TypeVar("T", bound=Flake8AsyncVisitor)
T_CST = TypeVar("T_CST", bound=Flake8AsyncVisitor_cst)
T_EITHER = TypeVar(
"T_EITHER", bound=Union[Flake8AsyncVisitor, Flake8AsyncVisitor_cst]
)
T_EITHER = TypeVar("T_EITHER", bound=Flake8AsyncVisitor | Flake8AsyncVisitor_cst)

T_Call = TypeVar("T_Call", bound=Union[cst.Call, ast.Call])
T_Call = TypeVar("T_Call", bound=cst.Call | ast.Call)


def error_class(error_class: type[T]) -> type[T]:
Expand Down
17 changes: 6 additions & 11 deletions flake8_async/visitors/visitor91x.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,12 @@ class LoopState:
default_factory=set[Statement]
)
uncheckpointed_before_break: set[Statement] = field(default_factory=set[Statement])
# pyright emits reportUnknownVariableType, requiring the generic to default_factory
# to be specified.
# But for these we require a union, and `|` doesn't work on py39, and uses of
# `Union` gets autofixed by ruff.
# So.... let's just ignore the error for now
artificial_errors: set[ # pyright: ignore[reportUnknownVariableType]
cst.Return | cst.Yield
] = field(default_factory=set)
nodes_needing_checkpoints: list[ # pyright: ignore[reportUnknownVariableType]
cst.Return | cst.Yield | ArtificialStatement
] = field(default_factory=list)
artificial_errors: set[cst.Return | cst.Yield] = field(
default_factory=set[cst.Return | cst.Yield]
)
nodes_needing_checkpoints: list[cst.Return | cst.Yield | ArtificialStatement] = (
field(default_factory=list[cst.Return | cst.Yield | ArtificialStatement])
)

def copy(self):
return LoopState(
Expand Down
3 changes: 1 addition & 2 deletions flake8_async/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ def visit_With(self, node: ast.With | ast.AsyncWith):
nursery_type = "task group"
start_methods = ("create_task",)
else:
# incorrectly marked as not covered on py39
continue # pragma: no cover # https://github.com/nedbat/coveragepy/issues/198
continue

body_call = node.body[0].value
if isinstance(body_call, ast.Await):
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ skip_glob = "tests/*_files/*"
[tool.mypy]
check_untyped_defs = true
disable_error_code = ["no-untyped-def", "misc", "no-untyped-call", "no-any-return"]
python_version = "3.9"
python_version = "3.10"
strict = true
warn_unreachable = true
warn_unused_ignores = false
Expand Down Expand Up @@ -84,7 +84,7 @@ extend-exclude = [
"tests/autofix_files/*"
]
line-length = 90
target-version = "py39"
target-version = "py310"

[tool.ruff.lint]
ignore = [
Expand Down
35 changes: 18 additions & 17 deletions tests/autofix_files/async910.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,23 +430,24 @@ async def try_bare_except_reraises():
...


async def return_in_finally_bare_except_checkpoint():
try:
await trio.sleep(0)
except:
await trio.sleep(0)
finally:
return


async def return_in_finally_bare_except_empty():
try:
await trio.sleep(0)
except:
...
finally:
await trio.lowlevel.checkpoint()
return # error: 8, 'return', Statement('function definition', lineno-6)
# return in finally is a SyntaxError on py314. We currently don't have
# test infra to set a max python version for an eval file.
# async def return_in_finally_bare_except_checkpoint():
# try:
# await trio.sleep(0)
# except:
# await trio.sleep(0)
# finally:
# return
#
#
# async def return_in_finally_bare_except_empty():
# try:
# await trio.sleep(0)
# except:
# ...
# finally:
# return # error: 8, 'return', Statement('function definition', lineno-6)


# early return
Expand Down
8 changes: 1 addition & 7 deletions tests/autofix_files/async910.py.diff
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,7 @@


# safe
@@ x,16 x,19 @@
except:
...
finally:
+ await trio.lowlevel.checkpoint()
return # error: 8, 'return', Statement('function definition', lineno-6)

@@ x,11 x,13 @@

# early return
async def foo_return_1():
Expand Down
84 changes: 84 additions & 0 deletions tests/autofix_files/async91x_autofix.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,87 @@ async def no_checkpoint(): # ASYNC910: 0, "exit", Statement("function definitio
except TypeError:
...
await trio.lowlevel.checkpoint()


# structural pattern matching
async def match_subject() -> None:
match await foo():
case False:
pass


async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
None
):
match foo():
case 1:
...
case _:
await foo()
await trio.lowlevel.checkpoint()


async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
None
):
match bar():
case 1:
await foo()
case 2:
await foo()
case _ if True:
await foo()
await trio.lowlevel.checkpoint()


async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
None
):
match bar():
case 1:
await foo()
case 2:
await foo()
case _ if foo():
await foo()
await trio.lowlevel.checkpoint()


async def match_all_cases() -> None:
match bar():
case 1:
await foo()
case 2:
await foo()
case _:
await foo()


async def match_fallback_await_in_guard() -> None:
# The case guard is only executed if the pattern matches, so we can mostly treat
# it as part of the body, except for a special case for fallback+checkpointing guard.
match foo():
case 1 if await foo():
...
case _ if await foo():
...


async def match_checkpoint_guard() -> None:
# The above pattern is quite cursed, but this seems fairly reasonable to do.
match foo():
case 1 if await foo():
...
case _:
await foo()


async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
None
):
match foo():
case 1:
...
case _ if await foo():
...
await trio.lowlevel.checkpoint()
34 changes: 33 additions & 1 deletion tests/autofix_files/async91x_autofix.py.diff
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,40 @@
yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2) # ASYNC911: 8, "yield", Statement("yield", lineno)

async def bar():
@@ x,3 x,4 @@
@@ x,6 x,7 @@
await foo("1") # type: ignore[call-arg]
except TypeError:
...
+ await trio.lowlevel.checkpoint()


# structural pattern matching
@@ x,6 x,7 @@
...
case _:
await foo()
+ await trio.lowlevel.checkpoint()


async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ x,6 x,7 @@
await foo()
case _ if True:
await foo()
+ await trio.lowlevel.checkpoint()


async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
@@ x,6 x,7 @@
await foo()
case _ if foo():
await foo()
+ await trio.lowlevel.checkpoint()


async def match_all_cases() -> None:
@@ x,3 x,4 @@
...
case _ if await foo():
...
+ await trio.lowlevel.checkpoint()
Loading
Loading