Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bd2c4a7
add nix support
alexeev-prog Jun 16, 2025
c2721c0
update gitignore
alexeev-prog Jun 16, 2025
b4ccf16
add Untrivial Logic in Finally block checker
alexeev-prog Jun 16, 2025
97db0bb
add tests
alexeev-prog Jun 16, 2025
ce045bc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2025
3a1922e
fix UntrivialFinallyBlocksVisitor typing
alexeev-prog Jun 16, 2025
3f44ce7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2025
0837b63
remove nix flake files, revert changelog
alexeev-prog Jun 16, 2025
22628fd
fix
alexeev-prog Jun 16, 2025
0a7be26
fix changelog
alexeev-prog Jun 16, 2025
1de1dd6
fix tests and docs
alexeev-prog Jun 16, 2025
19240c6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2025
03d5978
fix tests
alexeev-prog Jun 16, 2025
169d253
fix exception for tests
alexeev-prog Jun 16, 2025
1709763
Update .gitignore
alexeev-prog Jun 16, 2025
007760f
fix bugs and tests
alexeev-prog Jun 17, 2025
898ae18
fix WPS errors
alexeev-prog Jun 23, 2025
7f1810a
fix WPS errors
alexeev-prog Jun 23, 2025
db403be
fix test noqa
alexeev-prog Jun 24, 2025
aa7c397
fix tests errors
alexeev-prog Jun 27, 2025
1b27636
fix tests errors, add noqa
alexeev-prog Jun 27, 2025
d6f5b9d
add visitor to presets
alexeev-prog Jun 27, 2025
f9f6f51
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 27, 2025
49a9993
fix tests
alexeev-prog Jun 27, 2025
03fa4df
Merge branch 'fat-finally' of github.com:alexeev-prog/wemake-python-s…
alexeev-prog Jun 27, 2025
8eac3b0
fix noqa small error
alexeev-prog Jun 27, 2025
9d34bf3
fix test error
alexeev-prog Jun 27, 2025
7e7d033
Merge branch 'master' into fat-finally
sobolevn Jun 27, 2025
b83f5c6
edit msg template, fix visitor and add more tests
alexeev-prog Jun 27, 2025
b1a6c57
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 27, 2025
1276c81
apply review
alexeev-prog Jun 27, 2025
2f9e7e3
apply review
alexeev-prog Jun 27, 2025
302f10c
fix tests and option
alexeev-prog Jun 27, 2025
1bc6a08
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 27, 2025
1d822ef
Fix CI
sobolevn Jun 27, 2025
11796a0
Fix docs
sobolevn Jun 27, 2025
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
22 changes: 12 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,29 @@ Semantic versioning in our case means:

## 1.2.0 WIP

Due to PEP-695, it's now allowed
to use `[]` in decorators only for `python3.12+`.

```python
@MyClassDecorator[T, V]
def some_function(): ...
```

### Features

- Adds `WPS243`: forbids complex `finally` bodies, #3458
- Adds `WPS478`: forbids using non strict slice operations, #1011
- Adds `WPS479`: forbids using multiline fstrings, #3405
- Adds `WPS480`: forbids using comments inside formatted string, #3404

### Bugfixes

- Removes unnecessary WPS604 and WPS614 rules from the noqa.py, #3420
- Removes unnecessary `WPS604` and `WPS614` rules from the `noqa.py`, #3420
- Fixes `WPS115` false-positive on `StrEnum`, `IntEnum`, `IntFlag` attributes, #3381
- Fixes `WPS432`, now it ignores magic numbers in `Literal`, #3397
- Fixes `WPS466` for generic type specifications `MyClassDecorator[T]`, #3417
- Fixes `WPS212` to ignore nested classes and functions when counting `return` statements, #3413

Due to PEP-695, it's now allowed to use `[]` in the decorator only for `python3.12+`.

```python
@MyClassDecorator[T, V]
def some_function(): ...
```

- Fixes `WPS212` to ignore nested classes and functions
when counting `return` statements, #3413
- Improves `WPS349` highlighting, #3437


Expand Down
9 changes: 9 additions & 0 deletions tests/fixtures/noqa/noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,3 +756,12 @@ async def test_await_in_loop():

some_sequence = some_sequence[::-1] # noqa: WPS478

try: # noqa: WPS243
my_print("1")
except AnyError:
my_print("oh no error")
finally:
my_print("3 / 0")
my_print('zero division error')
my_print(3 / 3)
my_print('no zero division error')
1 change: 1 addition & 0 deletions tests/test_checker/test_noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
'WPS240': 0, # only triggers on 3.12+
'WPS241': 1,
'WPS242': 1,
'WPS243': 1,
'WPS300': 1,
'WPS301': 1,
'WPS302': 0, # disabled since 1.0.0
Expand Down
207 changes: 207 additions & 0 deletions tests/test_visitors/test_ast/test_exceptions/test_complex_finally.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import pytest

from wemake_python_styleguide.violations.complexity import (
ComplexFinallyViolation,
)
from wemake_python_styleguide.visitors.ast.complexity.complex_finally import (
ComplexFinallyBlocksVisitor,
)

# Correct:

no_finally = """
try:
...
except:
...
"""

correct_example1 = """
try:
...
except:
...
finally:
...
"""

correct_example2 = """
try:
...
finally:
...
"""

correct_example3 = """
try:
...
except:
...
finally:
... # 2 lines
...
"""

correct_example4 = """
try:
...
finally:
if ...: # 2 lines
...
"""

# Wrong:

wrong_example1 = """
try:
...
finally:
if ...: # 4 lines
...
else:
...
"""

wrong_example2 = """
try:
...
except:
...
finally:
if ...: # 3 lines
...
...
... # +1 line
"""

wrong_example3 = """
try:
...
except:
...
else:
...
finally:
try: # 4 lines
...
except:
...
"""

wrong_example4 = """
try:
...
finally:
... # 4 lines
...
...
...
"""


@pytest.mark.parametrize(
'code',
[
wrong_example1,
wrong_example2,
wrong_example3,
wrong_example4,
],
)
def test_untrivial_try_blocks(
assert_errors,
assert_error_text,
parse_ast_tree,
code,
default_options,
):
"""Violations are raised when finally blocks exceed default line limit."""
tree = parse_ast_tree(code)

visitor = ComplexFinallyBlocksVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [ComplexFinallyViolation])
assert_error_text(visitor, '4', baseline=2)


@pytest.mark.parametrize(
'code',
[
no_finally,
correct_example1,
correct_example2,
correct_example3,
correct_example4,
],
)
def test_correct_try_blocks(
assert_errors,
parse_ast_tree,
code,
default_options,
):
"""No violations for finally blocks within default line limit."""
tree = parse_ast_tree(code)

visitor = ComplexFinallyBlocksVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize(
'code',
[
no_finally,
correct_example1,
correct_example2,
correct_example3,
correct_example4,
wrong_example1,
wrong_example2,
wrong_example3,
wrong_example4,
],
)
def test_custom_correct_try_blocks(
assert_errors,
parse_ast_tree,
code,
options,
):
"""No violations for finally blocks within custom line limit."""
tree = parse_ast_tree(code)

option_values = options(max_lines_in_finally=5)
visitor = ComplexFinallyBlocksVisitor(option_values, tree=tree)
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize(
'code',
[
correct_example3,
correct_example4,
wrong_example1,
wrong_example2,
wrong_example3,
wrong_example4,
],
)
def test_custom_low_max_lines_in_finally(
assert_errors,
parse_ast_tree,
code,
options,
):
"""Setting `max_lines_in_finally` to 1 will report many usages."""
tree = parse_ast_tree(code)

option_values = options(max_lines_in_finally=1)
visitor = ComplexFinallyBlocksVisitor(option_values, tree=tree)
visitor.run()

assert_errors(visitor, [ComplexFinallyViolation])
8 changes: 8 additions & 0 deletions wemake_python_styleguide/options/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ class of violations that are forbidden to ignore inline, defaults to
- ``max-match-cases`` - maximum number of cases in a match block of code
defaults to
:str:`wemake_python_styleguide.options.defaults.MAX_MATCH_CASES`
- ``max-lines-in-finally`` - maximum lines in finally block of code
defaults to
:str:`wemake_python_styleguide.options.defaults.MAX_LINES_IN_FINALLY`

.. rubric:: Formatter options

Expand Down Expand Up @@ -431,6 +434,11 @@ class Configuration:
defaults.MAX_MATCH_CASES,
'Maximum number of match cases in a single match.',
),
_Option(
'--max-lines-in-finally',
defaults.MAX_LINES_IN_FINALLY,
'Maximum lines of expressions in a finally block.',
),
# Formatter:
_Option(
'--show-violation-links',
Expand Down
3 changes: 3 additions & 0 deletions wemake_python_styleguide/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
# Complexity:
# ===========

#: Maximum lines of expressions in a `finally` block.
MAX_LINES_IN_FINALLY: Final = 2 # guessed

#: Maximum number of `return` statements allowed in a single function.
MAX_RETURNS: Final = 5 # 7-2

Expand Down
1 change: 1 addition & 0 deletions wemake_python_styleguide/options/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class ValidatedOptions:
max_type_params: int = attr.ib(validator=[_min_max(min=1)])
max_match_subjects: int = attr.ib(validator=[_min_max(min=1)])
max_match_cases: int = attr.ib(validator=[_min_max(min=1)])
max_lines_in_finally: int = attr.ib(validator=[_min_max(min=1)])
show_violation_links: bool
exps_for_one_empty_line: int

Expand Down
2 changes: 2 additions & 0 deletions wemake_python_styleguide/presets/topics/complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
annotations,
calls,
classes,
complex_finally,
counts,
function,
imports,
Expand Down Expand Up @@ -39,4 +40,5 @@
annotations.AnnotationComplexityVisitor,
pm.MatchSubjectsVisitor,
pm.MatchCasesVisitor,
complex_finally.ComplexFinallyBlocksVisitor,
)
31 changes: 31 additions & 0 deletions wemake_python_styleguide/violations/complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
TooManyTypeParamsViolation
TooManyMatchSubjectsViolation
TooManyMatchCaseViolation
ComplexFinallyViolation

Module complexity
-----------------
Expand Down Expand Up @@ -109,6 +110,7 @@
.. autoclass:: TooManyTypeParamsViolation
.. autoclass:: TooManyMatchSubjectsViolation
.. autoclass:: TooManyMatchCaseViolation
.. autoclass:: ComplexFinallyViolation

"""

Expand Down Expand Up @@ -1397,3 +1399,32 @@ class TooManyMatchCaseViolation(ASTViolation):

error_template = 'Found too many cases in `match` block: {0}'
code = 242


@final
class ComplexFinallyViolation(ASTViolation):
"""
Forbids complex ``finally`` block.

Reasoning:
``finally`` is very special. It executes code in all
cases and therefore can't fail. When there are many lines in ``finally``
it indicates a larger problem: brittle and complex cleanups.

Solution:
Simplify the ``finally`` block. Use context managers, use ``ExitStack``.

Configuration:
This rule is configurable with ``--max-lines-in-finally``.
Default:
:str:`wemake_python_styleguide.options.defaults.MAX_LINES_IN_FINALLY`

See also:
https://peps.python.org/pep-0765

.. versionadded:: 1.2.0

"""

error_template = 'Found too many lines in `finally` block: {0}'
code = 243
Loading