community-of-python-flake8-plugin 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/PKG-INFO +1 -1
  2. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/pyproject.toml +1 -1
  3. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/final_class.py +14 -2
  4. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/function_verb.py +7 -6
  5. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/mapping_proxy.py +5 -5
  6. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/name_length.py +45 -36
  7. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/temp_var.py +38 -37
  8. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/constants.py +2 -0
  9. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/plugin.py +10 -10
  10. community_of_python_flake8_plugin-0.3.2/src/community_of_python_flake8_plugin/utils.py +46 -0
  11. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/violation_codes.py +3 -1
  12. community_of_python_flake8_plugin-0.3.1/src/community_of_python_flake8_plugin/utils.py +0 -35
  13. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/README.md +0 -0
  14. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/__init__.py +0 -0
  15. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/__init__.py +0 -0
  16. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/async_get_prefix.py +0 -0
  17. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/dataclass_config.py +0 -0
  18. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/disabled/__init__.py +0 -0
  19. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/disabled/module_import_many_names.py +0 -0
  20. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/for_loop_one_prefix.py +0 -0
  21. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/module_import_stdlib.py +0 -0
  22. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/scalar_annotation.py +0 -0
  23. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/py.typed +0 -0
  24. {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/violations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: community-of-python-flake8-plugin
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Community of Python flake8 plugin
5
5
  Author: Lev Vereshchagin
6
6
  Author-email: Lev Vereshchagin <mail@vrslev.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "community-of-python-flake8-plugin"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "Community of Python flake8 plugin"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Lev Vereshchagin", email = "mail@vrslev.com" }]
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  import ast
3
3
  import typing
4
4
 
5
+ from community_of_python_flake8_plugin.utils import check_inherits_from_bases
5
6
  from community_of_python_flake8_plugin.violation_codes import ViolationCodes
6
7
  from community_of_python_flake8_plugin.violations import Violation
7
8
 
@@ -25,9 +26,20 @@ def is_protocol_class(class_node: ast.ClassDef) -> bool:
25
26
  # Check for attributed Protocol reference: class MyClass(typing.Protocol):
26
27
  if isinstance(one_base, ast.Attribute) and one_base.attr == "Protocol":
27
28
  return True
29
+ # Check for subscripted Protocol reference: class MyClass(Protocol[SomeType]):
30
+ if isinstance(one_base, ast.Subscript):
31
+ if isinstance(one_base.value, ast.Name) and one_base.value.id == "Protocol":
32
+ return True
33
+ if isinstance(one_base.value, ast.Attribute) and one_base.value.attr == "Protocol":
34
+ return True
28
35
  return False
29
36
 
30
37
 
38
+ def is_model_factory_class(class_node: ast.ClassDef) -> bool:
39
+ """Check if the class inherits from ModelFactory or SQLAlchemyFactory."""
40
+ return check_inherits_from_bases(class_node, {"ModelFactory", "SQLAlchemyFactory"})
41
+
42
+
31
43
  @typing.final
32
44
  class FinalClassCheck(ast.NodeVisitor):
33
45
  def __init__(self, syntax_tree: ast.AST) -> None: # noqa: ARG002
@@ -38,8 +50,8 @@ class FinalClassCheck(ast.NodeVisitor):
38
50
  self.generic_visit(ast_node)
39
51
 
40
52
  def _check_final_decorator(self, ast_node: ast.ClassDef) -> None:
41
- # Skip Protocol classes and test classes
42
- if is_protocol_class(ast_node) or ast_node.name.startswith("Test"):
53
+ # Skip Protocol classes, test classes, and ModelFactory classes
54
+ if is_protocol_class(ast_node) or ast_node.name.startswith("Test") or is_model_factory_class(ast_node):
43
55
  return
44
56
 
45
57
  if not contains_final_decorator(ast_node):
@@ -11,19 +11,20 @@ from community_of_python_flake8_plugin.violations import Violation
11
11
  def check_is_ignored_name(identifier: str) -> bool:
12
12
  if identifier == "main":
13
13
  return True
14
- if identifier.startswith("__") and identifier.endswith("__"):
15
- return True
16
- return bool(identifier.startswith("_"))
14
+ return bool(identifier.startswith("__") and identifier.endswith("__"))
17
15
 
18
16
 
19
17
  def check_is_verb_name(identifier: str) -> bool:
20
- return any(identifier == verb_name or identifier.startswith(f"{verb_name}_") for verb_name in VERB_PREFIXES)
18
+ return any(
19
+ identifier == one_verb_name or identifier.startswith((f"{one_verb_name}_", f"_{one_verb_name}"))
20
+ for one_verb_name in VERB_PREFIXES
21
+ )
21
22
 
22
23
 
23
24
  def check_is_property(function_node: ast.AST) -> bool:
24
25
  if not isinstance(function_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
25
26
  return False
26
- return any(check_is_property_decorator(decorator) for decorator in function_node.decorator_list) # noqa: COP011
27
+ return any(check_is_property_decorator(one_decorator) for one_decorator in function_node.decorator_list) # noqa: COP011
27
28
 
28
29
 
29
30
  def check_is_property_decorator(decorator: ast.expr) -> bool: # noqa: PLR0911
@@ -56,7 +57,7 @@ def check_is_property_decorator(decorator: ast.expr) -> bool: # noqa: PLR0911
56
57
  def check_is_pytest_fixture(function_node: ast.AST) -> bool:
57
58
  if not isinstance(function_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
58
59
  return False
59
- return any(check_is_fixture_decorator(decorator) for decorator in function_node.decorator_list) # noqa: COP011
60
+ return any(check_is_fixture_decorator(one_decorator) for one_decorator in function_node.decorator_list) # noqa: COP011
60
61
 
61
62
 
62
63
  def check_is_fixture_decorator(decorator: ast.expr) -> bool:
@@ -71,9 +71,9 @@ class MappingProxyCheck(ast.NodeVisitor):
71
71
  self.violations: list[Violation] = []
72
72
 
73
73
  def visit_Module(self, ast_node: ast.Module) -> None:
74
- for statement in ast_node.body:
75
- if isinstance(statement, (ast.Assign, ast.AnnAssign)):
76
- self._check_mapping_assignment(statement)
74
+ for one_statement in ast_node.body:
75
+ if isinstance(one_statement, (ast.Assign, ast.AnnAssign)):
76
+ self._check_mapping_assignment(one_statement)
77
77
  self.generic_visit(ast_node)
78
78
 
79
79
  def _check_mapping_assignment(self, ast_node: ast.Assign | ast.AnnAssign) -> None:
@@ -99,8 +99,8 @@ class MappingProxyCheck(ast.NodeVisitor):
99
99
  # Only check module-level assignments (no parent function/class)
100
100
  if assigned_value is not None and isinstance(assigned_value, ast.Dict) and assignment_targets:
101
101
  # Check if this is a module-level assignment
102
- for target in assignment_targets: # noqa: COP011
103
- if isinstance(target, ast.Name):
102
+ for one_target in assignment_targets: # noqa: COP011
103
+ if isinstance(one_target, ast.Name):
104
104
  self.violations.append(
105
105
  Violation(
106
106
  line_number=ast_node.lineno,
@@ -3,7 +3,11 @@ import ast
3
3
  import typing
4
4
 
5
5
  from community_of_python_flake8_plugin.constants import FINAL_CLASS_EXCLUDED_BASES, MIN_NAME_LENGTH
6
- from community_of_python_flake8_plugin.utils import check_inherits_from_bases, find_parent_class_definition
6
+ from community_of_python_flake8_plugin.utils import (
7
+ check_inherits_from_bases,
8
+ find_parent_class_definition,
9
+ find_parent_function_definition,
10
+ )
7
11
  from community_of_python_flake8_plugin.violation_codes import ViolationCodes
8
12
  from community_of_python_flake8_plugin.violations import Violation
9
13
 
@@ -33,7 +37,7 @@ def check_is_whitelisted_annotation(annotation: ast.expr | None) -> bool:
33
37
  def check_is_pytest_fixture(ast_node: ast.AST) -> bool:
34
38
  if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
35
39
  return False
36
- return any(check_is_fixture_decorator(decorator) for decorator in ast_node.decorator_list) # noqa: COP011
40
+ return any(check_is_fixture_decorator(one_decorator) for one_decorator in ast_node.decorator_list) # noqa: COP011
37
41
 
38
42
 
39
43
  def check_is_fixture_decorator(decorator: ast.expr) -> bool:
@@ -58,9 +62,11 @@ class COP004NameLengthCheck(ast.NodeVisitor):
58
62
  self.generic_visit(ast_node)
59
63
 
60
64
  def visit_Assign(self, ast_node: ast.Assign) -> None:
61
- for target in ast_node.targets:
62
- if isinstance(target, ast.Name):
63
- self.validate_name_length(target.id, ast_node, find_parent_class_definition(self.syntax_tree, ast_node))
65
+ for one_target in ast_node.targets:
66
+ if isinstance(one_target, ast.Name):
67
+ self.validate_name_length(
68
+ one_target.id, ast_node, find_parent_class_definition(self.syntax_tree, ast_node)
69
+ )
64
70
  self.generic_visit(ast_node)
65
71
 
66
72
  def visit_FunctionDef(self, ast_node: ast.FunctionDef) -> None:
@@ -79,18 +85,18 @@ class COP004NameLengthCheck(ast.NodeVisitor):
79
85
  self.generic_visit(ast_node)
80
86
 
81
87
  def visit_ListComp(self, ast_node: ast.ListComp) -> None:
82
- for comprehension in ast_node.generators:
83
- self._validate_comprehension_target(comprehension.target)
88
+ for one_comprehension in ast_node.generators:
89
+ self._validate_comprehension_target(one_comprehension.target)
84
90
  self.generic_visit(ast_node)
85
91
 
86
92
  def visit_SetComp(self, ast_node: ast.SetComp) -> None:
87
- for comprehension in ast_node.generators:
88
- self._validate_comprehension_target(comprehension.target)
93
+ for one_comprehension in ast_node.generators:
94
+ self._validate_comprehension_target(one_comprehension.target)
89
95
  self.generic_visit(ast_node)
90
96
 
91
97
  def visit_DictComp(self, ast_node: ast.DictComp) -> None:
92
- for comprehension in ast_node.generators:
93
- self._validate_comprehension_target(comprehension.target)
98
+ for one_comprehension in ast_node.generators:
99
+ self._validate_comprehension_target(one_comprehension.target)
94
100
  self.generic_visit(ast_node)
95
101
 
96
102
  def visit_Lambda(self, ast_node: ast.Lambda) -> None:
@@ -98,9 +104,9 @@ class COP004NameLengthCheck(ast.NodeVisitor):
98
104
  self.generic_visit(ast_node)
99
105
 
100
106
  def visit_With(self, ast_node: ast.With) -> None:
101
- for item in ast_node.items:
102
- if item.optional_vars is not None:
103
- self._validate_with_target(item.optional_vars)
107
+ for one_item in ast_node.items:
108
+ if one_item.optional_vars is not None:
109
+ self._validate_with_target(one_item.optional_vars)
104
110
  self.generic_visit(ast_node)
105
111
 
106
112
  def visit_ExceptHandler(self, ast_node: ast.ExceptHandler) -> None:
@@ -109,18 +115,18 @@ class COP004NameLengthCheck(ast.NodeVisitor):
109
115
  self.generic_visit(ast_node)
110
116
 
111
117
  def visit_GeneratorExp(self, ast_node: ast.GeneratorExp) -> None:
112
- for comprehension in ast_node.generators:
113
- self._validate_comprehension_target(comprehension.target)
118
+ for one_comprehension in ast_node.generators:
119
+ self._validate_comprehension_target(one_comprehension.target)
114
120
  self.generic_visit(ast_node)
115
121
 
116
122
  def _validate_function_args(self, arguments_node: ast.arguments) -> None:
117
123
  # Process all argument types
118
- for argument in arguments_node.posonlyargs:
119
- self._validate_argument_name_length(argument)
120
- for argument in arguments_node.args:
121
- self._validate_argument_name_length(argument)
122
- for argument in arguments_node.kwonlyargs:
123
- self._validate_argument_name_length(argument)
124
+ for one_argument in arguments_node.posonlyargs:
125
+ self._validate_argument_name_length(one_argument)
126
+ for one_argument in arguments_node.args:
127
+ self._validate_argument_name_length(one_argument)
128
+ for one_argument in arguments_node.kwonlyargs:
129
+ self._validate_argument_name_length(one_argument)
124
130
 
125
131
  if arguments_node.vararg is not None:
126
132
  self._validate_argument_name_length(arguments_node.vararg)
@@ -157,8 +163,8 @@ class COP004NameLengthCheck(ast.NodeVisitor):
157
163
  )
158
164
  elif isinstance(comprehension_target, ast.Tuple):
159
165
  # Handle tuple unpacking in comprehensions like [(a, b) for a, b in pairs]
160
- for elt in comprehension_target.elts:
161
- self._validate_comprehension_target(elt)
166
+ for one_elt in comprehension_target.elts:
167
+ self._validate_comprehension_target(one_elt)
162
168
 
163
169
  def _validate_with_target(self, target_node: ast.expr) -> None:
164
170
  if isinstance(target_node, ast.Name):
@@ -173,8 +179,8 @@ class COP004NameLengthCheck(ast.NodeVisitor):
173
179
  )
174
180
  elif isinstance(target_node, ast.Tuple):
175
181
  # Handle tuple unpacking in with statements like with open(f1) as f1, open(f2) as f2:
176
- for elt in target_node.elts:
177
- self._validate_with_target(elt)
182
+ for one_elt in target_node.elts:
183
+ self._validate_with_target(one_elt)
178
184
 
179
185
  def _validate_except_target(self, ast_node: ast.ExceptHandler) -> None:
180
186
  # For except targets, we'll treat them as variables
@@ -204,15 +210,18 @@ class COP004NameLengthCheck(ast.NodeVisitor):
204
210
  return
205
211
 
206
212
  if len(identifier) < MIN_NAME_LENGTH:
207
- # Determine if this is an attribute (inside a class) or variable (at module level)
213
+ # Determine if this is an attribute (inside a class but not in a method) or variable
214
+ parent_function: typing.Final = find_parent_function_definition(self.syntax_tree, ast_node)
215
+
216
+ # It's an attribute only if it's in a class but NOT in a function/method
217
+ is_attribute: typing.Final = parent_class is not None and parent_function is None
218
+
208
219
  self.violations.append(
209
220
  Violation(
210
221
  line_number=ast_node.lineno,
211
222
  column_number=ast_node.col_offset,
212
223
  violation_code=(
213
- ViolationCodes.ATTRIBUTE_NAME_LENGTH
214
- if parent_class is not None
215
- else ViolationCodes.VARIABLE_NAME_LENGTH
224
+ ViolationCodes.ATTRIBUTE_NAME_LENGTH if is_attribute else ViolationCodes.VARIABLE_NAME_LENGTH
216
225
  ),
217
226
  )
218
227
  )
@@ -240,12 +249,12 @@ class COP004NameLengthCheck(ast.NodeVisitor):
240
249
 
241
250
  def validate_function_args(self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
242
251
  # Process all argument types
243
- for argument in ast_node.args.posonlyargs:
244
- self.validate_argument_name_length(argument)
245
- for argument in ast_node.args.args:
246
- self.validate_argument_name_length(argument)
247
- for argument in ast_node.args.kwonlyargs:
248
- self.validate_argument_name_length(argument)
252
+ for one_argument in ast_node.args.posonlyargs:
253
+ self.validate_argument_name_length(one_argument)
254
+ for one_argument in ast_node.args.args:
255
+ self.validate_argument_name_length(one_argument)
256
+ for one_argument in ast_node.args.kwonlyargs:
257
+ self.validate_argument_name_length(one_argument)
249
258
 
250
259
  if ast_node.args.vararg is not None:
251
260
  self.validate_argument_name_length(ast_node.args.vararg)
@@ -15,32 +15,30 @@ def extract_names(expression: ast.expr) -> typing.Iterable[str]:
15
15
  if isinstance(expression, ast.Name):
16
16
  yield expression.id
17
17
  elif isinstance(expression, ast.Tuple):
18
- for elt in expression.elts:
19
- yield from extract_names(elt)
18
+ for one_elt in expression.elts:
19
+ yield from extract_names(one_elt)
20
20
 
21
21
 
22
- def is_single_line_assignment(assign_node: ast.Assign) -> bool:
22
+ def is_single_line_assignment(assign_node: ast.Assign | ast.AnnAssign) -> bool:
23
23
  """Check if assignment value fits on a single line."""
24
24
  # For simple values, they're always single line
25
25
  if isinstance(assign_node.value, (ast.Constant, ast.Name, ast.Attribute)):
26
26
  return True
27
-
27
+
28
28
  # For complex expressions, check if they span multiple lines
29
29
  # We'll consider it multi-line if the end line is different from start line
30
- if hasattr(assign_node, 'lineno') and hasattr(assign_node.value, 'end_lineno'):
30
+ if hasattr(assign_node, "lineno") and assign_node.value is not None and hasattr(assign_node.value, "end_lineno"):
31
31
  return assign_node.lineno == assign_node.value.end_lineno
32
-
32
+
33
33
  return True
34
34
 
35
35
 
36
- def collect_variable_usage_and_stores_with_nodes(function_node: ast.AST) -> tuple[
37
- dict[str, list[ast.Name]],
38
- set[str],
39
- dict[str, ast.Assign]
40
- ]:
36
+ def collect_variable_usage_and_stores_with_nodes(
37
+ function_node: ast.AST,
38
+ ) -> tuple[dict[str, list[ast.Name]], set[str], dict[str, ast.Assign | ast.AnnAssign]]:
41
39
  variable_usage: typing.Final[defaultdict[str, list[ast.Name]]] = defaultdict(list)
42
40
  assigned_variable_names: typing.Final[set[str]] = set()
43
- variable_assignments: typing.Final[dict[str, ast.Assign]] = {}
41
+ variable_assignments: typing.Final[dict[str, ast.Assign | ast.AnnAssign]] = {}
44
42
 
45
43
  @typing.final
46
44
  class UsageCollector(ast.NodeVisitor):
@@ -54,43 +52,42 @@ def collect_variable_usage_and_stores_with_nodes(function_node: ast.AST) -> tupl
54
52
  self.generic_visit(assign_node)
55
53
  return
56
54
 
57
- for target in assign_node.targets:
58
- names = list(extract_names(target))
59
- assigned_variable_names.update(names)
55
+ for one_target in assign_node.targets:
56
+ one_names = list(extract_names(one_target))
57
+ assigned_variable_names.update(one_names)
60
58
  # Store the assignment node for each variable
61
- for name in names:
62
- variable_assignments[name] = assign_node
59
+ for one_name in one_names:
60
+ variable_assignments[one_name] = assign_node
63
61
  self.generic_visit(assign_node)
64
62
 
65
63
  def visit_AugAssign(self, aug_assign_node: ast.AugAssign) -> None:
66
- names = list(extract_names(aug_assign_node.target))
67
- assigned_variable_names.update(names)
64
+ assigned_variable_names.update(list(extract_names(aug_assign_node.target)))
68
65
  self.generic_visit(aug_assign_node)
69
66
 
70
67
  def visit_AnnAssign(self, ann_assign_node: ast.AnnAssign) -> None:
71
- names = list(extract_names(ann_assign_node.target))
72
- assigned_variable_names.update(names)
68
+ extracted_names: typing.Final = list(extract_names(ann_assign_node.target)) # noqa: COP011
69
+ assigned_variable_names.update(extracted_names)
73
70
  # Store the assignment node for each variable
74
- for name in names:
75
- variable_assignments[name] = ann_assign_node
71
+ for one_name in extracted_names:
72
+ variable_assignments[one_name] = ann_assign_node
76
73
  self.generic_visit(ann_assign_node)
77
74
 
78
75
  UsageCollector().visit(function_node)
79
76
  return dict(variable_usage), assigned_variable_names, variable_assignments
80
77
 
81
78
 
82
- def is_used_in_next_line(assign_node: ast.Assign, usage_nodes: list[ast.Name]) -> bool:
79
+ def is_used_in_next_line(assign_node: ast.Assign | ast.AnnAssign, usage_nodes: list[ast.Name]) -> bool:
83
80
  """Check if variable is used in the immediate next line."""
84
81
  if not usage_nodes:
85
82
  return False
86
-
83
+
87
84
  # Find the load usage (actual use of the variable)
88
- load_usages = [node for node in usage_nodes if isinstance(node.ctx, ast.Load)]
85
+ load_usages: typing.Final = [one_node for one_node in usage_nodes if isinstance(one_node.ctx, ast.Load)]
89
86
  if not load_usages:
90
87
  return False
91
-
92
- first_load = load_usages[0]
93
-
88
+
89
+ first_load: typing.Final = load_usages[0]
90
+
94
91
  # Check if the usage is on the line immediately following the assignment
95
92
  return first_load.lineno == assign_node.lineno + 1
96
93
 
@@ -110,16 +107,20 @@ class TempVarCheck(ast.NodeVisitor):
110
107
 
111
108
  def _check_temporary_variables(self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
112
109
  usage_and_stores: typing.Final = collect_variable_usage_and_stores_with_nodes(ast_node)
113
- variable_usages = usage_and_stores[0]
114
- assigned_variable_names = usage_and_stores[1]
115
- variable_assignments = usage_and_stores[2]
110
+ variable_usages: typing.Final = usage_and_stores[0]
111
+ assigned_variable_names: typing.Final = usage_and_stores[1]
112
+ variable_assignments: typing.Final = usage_and_stores[2]
116
113
 
117
114
  for variable_name, usages in variable_usages.items():
118
115
  if variable_name.startswith("_") or variable_name in {"self", "cls"}:
119
116
  continue
120
117
 
121
- store_count = len([usage_element for usage_element in usages if isinstance(usage_element.ctx, ast.Store)])
122
- load_count = len([usage_element for usage_element in usages if isinstance(usage_element.ctx, ast.Load)])
118
+ store_count = len(
119
+ [one_usage_element for one_usage_element in usages if isinstance(one_usage_element.ctx, ast.Store)]
120
+ )
121
+ load_count = len(
122
+ [one_usage_element for one_usage_element in usages if isinstance(one_usage_element.ctx, ast.Load)]
123
+ )
123
124
 
124
125
  # Check if variable is assigned once and used once
125
126
  if store_count == 1 and load_count == 1 and variable_name in assigned_variable_names:
@@ -127,16 +128,16 @@ class TempVarCheck(ast.NodeVisitor):
127
128
  assign_node = variable_assignments.get(variable_name)
128
129
  if not assign_node:
129
130
  continue
130
-
131
+
131
132
  # Check if it's a single-line assignment
132
133
  if not is_single_line_assignment(assign_node):
133
134
  continue
134
-
135
+
135
136
  # Check if it's used in the next line
136
137
  if is_used_in_next_line(assign_node, usages):
137
138
  # Only flag the variable that is assigned and then immediately used
138
139
  # Find the store usage (assignment)
139
- store_usages = [node for node in usages if isinstance(node.ctx, ast.Store)]
140
+ store_usages = [one_node for one_node in usages if isinstance(one_node.ctx, ast.Store)]
140
141
  if store_usages:
141
142
  first_store = store_usages[0]
142
143
  self.violations.append(
@@ -109,6 +109,8 @@ VERB_PREFIXES: typing.Final = {
109
109
  "submit",
110
110
  "clear",
111
111
  "undo",
112
+ "cache",
113
+ "fill",
112
114
  }
113
115
 
114
116
  SCALAR_ANNOTATIONS: typing.Final = {"int", "str", "float", "bool", "bytes", "complex"}
@@ -31,23 +31,23 @@ class CommunityOfPythonFlake8Plugin:
31
31
  self.ast_syntax_tree: typing.Final[ast.AST] = tree
32
32
 
33
33
  def run(self) -> Iterable[tuple[int, int, str, type[object]]]: # noqa: COP007
34
- for check_instance in self._collect_checks():
35
- for violation in check_instance.violations:
34
+ for one_check_instance in self._collect_checks():
35
+ for one_violation in one_check_instance.violations:
36
36
  yield (
37
- violation.line_number,
38
- violation.column_number,
39
- f"{violation.violation_code.code} {violation.violation_code.description}",
37
+ one_violation.line_number,
38
+ one_violation.column_number,
39
+ f"{one_violation.violation_code.code} {one_violation.violation_code.description}",
40
40
  type(self),
41
41
  )
42
42
 
43
43
  def _collect_checks(self) -> list[PluginCheckProtocol]:
44
44
  checks_collection: typing.Final = []
45
- for _, module_name, _ in pkgutil.iter_modules(checks_module.__path__):
46
- imported_module = importlib.import_module(f"{checks_module.__name__}.{module_name}")
45
+ for _, one_module_name, _ in pkgutil.iter_modules(checks_module.__path__):
46
+ imported_module = importlib.import_module(f"{checks_module.__name__}.{one_module_name}")
47
47
 
48
- for attribute_name in dir(imported_module):
49
- attribute = getattr(imported_module, attribute_name)
50
- if isinstance(attribute, type) and attribute_name.endswith("Check") and hasattr(attribute, "visit"):
48
+ for one_attribute_name in dir(imported_module):
49
+ attribute = getattr(imported_module, one_attribute_name)
50
+ if isinstance(attribute, type) and one_attribute_name.endswith("Check") and hasattr(attribute, "visit"):
51
51
  check_instance = attribute(self.ast_syntax_tree)
52
52
  check_instance.visit(self.ast_syntax_tree)
53
53
  checks_collection.append(check_instance)
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+ import ast
3
+
4
+
5
+ def find_parent_class_definition(syntax_tree: ast.AST, target_node: ast.AST) -> ast.ClassDef | None:
6
+ for one_potential_parent in ast.walk(syntax_tree):
7
+ if isinstance(one_potential_parent, ast.ClassDef):
8
+ for one_child_node in ast.walk(one_potential_parent):
9
+ if one_child_node is target_node:
10
+ return one_potential_parent
11
+ return None
12
+
13
+
14
+ def find_parent_function_definition(
15
+ syntax_tree: ast.AST, target_node: ast.AST
16
+ ) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
17
+ for one_potential_parent in ast.walk(syntax_tree):
18
+ if isinstance(one_potential_parent, (ast.FunctionDef, ast.AsyncFunctionDef)):
19
+ for one_child_node in ast.walk(one_potential_parent):
20
+ if one_child_node is target_node:
21
+ return one_potential_parent
22
+ return None
23
+
24
+
25
+ def check_inherits_from_bases(class_definition: ast.ClassDef, base_classes: set[str]) -> bool:
26
+ for one_base_class in class_definition.bases:
27
+ if isinstance(one_base_class, ast.Name) and one_base_class.id in base_classes:
28
+ return True
29
+ if isinstance(one_base_class, ast.Attribute) and one_base_class.attr in base_classes:
30
+ return True
31
+ # Handle generic types like ModelFactory[SomeType]
32
+ if isinstance(one_base_class, ast.Subscript):
33
+ if isinstance(one_base_class.value, ast.Name) and one_base_class.value.id in base_classes:
34
+ return True
35
+ if isinstance(one_base_class.value, ast.Attribute) and one_base_class.value.attr in base_classes:
36
+ return True
37
+ return False
38
+
39
+
40
+ def find_parent_node(syntax_tree: ast.AST, target_node: ast.AST, node_types: tuple[type, ...]) -> ast.AST | None:
41
+ for one_potential_parent in ast.walk(syntax_tree):
42
+ if isinstance(one_potential_parent, node_types):
43
+ for one_child_node in ast.walk(one_potential_parent):
44
+ if one_child_node is target_node:
45
+ return one_potential_parent
46
+ return None
@@ -35,7 +35,9 @@ class ViolationCodes:
35
35
  ASYNC_GET_PREFIX = ViolationCodeItem(code="COP010", description="Avoid get_ prefix in async function names")
36
36
 
37
37
  # Variable usage violations
38
- TEMP_VAR = ViolationCodeItem(code="COP011", description="Inline variables that are used only once")
38
+ TEMP_VAR = ViolationCodeItem(
39
+ code="COP011", description="Inline those temporary variables that are used only once and close to assignment"
40
+ )
39
41
 
40
42
  # Class related violations
41
43
  FINAL_CLASS = ViolationCodeItem(code="COP012", description="Classes must be marked final with @typing.final")
@@ -1,35 +0,0 @@
1
- from __future__ import annotations
2
- import ast
3
-
4
-
5
- def find_parent_class_definition(syntax_tree: ast.AST, target_node: ast.AST) -> ast.ClassDef | None:
6
- for potential_parent in ast.walk(syntax_tree):
7
- if isinstance(potential_parent, ast.ClassDef):
8
- for child_node in ast.walk(potential_parent):
9
- if child_node is target_node:
10
- return potential_parent
11
- return None
12
-
13
-
14
- def check_inherits_from_bases(class_definition: ast.ClassDef, base_classes: set[str]) -> bool:
15
- for base_class in class_definition.bases:
16
- if isinstance(base_class, ast.Name) and base_class.id in base_classes:
17
- return True
18
- if isinstance(base_class, ast.Attribute) and base_class.attr in base_classes:
19
- return True
20
- # Handle generic types like ModelFactory[SomeType]
21
- if isinstance(base_class, ast.Subscript):
22
- if isinstance(base_class.value, ast.Name) and base_class.value.id in base_classes:
23
- return True
24
- if isinstance(base_class.value, ast.Attribute) and base_class.value.attr in base_classes:
25
- return True
26
- return False
27
-
28
-
29
- def find_parent_node(syntax_tree: ast.AST, target_node: ast.AST, node_types: tuple[type, ...]) -> ast.AST | None:
30
- for potential_parent in ast.walk(syntax_tree):
31
- if isinstance(potential_parent, node_types):
32
- for child_node in ast.walk(potential_parent):
33
- if child_node is target_node:
34
- return potential_parent
35
- return None