Skip to content

Commit c137af3

Browse files
committed
Fix crash on weird decorators
1 parent 490a775 commit c137af3

File tree

4 files changed

+32
-4
lines changed

4 files changed

+32
-4
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22
*[CalVer, YY.month.patch](https://calver.org/)*
33

4+
## 22.9.2
5+
- Fix a crash on nontrivial decorator expressions (calls, PEP-614) and document behavior.
6+
47
## 22.9.1
58
- Add `--no-checkpoint-warning-decorators` option, to disable missing-checkpoint warnings for certain decorated functions.
69

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pip install flake8-trio
4040
## Configuration
4141
`no-checkpoint-warning-decorators`: Specify a list of decorators to disable checkpointing checks for, turning off TRIO107 and TRIO108 warnings for functions decorated with any decorator matching any in the list. Matching is done with [fnmatch](https://docs.python.org/3/library/fnmatch.html). Defaults to disabling for `asynccontextmanager`.
4242

43+
Decorators-to-match must be identifiers or dotted names only (not PEP-614 expressions), and will match against the name only - e.g. `foo.bar` matches `foo.bar`, `foo.bar()`, and `foo.bar(args, here)`, etc.
44+
4345
For example:
4446
```
4547
[flake8]

flake8_trio.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from flake8.options.manager import OptionManager
3030

3131
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
32-
__version__ = "22.9.1"
32+
__version__ = "22.9.2"
3333

3434

3535
Error_codes = {
@@ -208,14 +208,22 @@ def has_decorator(decorator_list: List[ast.expr], *names: str):
208208

209209
# matches the full decorator name against fnmatch pattern
210210
def fnmatch_decorator(decorator_list: List[ast.expr], *patterns: str):
211-
def construct_name(expr: ast.expr) -> str:
211+
def construct_name(expr: ast.expr) -> Optional[str]:
212+
if isinstance(expr, ast.Call):
213+
expr = expr.func
212214
if isinstance(expr, ast.Name):
213215
return expr.id
214-
assert isinstance(expr, ast.Attribute)
215-
return construct_name(expr.value) + "." + expr.attr
216+
elif isinstance(expr, ast.Attribute):
217+
attr = construct_name(expr.value)
218+
assert attr is not None
219+
return attr + "." + expr.attr
220+
# See https://peps.python.org/pep-0614/ - we don't handle everything
221+
return None # pragma: no cover # impossible on Python 3.8
216222

217223
for decorator in decorator_list:
218224
qualified_decorator_name = construct_name(decorator)
225+
if qualified_decorator_name is None:
226+
continue # pragma: no cover # impossible on Python 3.8
219227
for pattern in patterns:
220228
if fnmatch(qualified_decorator_name, pattern.lstrip("@")):
221229
return True

tests/test_decorator.py

+15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import ast
2+
import sys
23
from argparse import Namespace
34
from typing import Tuple
45

6+
import pytest
57
from flake8.main.application import Application
68

79
from flake8_trio import Error_codes, Plugin, Statement, fnmatch_decorator
@@ -69,6 +71,19 @@ def test_at():
6971
assert wrap(("foo.bar",), "@foo.bar")
7072

7173

74+
def test_calls():
75+
assert wrap(("foo()",), "@foo")
76+
assert wrap(("foo(1, 2, *x, **y)",), "@foo")
77+
assert wrap(("foo.bar()",), "@foo.bar")
78+
assert wrap(("foo.bar(1, 2, *x, **y)",), "@foo.bar")
79+
80+
81+
@pytest.mark.skipif(sys.version_info[:2] < (3, 9), reason="not yet supported")
82+
def test_pep614():
83+
# Just don't crash and we'll be good.
84+
assert not wrap(("(any, expression, we, like)",), "no match here")
85+
86+
7287
def test_plugin():
7388
tree = dec_list("app.route")
7489
plugin = Plugin(tree)

0 commit comments

Comments
 (0)