Skip to content

Commit e477120

Browse files
authored
Fixes #3417 , #3420 (#3423)
* Allow to use ast.Subscript in the decorator (#3417) * Remove unnecessary rules from noqa.py (#3420)
1 parent 4ebd9e2 commit e477120

5 files changed

Lines changed: 82 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,17 @@ Semantic versioning in our case means:
2727

2828
### Bugfixes
2929

30+
- Removes unnecessary WPS604 and WPS614 rules from the noqa.py, #3420
3031
- Fixes `WPS115` false-positive on `StrEnum`, `IntEnum`, `IntFlag` attributes, #3381
3132
- Fixes `WPS432`, now it ignores magic numbers in `Literal`, #3397
33+
- Fixes `WPS466` for generic type specifications `MyClassDecorator[T]`, #3417
34+
35+
Due to PEP-695, it's now allowed to use `[]` in the decorator only for `python3.12+`.
36+
37+
```python
38+
@MyClassDecorator[T, V]
39+
def some_function(): ...
40+
```
3241

3342
## 1.1.0
3443

tests/fixtures/noqa/noqa.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def __init__(self):
107107
self.boom = 7
108108

109109

110-
@property # noqa: WPS614
110+
@property
111111
def function_name( # noqa: WPS614
112112
value: int = 0, # noqa: WPS110
113113
):
@@ -118,7 +118,7 @@ def function_name( # noqa: WPS614
118118

119119
def some(): # noqa: WPS110
120120
class Nested: # noqa: WPS431
121-
"""Docs.""" # noqa: WPS604
121+
"""Docs."""
122122

123123
def nested(): # noqa: WPS430
124124
...
@@ -730,7 +730,7 @@ def wrong_comprehension2():
730730
my_print(4)
731731

732732

733-
@some_decorator['text'] # noqa: WPS466
733+
@some_decorator + other_decorator # noqa: WPS466
734734
def my_function():
735735
return 1
736736

tests/test_visitors/test_ast/test_decorators/test_new_style_decorators.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
from wemake_python_styleguide.compat.constants import PY312
34
from wemake_python_styleguide.violations.best_practices import (
45
NewStyledDecoratorViolation,
56
)
@@ -18,6 +19,39 @@ class Some:
1819
def some(self): ...
1920
"""
2021

22+
invalid_decorators = [
23+
'some[1]',
24+
'some.attr[0]',
25+
'some[0].attr',
26+
'call()[1].attr',
27+
'some + other',
28+
'really @ strange[0]',
29+
]
30+
31+
valid_decorators = [
32+
'some',
33+
'some()',
34+
'some(index[1])',
35+
'some.attr',
36+
'some.attr(1 + 1)',
37+
]
38+
39+
invalid_decorators3_12 = [
40+
'some + other',
41+
'some[1] + other[1]',
42+
'some.attr + other.attr',
43+
'some[0].attr + other.attr[0]',
44+
'really @ strange[0]',
45+
]
46+
47+
valid_decorators3_12 = [
48+
*valid_decorators,
49+
'some[my_type]',
50+
'some[my_type](index)',
51+
'some.attr[my_type]()',
52+
'some.attr(1)[my_type]',
53+
]
54+
2155

2256
@pytest.mark.parametrize(
2357
'code',
@@ -28,14 +62,7 @@ def some(self): ...
2862
)
2963
@pytest.mark.parametrize(
3064
'decorator',
31-
[
32-
'some[1]',
33-
'some.attr[0]',
34-
'some[0].attr',
35-
'call()[1].attr',
36-
'some + other',
37-
'really @ strange[0]',
38-
],
65+
invalid_decorators3_12 if PY312 else invalid_decorators,
3966
)
4067
def test_invalid_decorators(
4168
assert_errors,
@@ -63,13 +90,7 @@ def test_invalid_decorators(
6390
)
6491
@pytest.mark.parametrize(
6592
'decorator',
66-
[
67-
'some',
68-
'some()',
69-
'some(index[1])',
70-
'some.attr',
71-
'some.attr(1 + 1)',
72-
],
93+
valid_decorators3_12 if PY312 else valid_decorators,
7394
)
7495
def test_valid_decorators(
7596
assert_errors,

wemake_python_styleguide/violations/best_practices.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2662,7 +2662,8 @@ class NewStyledDecoratorViolation(ASTViolation):
26622662
Because decorators should be simple and easy to read.
26632663
26642664
Solution:
2665-
Use names, attributes, and calls as decorators only.
2665+
Use names, attributes and calls with generic type specifications
2666+
as decorators only.
26662667
You are free to pass any args to function calls, however.
26672668
26682669
Example::
@@ -2671,8 +2672,18 @@ class NewStyledDecoratorViolation(ASTViolation):
26712672
@some.decorator(args)
26722673
def my_function(): ...
26732674
2675+
Only for ``python3.12+``
2676+
@MyClassDecorator[my_type](args)
2677+
def my_function(): ...
2678+
26742679
# Wrong:
2675-
@some.decorator['method'] + other
2680+
@some.decorator + other.decorator
2681+
def my_function(): ...
2682+
2683+
@some.dict_decorators['method']
2684+
def my_function(): ...
2685+
2686+
@some.list_decorators[index]
26762687
def my_function(): ...
26772688
26782689
.. versionadded:: 0.15.0

wemake_python_styleguide/visitors/ast/decorators.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import ast
22
from typing import Final, final
33

4+
from wemake_python_styleguide.compat.constants import PY312
45
from wemake_python_styleguide.logic.tree import attributes
6+
from wemake_python_styleguide.options.validation import ValidatedOptions
57
from wemake_python_styleguide.types import AnyFunctionDef
68
from wemake_python_styleguide.violations.best_practices import (
79
NewStyledDecoratorViolation,
@@ -15,6 +17,11 @@
1517
ast.Name,
1618
)
1719

20+
_ALLOWED_DECORATOR_TYPES3_12: Final = (
21+
*_ALLOWED_DECORATOR_TYPES,
22+
ast.Subscript, # PEP 695 - Type Parameter Syntax
23+
)
24+
1825

1926
@final
2027
@alias(
@@ -27,6 +34,18 @@
2734
class WrongDecoratorVisitor(BaseNodeVisitor):
2835
"""Checks decorators's correctness."""
2936

37+
def __init__(
38+
self,
39+
options: ValidatedOptions,
40+
tree: ast.AST,
41+
**kwargs,
42+
) -> None:
43+
"""Creates Decorator Visitor."""
44+
self.ALLOWED_DECORATOR_TYPES: Final = (
45+
_ALLOWED_DECORATOR_TYPES3_12 if PY312 else _ALLOWED_DECORATOR_TYPES
46+
)
47+
super().__init__(options, tree, **kwargs)
48+
3049
def visit_any_function(self, node: AnyFunctionDef) -> None:
3150
"""Checks functions' decorators."""
3251
self._check_new_decorator_syntax(node)
@@ -38,13 +57,13 @@ def _check_new_decorator_syntax(self, node: AnyFunctionDef) -> None:
3857
self.add_violation(NewStyledDecoratorViolation(decorator))
3958

4059
def _is_allowed_decorator(self, node: ast.expr) -> bool:
41-
if not isinstance(node, _ALLOWED_DECORATOR_TYPES):
60+
if not isinstance(node, self.ALLOWED_DECORATOR_TYPES):
4261
return False
4362

4463
if isinstance(node, ast.Name):
4564
return True # Simple names are fine!
4665

4766
return all(
48-
isinstance(part, _ALLOWED_DECORATOR_TYPES)
67+
isinstance(part, self.ALLOWED_DECORATOR_TYPES)
4968
for part in attributes.parts(node)
5069
)

0 commit comments

Comments
 (0)