Skip to content

Commit

Permalink
1733 return only assignments and rename get_attributes to get_assignm…
Browse files Browse the repository at this point in the history
…ents
  • Loading branch information
AlexandrKhabarov committed Mar 8, 2021
1 parent 816cb36 commit f534c8c
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 130 deletions.
1 change: 1 addition & 0 deletions tests/fixtures/noqa/noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ def public(self):
return self

class LambdaAssignment(object):
cls_attr = lambda: ... # noqa: WPS467
def __init__(self):
self._attr = lambda: ... # noqa: WPS467

Expand Down
2 changes: 1 addition & 1 deletion tests/test_checker/test_noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
'WPS464': 0, # logically unacceptable.
'WPS465': 1,
'WPS466': 0, # defined in version specific table.
'WPS467': 1,
'WPS467': 2,

'WPS500': 1,
'WPS501': 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class Example(object):
{0}
"""

classmethod_lambda_assignment = """
class Example(object):
@classmethod
def some(cls):
{0}
"""


@pytest.mark.parametrize('assignment', [
'self.attr = lambda: ...',
Expand Down Expand Up @@ -63,3 +70,27 @@ def test_class_lambda_assignment(
visitor.run()

assert_errors(visitor, [InstanceLambdaAssignmentViolation])

@pytest.mark.parametrize('assignment', [
'cls.attr = lambda: ...',
'cls.attr1 = cls.attr2 = lambda: ...',
'cls.attr1, cls.attr2 = lambda: ..., "value"',
'cls.attr: Callable[[], None] = lambda: ...',
])
def test_classmethod_lambda_assignment(
assert_errors,
assert_error_text,
parse_ast_tree,
default_options,
assignment,
mode,
):
"""Testing lambda assignment to class."""
tree = parse_ast_tree(mode(
classmethod_lambda_assignment.format(assignment),
))

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

assert_errors(visitor, [InstanceLambdaAssignmentViolation])
32 changes: 32 additions & 0 deletions wemake_python_styleguide/logic/naming/name_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from wemake_python_styleguide.compat.functions import get_assign_targets
from wemake_python_styleguide.compat.types import AnyAssignWithWalrus
from wemake_python_styleguide.types import AnyAssign


def is_same_variable(left: ast.AST, right: ast.AST) -> bool:
Expand Down Expand Up @@ -59,6 +60,37 @@ def flat_variable_names(nodes: Iterable[AnyAssignWithWalrus]) -> Iterable[str]:
))


def flat_assignment_values(assigns: Iterable[AnyAssign]) -> Iterable[ast.AST]:
"""
Returns flat values from assignment.
Use this function when you need to get list of values
from assign nodes.
"""
return itertools.chain.from_iterable((
flat_tuples(assign.value)
for assign in assigns
if isinstance(assign.value, ast.AST)
))


def flat_tuples(node: ast.AST) -> List[ast.AST]:
"""
Returns flat values from tuples.
Use this function when you need to get list of values
from tuple nodes.
"""
flatten_nodes: List[ast.AST] = []

if isinstance(node, ast.Tuple):
for subnode in node.elts:
flatten_nodes.extend(flat_tuples(subnode))
else:
flatten_nodes.append(node)
return flatten_nodes


def get_variables_from_node(node: ast.AST) -> List[str]:
"""
Gets the assigned names from the list of nodes.
Expand Down
87 changes: 0 additions & 87 deletions wemake_python_styleguide/logic/tree/assignments.py

This file was deleted.

50 changes: 29 additions & 21 deletions wemake_python_styleguide/logic/tree/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

from typing_extensions import Final

from wemake_python_styleguide import constants
from wemake_python_styleguide.compat.aliases import AssignNodes, FunctionNodes
from wemake_python_styleguide.compat.functions import get_assign_targets
from wemake_python_styleguide.constants import ALLOWED_BUILTIN_CLASSES
from wemake_python_styleguide.logic import nodes
from wemake_python_styleguide.logic.naming.builtins import is_builtin_name
from wemake_python_styleguide.logic.naming.name_nodes import flat_tuples
from wemake_python_styleguide.types import AnyAssign, AnyFunctionDef

#: Type alias for the attributes we return from class inspection.
_AllAttributes = Tuple[List[AnyAssign], List[ast.Attribute]]
#: Type alias for the assignments we return from class inspection.
_AllAssignments = Tuple[List[AnyAssign], List[AnyAssign]]

#: Prefixes that usually define getters and setters.
_GetterSetterPrefixes: Final = ('get_', 'set_')
Expand Down Expand Up @@ -49,11 +52,11 @@ def is_forbidden_super_class(class_name: Optional[str]) -> bool:
return is_builtin_name(class_name)


def get_attributes(
def get_assignments(
node: ast.ClassDef,
*,
include_annotated: bool,
) -> _AllAttributes:
) -> _AllAssignments:
"""
Helper to get all attributes from class nod definitions.
Expand All @@ -70,35 +73,40 @@ def get_attributes(
A tuple of lists for both class and instance level variables.
"""
class_attributes = []
instance_attributes = []
class_assignments = []
instance_assignments = []

for subnode in ast.walk(node):
instance_attr = _get_instance_attribute(subnode)
if instance_attr is not None:
instance_attributes.append(instance_attr)
instance_assign = _get_instance_assignment(subnode)
if instance_assign is not None:
instance_assignments.append(instance_assign)
continue

if include_annotated:
class_attr = _get_annotated_class_attribute(node, subnode)
class_assign = _get_annotated_class_attribute(node, subnode)
else:
class_attr = _get_class_attribute(node, subnode)
if class_attr is not None:
class_attributes.append(class_attr)
class_assign = _get_class_assignment(node, subnode)
if class_assign is not None:
class_assignments.append(class_assign)

return class_attributes, instance_attributes
return class_assignments, instance_assignments


def _get_instance_attribute(node: ast.AST) -> Optional[ast.Attribute]:
return node if (
isinstance(node, ast.Attribute) and
isinstance(node.ctx, ast.Store) and
isinstance(node.value, ast.Name) and
node.value.id == 'self'
def _get_instance_assignment(subnode: ast.AST) -> Optional[AnyAssign]:
return subnode if (
isinstance(subnode, AssignNodes) and
any(
isinstance(target, ast.Attribute) and
isinstance(target.ctx, ast.Store) and
isinstance(target.value, ast.Name) and
target.value.id in constants.SPECIAL_ARGUMENT_NAMES_WHITELIST
for targets in get_assign_targets(subnode)
for target in flat_tuples(targets)
)
) else None


def _get_class_attribute(
def _get_class_assignment(
node: ast.ClassDef,
subnode: ast.AST,
) -> Optional[AnyAssign]:
Expand Down
4 changes: 4 additions & 0 deletions wemake_python_styleguide/violations/best_practices.py
Original file line number Diff line number Diff line change
Expand Up @@ -2594,9 +2594,13 @@ class InstanceLambdaAssignmentViolation(ASTViolation):
class Example(object):
def foo(self):
...
@classmethod
def cls_foo(cls):
...
# Wrong:
class Example(object):
cls_foo = lambda: ...
def __init__(self):
self.foo = lambda: ...
Expand Down
39 changes: 22 additions & 17 deletions wemake_python_styleguide/visitors/ast/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from wemake_python_styleguide.logic.arguments import function_args, super_args
from wemake_python_styleguide.logic.naming import access, name_nodes
from wemake_python_styleguide.logic.tree import (
assignments,
attributes,
classes,
functions,
Expand Down Expand Up @@ -120,18 +119,22 @@ def _is_correct_base_class(self, base_class: ast.AST) -> bool:
return False

def _check_getters_setters_methods(self, node: ast.ClassDef) -> None:
class_attributes, instance_attributes = classes.get_attributes(
class_assignments, instance_assignments = classes.get_assignments(
node,
include_annotated=True,
)
flat_class_attributes = name_nodes.flat_variable_names(class_attributes)
flat_class_attributes = name_nodes.flat_variable_names(class_assignments)

attributes_stripped = {
class_attribute.lstrip(constants.UNUSED_PLACEHOLDER)
for class_attribute in flat_class_attributes
}.union({
instance.attr.lstrip(constants.UNUSED_PLACEHOLDER)
for instance in instance_attributes
instance_attribute.attr.lstrip(constants.UNUSED_PLACEHOLDER)
for assign in instance_assignments
for instance_attribute in walk.get_subnodes_by_type(
assign,
ast.Attribute,
)
})

for method in classes.find_getters_and_setters(node):
Expand Down Expand Up @@ -373,22 +376,23 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
self.generic_visit(node)

def _check_attributes_shadowing(self, node: ast.ClassDef) -> None:
class_attributes, instance_attributes = classes.get_attributes(
class_assignments, instance_assignments = classes.get_assignments(
node,
include_annotated=False,
)
class_attribute_names = set(
name_nodes.flat_variable_names(class_attributes),
name_nodes.flat_variable_names(class_assignments),
)

for instance_attr in instance_attributes:
if instance_attr.attr in class_attribute_names:
self.add_violation(
oop.ShadowedClassAttributeViolation(
instance_attr,
text=instance_attr.attr,
),
)
for instance_assignment in instance_assignments:
for instance_attr in walk.get_subnodes_by_type(instance_assignment, ast.Attribute):
if instance_attr.attr in class_attribute_names:
self.add_violation(
oop.ShadowedClassAttributeViolation(
instance_attr,
text=instance_attr.attr,
),
)


@final
Expand Down Expand Up @@ -452,10 +456,11 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
self.generic_visit(node)

def _check_lambda_assignment(self, node: ast.ClassDef) -> None:
class_assignments, instance_assignments = assignments.get_assignments(
class_assignments, instance_assignments = classes.get_assignments(
node,
include_annotated=True,
)
flatten_values = assignments.flat_assignment_values(
flatten_values = name_nodes.flat_assignment_values(
itertools.chain(
class_assignments,
instance_assignments,
Expand Down
Loading

0 comments on commit f534c8c

Please sign in to comment.