Skip to content

Commit e0fed3e

Browse files
authored
Merge branch 'main' into 91x_autofix
2 parents b926c40 + 881b16a commit e0fed3e

File tree

10 files changed

+88
-33
lines changed

10 files changed

+88
-33
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ repos:
4848
rev: 6.0.0
4949
hooks:
5050
- id: flake8
51-
args: ["--exclude", ".*,tests/eval_files/*,tests/autofix_files/*"]
51+
types: ["python", "pyi"]
5252
language_version: python3
5353
additional_dependencies:
5454
- flake8-builtins
@@ -67,12 +67,15 @@ repos:
6767
- flake8-return
6868
- flake8-simplify
6969
- flake8-type-checking
70+
- flake8-pyi
7071

72+
# Pinned to flake8 5.0.4 since flake8-eradicate is not updated to work with flake8 6
73+
# https://github.com/wemake-services/flake8-eradicate/pull/271
7174
- repo: https://github.com/PyCQA/flake8
7275
rev: 5.0.4
7376
hooks:
7477
- id: flake8
75-
args: ["--exclude", ".*,tests/eval_files/*", "--select=E800"]
78+
args: ["--select=E800"]
7679
language_version: python3
7780
additional_dependencies:
7881
- flake8-eradicate

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,23 @@ pip install flake8-trio
2020

2121
## List of warnings
2222

23-
- **TRIO100**: a `with trio.fail_after(...):` or `with trio.move_on_after(...):`
23+
- **TRIO100**: A `with trio.fail_after(...):` or `with trio.move_on_after(...):`
2424
context does not contain any `await` statements. This makes it pointless, as
2525
the timeout can only be triggered by a checkpoint.
2626
- **TRIO101**: `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
27-
- **TRIO102**: it's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled` unless you use a shielded
27+
- **TRIO102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled` unless you use a shielded
2828
cancel scope with a timeout.
2929
- **TRIO103**: `except BaseException`, `except trio.Cancelled` or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled` before.
3030
- **TRIO104**: `Cancelled` and `BaseException` must be re-raised - when a user tries to `return` or `raise` a different exception.
3131
- **TRIO105**: Calling a trio async function without immediately `await`ing it.
32-
- **TRIO106**: trio must be imported with `import trio` for the linter to work.
32+
- **TRIO106**: `trio` must be imported with `import trio` for the linter to work.
3333
- **TRIO107**: Renamed to TRIO910
3434
- **TRIO108**: Renamed to TRIO911
3535
- **TRIO109**: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead
3636
- **TRIO110**: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
3737
- **TRIO111**: Variable, from context manager opened inside nursery, passed to `start[_soon]` might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.
38-
- **TRIO112**: nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.
39-
- **TRIO113**: using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`.
38+
- **TRIO112**: Nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.
39+
- **TRIO113**: Using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`.
4040
- **TRIO114**: Startable function (i.e. has a `task_status` keyword parameter) not in `--startable-in-context-manager` parameter list, please add it so TRIO113 can catch errors when using it.
4141
- **TRIO115**: Replace `trio.sleep(0)` with the more suggestive `trio.lowlevel.checkpoint()`.
4242
- **TRIO116**: `trio.sleep()` with >24 hour interval should usually be`trio.sleep_forever()`.
@@ -59,8 +59,8 @@ pip install flake8-trio
5959

6060
### Warnings disabled by default
6161
- **TRIO900**: Async generator without `@asynccontextmanager` not allowed.
62-
- **TRIO910**: exit or `return` from async function with no guaranteed checkpoint or exception since function definition.
63-
- **TRIO911**: exit, yield or return from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
62+
- **TRIO910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition.
63+
- **TRIO911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
6464
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
6565

6666
## Configuration

flake8_trio/visitors/helpers.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,24 @@ def iter_guaranteed_once(iterable: ast.expr) -> bool:
137137
and iterable.func.id == "range"
138138
):
139139
try:
140-
return len(range(*[ast.literal_eval(a) for a in iterable.args])) > 0
140+
values = [ast.literal_eval(a) for a in iterable.args]
141141
except Exception: # noqa: PIE786
142+
# parameters aren't literal
142143
return False
144+
145+
try:
146+
evaluated_range = range(*values)
147+
except (ValueError, TypeError):
148+
str_values = ", ".join(map(str, values))
149+
raise RuntimeError(
150+
f"Invalid literal values to range function: `range({str_values})`"
151+
)
152+
153+
try:
154+
return len(evaluated_range) > 0
155+
# if the length is > sys.maxsize
156+
except OverflowError:
157+
return True
143158
return False
144159

145160

@@ -186,7 +201,10 @@ def iter_guaranteed_once_cst(iterable: cst.BaseExpression) -> bool:
186201
try:
187202
evaluated_range = range(*values)
188203
except (ValueError, TypeError):
189-
return False
204+
str_values = ", ".join(map(str, values))
205+
raise RuntimeError(
206+
f"Invalid literal values to range function: `range({str_values})`"
207+
)
190208
try:
191209
return len(evaluated_range) > 0
192210
# if the length is > sys.maxsize

tests/eval_files/trio103.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ def foo() -> Any:
250250
for i in range(3):
251251
raise
252252

253+
try:
254+
...
255+
except BaseException:
256+
for i in range(27670116110564327421):
257+
raise
258+
253259
try:
254260
...
255261
except BaseException: # TRIO103_trio: 7, "BaseException"

tests/eval_files/trio911.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -821,16 +821,6 @@ async def foo_loop_static():
821821
await foo()
822822
yield
823823

824-
# malformed range calls
825-
for i in range(5, 2, 3, 4): # type: ignore
826-
await foo()
827-
yield # error: 4, "yield", Stmt("yield", line-5)
828-
829-
# is not caught by type checkers
830-
for i in range(1, 10, 0):
831-
await foo()
832-
yield # error: 4, "yield", Stmt("yield", line-5)
833-
834824
for i in range(1 + 1): # not handled
835825
await foo()
836826
yield # error: 4, "yield", Stmt("yield", line-4)

tests/test_config_and_args.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ def test_anyio_from_config(tmp_path: Path, capsys: pytest.CaptureFixture[str]):
110110
returnvalue = main(
111111
argv=[
112112
err_file,
113-
"--append-config",
113+
"--config",
114114
str(tmp_path / ".flake8"),
115115
]
116116
)
117-
assert returnvalue == 1
118117
out, err = capsys.readouterr()
119118
assert not err
120119
assert expected == out
120+
assert returnvalue == 1
121121

122122

123123
def _test_trio200_from_config_common(tmp_path: Path) -> str:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Checks for exceptions raised on invalid code."""
2+
3+
import ast
4+
5+
import libcst as cst
6+
import pytest
7+
8+
from flake8_trio.visitors.helpers import iter_guaranteed_once, iter_guaranteed_once_cst
9+
10+
11+
def _raises_on_code_cst(source: str):
12+
expression = cst.parse_expression(source)
13+
with pytest.raises(RuntimeError, match="Invalid literal values.*"):
14+
iter_guaranteed_once_cst(expression)
15+
16+
17+
def test_iter_guaranteed_once_cst():
18+
_raises_on_code_cst("range(1, 10, 0)")
19+
_raises_on_code_cst("range(5, 2, 3, 4)")
20+
21+
22+
def _raises_on_code_ast(source: str):
23+
expression = ast.parse(source).body[0]
24+
assert isinstance(expression, ast.Expr)
25+
call = expression.value
26+
assert isinstance(call, ast.Call)
27+
with pytest.raises(RuntimeError, match="Invalid literal values.*"):
28+
iter_guaranteed_once(call)
29+
30+
31+
def test_iter_guaranteed_once():
32+
_raises_on_code_ast("range(1, 10, 0)")
33+
_raises_on_code_ast("range(5, 2, 3, 4)")

tox.ini

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ source =
3838
[flake8]
3939
max-line-length = 90
4040
extend-ignore = S101, D101, D102, D103, D105, D106, D107, TC006
41-
per-file-ignores = flake8_trio/visitors/__init__.py: F401, E402
41+
extend-enable = TC10
42+
exclude = .*, tests/eval_files/*
43+
per-file-ignores =
44+
flake8_trio/visitors/__init__.py: F401, E402
45+
# (TC002, TC003) We don't care about moving imports into type-checking blocks
46+
# see https://github.com/snok/flake8-type-checking/issues/152
47+
# (E301, E302) black formats stub files without excessive blank lines
48+
# (D) we don't care about docstrings in stub files
49+
# (Y021, Y048) pyright includes docstrings in generated files, so don't complain about them
50+
*.pyi: TC002, TC003, D, E301, E302
4251

4352
[coverage:report]
4453
exclude_lines =

typings/flake8/__init__.pyi

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
This type stub file was generated by pyright.
33
"""
44

5-
from __future__ import annotations
6-
75
import logging
86

97
"""Top-level module for Flake8.

typings/flake8/options/manager.pyi

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
"""
22
This type stub file was generated by pyright.
3+
34
Generated for flake8 5, so OptionManager signature is incorrect for flake8 6
45
"""
56

6-
from __future__ import annotations
7-
87
import argparse
9-
from collections.abc import Mapping, Sequence
10-
from typing import Any, Callable
8+
from collections.abc import Callable, Mapping, Sequence
9+
from typing import Any
1110

1211
from flake8.plugins.finder import Plugins
1312

@@ -44,12 +43,12 @@ class Option:
4443
long_option_name: str | _ARG = ...,
4544
action: str | type[argparse.Action] | _ARG = ...,
4645
default: Any | _ARG = ...,
47-
type: str | Callable[..., Any] | _ARG = ...,
46+
type: str | Callable[..., Any] | _ARG = ..., # noqa: A002
4847
dest: str | _ARG = ...,
4948
nargs: int | str | _ARG = ...,
5049
const: Any | _ARG = ...,
5150
choices: Sequence[Any] | _ARG = ...,
52-
help: str | _ARG = ...,
51+
help: str | _ARG = ..., # noqa: A002
5352
metavar: str | _ARG = ...,
5453
callback: Callable[..., Any] | _ARG = ...,
5554
callback_args: Sequence[Any] | _ARG = ...,
@@ -125,7 +124,6 @@ class Option:
125124
def filtered_option_kwargs(self) -> dict[str, Any]:
126125
"""Return any actually-specified arguments."""
127126
...
128-
def __repr__(self) -> str: ...
129127
def normalize(self, value: Any, *normalize_args: str) -> Any:
130128
"""Normalize the value based on the option configuration."""
131129
...

0 commit comments

Comments
 (0)