Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* Feat: adds stub of the new violation TooManyRaisesViolation

Test: Adds tests for TooManyRaisesViolation which for now are disabled

Test:
* Adds false positivity test with 3 raises in method
* Adds false positivity test with 4 raises in module
* Fixes wrong number of raises in tests
* Fixes string literals in tests to be more pythonic

Fix: fixes test for python 3.8

Docs: Document TooManyRaisesViolation rule. Issue #16 (#20)

Merge branches

Issue #14 (#21)

* Feat: Implement TooManyRaisesViolation rule and config. Issue #14

* Fix: A priori fix for I001 and WPS230 lint failures. Issue #14

* Fix: A priori fix for AssertionError related to tests. Issue #14

* Fix: Modify test_public_attrs_count tests to ignore class _ComplexityCounter. Issue #14

* Fix: Sort imports in test_public_attrs_count. Issue #14

* Fix: Try to avoid lint failures in test_public_attrs_count. Issue #14

* Fix: Undo a few commits and restore defaults. Issue #14

* Refactor: Create class _ComplexityExitMetrics(object). Issue #14

* Fix: (Temporarily) remove decorator @DataClass from class _ComplexityExitMetrics(object). Issue #14

* Fix: Added _ at the beginning of a public attribute in class _ComplexityCounter(object). Issue #14

* Fix: Added = to define a public attribute in class _ComplexityCounter(object). Issue #14

* Fix: Fix various lint errors. Issue #14

* Docs: Changed docs for class _ComplexityExitMetrics(object). Issue #14

* Test: Added Gwin73's fixes for his tests. Issue #14

* Fix: fixes incorrect annotation for test

* Fix for python 3.8

Co-authored-by: Adrian Westerberg <adrianwesterberg@hotmail.com>

Test: Removes integration tests and add unit tests which right now fail

Fix: Fixed a few lint errrors in test_raises_count.py. Issue #15

Issue #15 (#23)

* Test: Created new tests for issue 3. Issue #15

* Test: Modified WPS235 in test_noqa.py. Issue #15

Co-authored-by: iZafiro <60047972+iZafiro@users.noreply.github.com>

Docs: Added WPS235 to CHANGELOG.md. Issue wemake-services#1160

Docs: Changed weird comment in test_raises.py. Issue wemake-services#1160

Doc: Fixes doc

Test: Simplifies integration test

Test: Adds more tests

Fix: Changes code of violation to make merge easier

Refac: changes imports and formatting after rebasing.

Refact issue wemake-services#1160 (#26)

* Refactor issue wemake-services#1160 (#25)

* Refactor: Refactored function.py to include a @DataClass.

* Fix: Added missing comma to function.py.

* Fix: Tried to fix syntax errors.

* Fix: Tried to fix syntax errors.

* Fix: Tried to fix syntax errors.

* Fix: Tried to fix syntax errors.

* Fix: Tried to fix syntax errors.

* Fix: Tried to fix syntax errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix lint errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Tried to fix test errors.

* Fix: Changed wrong frozen=False tag.

* Fix: Changed default to factory.

* Fix: Fixed lint errors.

* Fix: Added defaultDict variable to dataclass.

* Fix: Lint fix.

* Refactor: Finish refactoring dataclass.

* Fix: Fixed lint errors.

* Fix: Fixed lint errors.

* Fix: Fixed test errors.

* Fixes merge

* Fixes merge

* Fixes merge

Co-authored-by: Gwin73 <adrianwesterberg@hotmail.com>
Co-authored-by: sobolevn <mail@sobolevn.me>
  • Loading branch information
3 people committed Oct 22, 2020
1 parent 2afc9a7 commit aedf714
Show file tree
Hide file tree
Showing 15 changed files with 295 additions and 50 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ Semantic versioning in our case means:
- WPS531: Forbids testing conditions to just return booleans when it is possible to simply return the condition itself
- Forbids to use unsafe infinite loops
- Forbids to use raw strings `r''` when not necessary
- Forbids to use too complex f-strings
- Forbids to use too complex `f`-strings
- Forbids to use too many `raise` statements inside a single function

### Bugfixes

Expand Down
10 changes: 10 additions & 0 deletions tests/fixtures/noqa/noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,3 +743,13 @@ def infinite_loop():
my_print('forever')

unnecessary_raw_string = r'no backslashes.' # noqa: WPS360


def many_raises_function(parameter): # noqa: WPS238
if parameter == 1:
raise ValueError('1')
if parameter == 2:
raise KeyError('2')
if parameter == 3:
raise IndexError('3')
raise TypeError('4')
1 change: 1 addition & 0 deletions tests/test_checker/test_noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
'WPS235': 1,
'WPS236': 1,
'WPS237': 1,
'WPS238': 1,

'WPS300': 1,
'WPS301': 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import pytest

from wemake_python_styleguide.compat.constants import PY38
from wemake_python_styleguide.violations.complexity import (
TooManyArgumentsViolation,
)
from wemake_python_styleguide.visitors.ast.complexity.function import (
FunctionComplexityVisitor,
TooManyArgumentsViolation,
)

lambda_without_arguments = 'lambda: ...'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import pytest

from wemake_python_styleguide.violations.complexity import (
TooManyAwaitsViolation,
)
from wemake_python_styleguide.visitors.ast.complexity.function import (
FunctionComplexityVisitor,
TooManyAwaitsViolation,
)

function_without_awaits = 'def function(): ...'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import pytest

from wemake_python_styleguide.violations.complexity import (
TooManyExpressionsViolation,
)
from wemake_python_styleguide.visitors.ast.complexity.function import (
FunctionComplexityVisitor,
TooManyExpressionsViolation,
)

function_without_expressions = """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-

import pytest

from wemake_python_styleguide.violations.complexity import (
TooManyRaisesViolation,
)
from wemake_python_styleguide.visitors.ast.complexity.function import (
FunctionComplexityVisitor,
)

module_many_raises = """
if some:
raise SomeException
raise SomeOtherException
"""

lambda_many_raises = """
lambda: SomeException if some else SomeOtherException
"""

function_template = """
def function(parameter):
{0}
{1}
{2}
"""

instance_method_template = """
class Test(object):
def method(self, parameter):
{0}
{1}
{2}
"""

class_method_template = """
class Test(object):
@classmethod
def method(cls, parameter):
{0}
{1}
{2}
"""

static_method_template = """
class Test(object):
@staticmethod
def method(parameter):
{0}
{1}
{2}
"""


@pytest.mark.parametrize('code', [
module_many_raises,
lambda_many_raises,
])
def test_asserts_correct_count1(
assert_errors,
parse_ast_tree,
options,
code,
):
"""Testing that raises counted correctly."""
tree = parse_ast_tree(code)

option_values = options(max_raises=1)
visitor = FunctionComplexityVisitor(option_values, tree=tree)
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize('context', [
function_template,
instance_method_template,
class_method_template,
static_method_template,
])
@pytest.mark.parametrize(('first', 'second', 'third'), [
('...', '', ''),
('if some:', ' raise SomeException', 'raise SomeOtherException'),
('def helper():', ' raise SomeException', 'raise SomeOtherException'),
])
def test_raises_correct_count2(
assert_errors,
parse_ast_tree,
context,
first,
second,
third,
default_options,
mode,
):
"""Testing that raises are counted correctly."""
test_instance = context.format(first, second, third)
tree = parse_ast_tree(mode(test_instance))

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

assert_errors(visitor, [])


@pytest.mark.parametrize('context', [
function_template,
instance_method_template,
class_method_template,
static_method_template,
])
@pytest.mark.parametrize(('first', 'second', 'third'), [
('if some:', ' raise SomeException', 'raise SomeOtherException'),
('def helper():', ' raise SomeException', 'raise SomeOtherException'),
])
def test_raises_wrong_count(
assert_errors,
assert_error_text,
parse_ast_tree,
options,
context,
first,
second,
third,
mode,
):
"""Testing that many raises raises a warning."""
test_instance = function_template.format(first, second, third)
tree = parse_ast_tree(mode(test_instance))

option_values = options(max_raises=1)
visitor = FunctionComplexityVisitor(option_values, tree=tree)
visitor.run()

assert_errors(visitor, [TooManyRaisesViolation])
assert_error_text(visitor, '2', option_values.max_raises)
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import pytest

from wemake_python_styleguide.violations.complexity import (
TooManyReturnsViolation,
)
from wemake_python_styleguide.visitors.ast.complexity.function import (
FunctionComplexityVisitor,
TooManyReturnsViolation,
)

function_without_returns = 'def function(): ...'
Expand Down
43 changes: 43 additions & 0 deletions wemake_python_styleguide/logic/complexity/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from collections import defaultdict
from typing import DefaultDict, List

import attr
from typing_extensions import final

from wemake_python_styleguide.types import (
AnyFunctionDef,
AnyFunctionDefAndLambda,
)

#: Function complexity counter.
FunctionCounter = DefaultDict[AnyFunctionDef, int]

#: Function and lambda complexity counter.
FunctionCounterWithLambda = DefaultDict[AnyFunctionDefAndLambda, int]

#: Function and their variables.
FunctionNames = DefaultDict[AnyFunctionDef, List[str]]


def _default_factory() -> FunctionCounter:
"""We use a lot of defaultdic magic in these metrics."""
return defaultdict(int)


@final
@attr.dataclass(frozen=False)
class ComplexityMetrics(object):
"""
Complexity metrics for functions.
We use it as a store of all metrics we count in a function's body.
There are quite a lot of them!
"""

returns: FunctionCounter = attr.ib(factory=_default_factory)
raises: FunctionCounter = attr.ib(factory=_default_factory)
awaits: FunctionCounter = attr.ib(factory=_default_factory)
asserts: FunctionCounter = attr.ib(factory=_default_factory)
expressions: FunctionCounter = attr.ib(factory=_default_factory)
arguments: FunctionCounterWithLambda = attr.ib(factory=_default_factory)
variables: FunctionNames = attr.ib(factory=lambda: defaultdict(list))
9 changes: 9 additions & 0 deletions wemake_python_styleguide/options/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@
- ``max-attributes`` - maximum number of public instance attributes,
defaults to
:str:`wemake_python_styleguide.options.defaults.MAX_ATTRIBUTES`
- ``max-raises`` - maximum number of raises in a function,
defaults to
:str:`wemake_python_styleguide.options.defaults.MAX_RAISES`
- ``max-cognitive-score`` - maximum amount of cognitive complexity
per function, defaults to
:str:`wemake_python_styleguide.options.defaults.MAX_COGNITIVE_SCORE`
Expand Down Expand Up @@ -369,6 +372,12 @@ class Configuration(object):
'Maximum number of public instance attributes.',
),

_Option(
'--max-raises',
defaults.MAX_RAISES,
'Maximum number of raises in a function.',
),

_Option(
'--max-cognitive-score',
defaults.MAX_COGNITIVE_SCORE,
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 @@ -107,6 +107,9 @@
#: Maximum number of public attributes in a single class.
MAX_ATTRIBUTES: Final = 6 # guessed

#: Maximum number of raises in a function.
MAX_RAISES: Final = 3 # guessed

#: Maximum amount of cognitive complexity per function.
MAX_COGNITIVE_SCORE: Final = 12 # based on this code statistics

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 @@ -87,6 +87,7 @@ class _ValidatedOptions(object):
max_asserts: int = attr.ib(validator=[_min_max(min=1)])
max_access_level: int = attr.ib(validator=[_min_max(min=1)])
max_attributes: int = attr.ib(validator=[_min_max(min=1)])
max_raises: int = attr.ib(validator=[_min_max(min=1)])
max_cognitive_score: int = attr.ib(validator=[_min_max(min=1)])
max_cognitive_average: int = attr.ib(validator=[_min_max(min=1)])
max_call_level: int = attr.ib(validator=[_min_max(min=1)])
Expand Down
4 changes: 4 additions & 0 deletions wemake_python_styleguide/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ def max_access_level(self) -> int:
def max_attributes(self) -> int:
...

@property
def max_raises(self) -> int:
...

@property
def max_cognitive_score(self) -> int:
...
Expand Down
34 changes: 32 additions & 2 deletions wemake_python_styleguide/violations/complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
TooManyImportedModuleMembersViolation
TooLongTupleUnpackViolation
TooComplexFormattedStringViolation
TooManyRaisesViolation
Module complexity
-----------------
Expand Down Expand Up @@ -100,6 +100,7 @@
.. autoclass:: TooManyImportedModuleMembersViolation
.. autoclass:: TooLongTupleUnpackViolation
.. autoclass:: TooComplexFormattedStringViolation
.. autoclass:: TooManyRaisesViolation
"""

Expand Down Expand Up @@ -1139,11 +1140,12 @@ class TooManyImportedModuleMembersViolation(ASTViolation):
Default:
:str:`wemake_python_styleguide.options.defaults.MAX_IMPORT_FROM_MEMBERS`
.. versionadded:: 0.14.0
.. versionadded:: 0.15.0
"""

error_template = 'Found too many imported names from a module: {0}'

code = 235


Expand Down Expand Up @@ -1219,3 +1221,31 @@ class TooComplexFormattedStringViolation(ASTViolation):

error_template = 'Found a too complex `f` string'
code = 237


@final
class TooManyRaisesViolation(ASTViolation):
"""
Forbids too many ``raise`` statements in a function.
Reasoning:
Too many ``raise`` statements in a function make the code
untraceable and overcomplicated.
Solution:
Split the function into smaller functions, such that
each of them can raise less errors.
Create more standard errors, or use alternative ways to
raise them.
Configuration:
This rule is configurable with ``--max-raises``.
Default:
:str:`wemake_python_styleguide.options.defaults.MAX_RAISES`
.. versionadded:: 0.15.0
"""

error_template = 'Found too many raises in a function: {0}'
code = 238
Loading

0 comments on commit aedf714

Please sign in to comment.