community-of-python-flake8-plugin 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ from community_of_python_flake8_plugin.plugin import CommunityOfPythonFlake8Plugin
2
+
3
+
4
+ __all__ = ["CommunityOfPythonFlake8Plugin"]
@@ -0,0 +1,35 @@
1
+ # Community of Python Flake8 Plugin - Check Files
2
+
3
+ This directory contains individual check files, each implementing a specific rule (COP error code).
4
+
5
+ ## Current Checks
6
+
7
+ | File | Error Code | Description |
8
+ |------|------------|-------------|
9
+ | `cop001.py` | COP001 | Use module import when importing more than two names |
10
+ | `cop002.py` | COP002 | Import standard library modules as whole modules |
11
+ | `cop003.py` | COP003 | Avoid explicit scalar type annotations |
12
+ | `cop004.py` | COP004 | Name must be at least 8 characters |
13
+ | `cop005.py` | COP005 | Function name must be a verb |
14
+ | `cop006.py` | COP006 | Avoid get_ prefix in async function names |
15
+ | `cop007.py` | COP007 | Avoid temporary variables used only once |
16
+ | `cop008.py` | COP008 | Classes should be marked typing.final |
17
+ | `cop009.py` | COP009 | Wrap module dictionaries with types.MappingProxyType |
18
+ | `cop010.py` | COP010 | Use dataclasses with kw_only=True, slots=True, frozen=True |
19
+
20
+ ## File Structure
21
+
22
+ Each check file follows this pattern:
23
+ - Contains a single class that inherits from `ast.NodeVisitor`
24
+ - Implements visit methods for the AST nodes it needs to check
25
+ - Stores violations in a `self.violations` list
26
+ - Each file is responsible for exactly one error code
27
+
28
+ ## Adding New Checks
29
+
30
+ To add a new check:
31
+ 1. Create a new file `copXXX.py` where XXX is the next available error code
32
+ 2. Implement a check class following the pattern of existing checks
33
+ 3. Add the import and instantiation in `__init__.py`
34
+ 4. Add tests in the test file
35
+ 5. Update this README
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.checks.cop001 import COP001Check
6
+ from community_of_python_flake8_plugin.checks.cop002 import COP002Check
7
+ from community_of_python_flake8_plugin.checks.cop003 import COP003Check
8
+ from community_of_python_flake8_plugin.checks.cop004 import COP004Check
9
+ from community_of_python_flake8_plugin.checks.cop005 import COP005Check
10
+ from community_of_python_flake8_plugin.checks.cop006 import COP006Check
11
+ from community_of_python_flake8_plugin.checks.cop007 import COP007Check
12
+ from community_of_python_flake8_plugin.checks.cop008 import COP008Check
13
+ from community_of_python_flake8_plugin.checks.cop009 import COP009Check
14
+ from community_of_python_flake8_plugin.checks.cop010 import COP010Check
15
+ from community_of_python_flake8_plugin.utils import check_module_has_all_declaration
16
+
17
+
18
+ if typing.TYPE_CHECKING:
19
+ from community_of_python_flake8_plugin.violations import Violation
20
+
21
+
22
+ def execute_all_validations(syntax_tree: ast.AST) -> list[Violation]:
23
+ contains_all_declaration: typing.Final = (
24
+ check_module_has_all_declaration(syntax_tree) if isinstance(syntax_tree, ast.Module) else False
25
+ )
26
+ collected_violations: typing.Final[list[Violation]] = []
27
+
28
+ # COP001: Use module import when importing more than two names
29
+ cop001_validator: typing.Final = COP001Check(contains_all_declaration)
30
+ cop001_validator.visit(syntax_tree)
31
+ collected_violations.extend(cop001_validator.violations)
32
+
33
+ # COP002: Import standard library modules as whole modules
34
+ cop002_validator: typing.Final = COP002Check()
35
+ cop002_validator.visit(syntax_tree)
36
+ collected_violations.extend(cop002_validator.violations)
37
+
38
+ # COP003: Avoid explicit scalar type annotations
39
+ cop003_validator: typing.Final = COP003Check(syntax_tree)
40
+ cop003_validator.visit(syntax_tree)
41
+ collected_violations.extend(cop003_validator.violations)
42
+
43
+ # COP004: Name must be at least 8 characters
44
+ cop004_validator: typing.Final = COP004Check(syntax_tree)
45
+ cop004_validator.visit(syntax_tree)
46
+ collected_violations.extend(cop004_validator.violations)
47
+
48
+ # COP005: Function identifier must be a verb
49
+ cop005_validator: typing.Final = COP005Check(syntax_tree)
50
+ cop005_validator.visit(syntax_tree)
51
+ collected_violations.extend(cop005_validator.violations)
52
+
53
+ # COP006: Avoid get_ prefix in async function names
54
+ cop006_validator: typing.Final = COP006Check()
55
+ cop006_validator.visit(syntax_tree)
56
+ collected_violations.extend(cop006_validator.violations)
57
+
58
+ # COP007: Avoid temporary variables used only once
59
+ cop007_validator: typing.Final = COP007Check()
60
+ cop007_validator.visit(syntax_tree)
61
+ collected_violations.extend(cop007_validator.violations)
62
+
63
+ # COP008: Classes should be marked typing.final
64
+ cop008_validator: typing.Final = COP008Check()
65
+ cop008_validator.visit(syntax_tree)
66
+ collected_violations.extend(cop008_validator.violations)
67
+
68
+ # COP009: Wrap module dictionaries with types.MappingProxyType
69
+ cop009_validator: typing.Final = COP009Check()
70
+ cop009_validator.visit(syntax_tree)
71
+ collected_violations.extend(cop009_validator.violations)
72
+
73
+ # COP010: Use dataclasses with kw_only=True, slots=True, frozen=True
74
+ cop010_validator: typing.Final = COP010Check()
75
+ cop010_validator.visit(syntax_tree)
76
+ collected_violations.extend(cop010_validator.violations)
77
+
78
+ return collected_violations
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+ from importlib import util as importlib_util
5
+
6
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
7
+ from community_of_python_flake8_plugin.violations import Violation
8
+
9
+
10
+ def check_module_path_exists(module_name: str) -> bool:
11
+ try:
12
+ return importlib_util.find_spec(module_name) is not None
13
+ except (ModuleNotFoundError, ValueError):
14
+ return False
15
+
16
+
17
+ MAX_IMPORT_NAMES: typing.Final = 2
18
+
19
+
20
+ @typing.final
21
+ class COP001Check(ast.NodeVisitor):
22
+ def __init__(self, contains_all_declaration: bool) -> None:
23
+ self.contains_all_declaration = contains_all_declaration
24
+ self.violations: list[Violation] = []
25
+
26
+ def visit_ImportFrom(self, ast_node: ast.ImportFrom) -> None:
27
+ if ast_node.module and ast_node.level == 0:
28
+ self.validate_import_size(ast_node)
29
+ self.generic_visit(ast_node)
30
+
31
+ def validate_import_size(self, ast_node: ast.ImportFrom) -> None:
32
+ if len(ast_node.names) <= MAX_IMPORT_NAMES:
33
+ return
34
+ if self.contains_all_declaration:
35
+ return
36
+ module_name: typing.Final = ast_node.module
37
+ if module_name is not None and module_name.endswith(".settings"):
38
+ return
39
+
40
+ contains_module_import: typing.Final = any(
41
+ isinstance(identifier, ast.alias)
42
+ and module_name is not None
43
+ and check_module_path_exists(f"{module_name}.{identifier.name}")
44
+ for identifier in ast_node.names
45
+ )
46
+ if not contains_module_import:
47
+ self.violations.append(
48
+ Violation(
49
+ line_number=ast_node.lineno,
50
+ column_number=ast_node.col_offset,
51
+ violation_code=ViolationCode.MODULE_IMPORT,
52
+ )
53
+ )
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import sys
4
+ import typing
5
+ from importlib import util as importlib_util
6
+
7
+ from community_of_python_flake8_plugin.constants import ALLOWED_STDLIB_FROM_IMPORTS
8
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
9
+ from community_of_python_flake8_plugin.violations import Violation
10
+
11
+
12
+ def check_is_stdlib_module(module_name: str) -> bool:
13
+ return module_name in sys.stdlib_module_names
14
+
15
+
16
+ def check_is_stdlib_package(module_name: str) -> bool:
17
+ if not check_is_stdlib_module(module_name):
18
+ return False
19
+ module_spec: typing.Final = importlib_util.find_spec(module_name)
20
+ return module_spec is not None and module_spec.submodule_search_locations is not None
21
+
22
+
23
+ @typing.final
24
+ class COP002Check(ast.NodeVisitor):
25
+ def __init__(self) -> None:
26
+ self.violations: list[Violation] = []
27
+
28
+ def visit_ImportFrom(self, ast_node: ast.ImportFrom) -> None:
29
+ if ast_node.module and ast_node.level == 0 and ast_node.module not in ALLOWED_STDLIB_FROM_IMPORTS:
30
+ self.validate_stdlib_import(ast_node)
31
+ self.generic_visit(ast_node)
32
+
33
+ def validate_stdlib_import(self, ast_node: ast.ImportFrom) -> None:
34
+ module_name: typing.Final = ast_node.module
35
+ if module_name is None:
36
+ return
37
+ if module_name == "__future__":
38
+ return
39
+ if (check_is_stdlib_module(module_name) and not check_is_stdlib_package(module_name)) or (
40
+ "." in module_name and check_is_stdlib_package(module_name.split(".")[0])
41
+ ):
42
+ self.violations.append(
43
+ Violation(
44
+ line_number=ast_node.lineno,
45
+ column_number=ast_node.col_offset,
46
+ violation_code=ViolationCode.STDLIB_IMPORT,
47
+ )
48
+ )
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.constants import SCALAR_ANNOTATIONS
6
+ from community_of_python_flake8_plugin.utils import find_parent_class_definition
7
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
8
+ from community_of_python_flake8_plugin.violations import Violation
9
+
10
+
11
+ def check_is_literal_value(value: ast.AST) -> bool:
12
+ if isinstance(value, ast.Constant):
13
+ return True
14
+ return bool(isinstance(value, (ast.List, ast.Tuple, ast.Set, ast.Dict)))
15
+
16
+
17
+ def check_is_final_annotation(annotation: ast.AST) -> bool:
18
+ if isinstance(annotation, ast.Name):
19
+ return annotation.id == "Final"
20
+ if isinstance(annotation, ast.Attribute):
21
+ return annotation.attr == "Final"
22
+ if isinstance(annotation, ast.Subscript):
23
+ return check_is_final_annotation(annotation.value)
24
+ return False
25
+
26
+
27
+ def check_is_scalar_annotation(annotation: ast.AST) -> bool:
28
+ if isinstance(annotation, ast.Name):
29
+ return annotation.id in SCALAR_ANNOTATIONS
30
+ if isinstance(annotation, ast.Attribute):
31
+ return annotation.attr in SCALAR_ANNOTATIONS
32
+ if isinstance(annotation, ast.Subscript):
33
+ if check_is_final_annotation(annotation.value):
34
+ return check_is_scalar_annotation(annotation.slice)
35
+ return check_is_scalar_annotation(annotation.value)
36
+ return False
37
+
38
+
39
+ def find_parent_function(syntax_tree: ast.AST, ast_node: ast.AST) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
40
+ for potential_parent in ast.walk(syntax_tree):
41
+ if isinstance(potential_parent, (ast.FunctionDef, ast.AsyncFunctionDef)):
42
+ for child in ast.walk(potential_parent):
43
+ if child is ast_node:
44
+ return potential_parent
45
+ return None
46
+
47
+
48
+ @typing.final
49
+ class COP003Check(ast.NodeVisitor):
50
+ def __init__(self, syntax_tree: ast.AST) -> None:
51
+ self.syntax_tree = syntax_tree
52
+ self.violations: list[Violation] = []
53
+
54
+ def visit_AnnAssign(self, ast_node: ast.AnnAssign) -> None:
55
+ if isinstance(ast_node.target, ast.Name):
56
+ parent_class: typing.Final = find_parent_class_definition(self.syntax_tree, ast_node)
57
+ parent_function: typing.Final = find_parent_function(self.syntax_tree, ast_node)
58
+ in_class_body: typing.Final = parent_class is not None and parent_function is None
59
+
60
+ if not in_class_body:
61
+ self.validate_scalar_annotation(ast_node)
62
+ self.generic_visit(ast_node)
63
+
64
+ def validate_scalar_annotation(self, ast_node: ast.AnnAssign) -> None:
65
+ if ast_node.value is None:
66
+ return
67
+ if not check_is_literal_value(ast_node.value):
68
+ return
69
+ if check_is_scalar_annotation(ast_node.annotation):
70
+ self.violations.append(
71
+ Violation(
72
+ line_number=ast_node.lineno,
73
+ column_number=ast_node.col_offset,
74
+ violation_code=ViolationCode.SCALAR_ANNOTATION,
75
+ )
76
+ )
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
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 find_parent_class_definition
7
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
8
+ from community_of_python_flake8_plugin.violations import Violation
9
+
10
+
11
+ def check_is_ignored_name(identifier: str) -> bool:
12
+ if identifier == "_":
13
+ return True
14
+ if identifier.isupper():
15
+ return True
16
+ if identifier in {"value", "values", "pattern"}:
17
+ return True
18
+ if identifier.startswith("__") and identifier.endswith("__"):
19
+ return True
20
+ return bool(identifier.startswith("_"))
21
+
22
+
23
+ def check_is_whitelisted_annotation(annotation: ast.expr | None) -> bool:
24
+ if annotation is None:
25
+ return False
26
+ if isinstance(annotation, ast.Name):
27
+ return annotation.id in {"fixture", "Faker"}
28
+ if isinstance(annotation, ast.Attribute) and isinstance(annotation.value, ast.Name):
29
+ return annotation.value.id in {"pytest", "faker"}
30
+ return False
31
+
32
+
33
+ def check_is_pytest_fixture(ast_node: ast.AST) -> bool:
34
+ if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
35
+ return False
36
+ return any(check_is_fixture_decorator(decorator) for decorator in ast_node.decorator_list)
37
+
38
+
39
+ def check_is_fixture_decorator(decorator: ast.expr) -> bool:
40
+ if isinstance(decorator, ast.Name):
41
+ return decorator.id == "fixture"
42
+ if isinstance(decorator, ast.Attribute):
43
+ return decorator.attr == "fixture" and isinstance(decorator.value, ast.Name) and decorator.value.id == "pytest"
44
+ return False
45
+
46
+
47
+ def check_inherits_from_whitelisted_class(class_node: ast.ClassDef) -> bool:
48
+ for base_class in class_node.bases:
49
+ if isinstance(base_class, ast.Name) and base_class.id in FINAL_CLASS_EXCLUDED_BASES:
50
+ return True
51
+ if isinstance(base_class, ast.Attribute) and base_class.attr in FINAL_CLASS_EXCLUDED_BASES:
52
+ return True
53
+ return False
54
+
55
+
56
+ @typing.final
57
+ class COP004Check(ast.NodeVisitor):
58
+ def __init__(self, syntax_tree: ast.AST) -> None:
59
+ self.syntax_tree = syntax_tree
60
+ self.violations: list[Violation] = []
61
+
62
+ def visit_AnnAssign(self, ast_node: ast.AnnAssign) -> None:
63
+ if isinstance(ast_node.target, ast.Name):
64
+ parent_class: typing.Final = find_parent_class_definition(self.syntax_tree, ast_node)
65
+ self.validate_name_length(ast_node.target.id, ast_node, parent_class)
66
+ self.generic_visit(ast_node)
67
+
68
+ def visit_Assign(self, ast_node: ast.Assign) -> None:
69
+ for target in ast_node.targets:
70
+ if isinstance(target, ast.Name):
71
+ parent_class = find_parent_class_definition(self.syntax_tree, ast_node)
72
+ self.validate_name_length(target.id, ast_node, parent_class)
73
+ self.generic_visit(ast_node)
74
+
75
+ def visit_FunctionDef(self, ast_node: ast.FunctionDef) -> None:
76
+ parent_class: typing.Final = find_parent_class_definition(self.syntax_tree, ast_node)
77
+ self.validate_function_name(ast_node, parent_class)
78
+ self.validate_function_args(ast_node)
79
+ self.generic_visit(ast_node)
80
+
81
+ def visit_AsyncFunctionDef(self, ast_node: ast.AsyncFunctionDef) -> None:
82
+ parent_class: typing.Final = find_parent_class_definition(self.syntax_tree, ast_node)
83
+ self.validate_function_name(ast_node, parent_class)
84
+ self.validate_function_args(ast_node)
85
+ self.generic_visit(ast_node)
86
+
87
+ def visit_ClassDef(self, ast_node: ast.ClassDef) -> None:
88
+ if not ast_node.name.startswith("Test"):
89
+ self.validate_class_name_length(ast_node)
90
+ self.generic_visit(ast_node)
91
+
92
+ def validate_name_length(self, identifier: str, ast_node: ast.stmt, parent_class: ast.ClassDef | None) -> None:
93
+ if check_is_ignored_name(identifier):
94
+ return
95
+ # Only apply parent class exemption for assignments within classes
96
+ if (
97
+ parent_class
98
+ and isinstance(ast_node, (ast.AnnAssign, ast.Assign))
99
+ and check_inherits_from_whitelisted_class(parent_class)
100
+ ):
101
+ return
102
+ if len(identifier) < MIN_NAME_LENGTH:
103
+ self.violations.append(
104
+ Violation(
105
+ line_number=ast_node.lineno,
106
+ column_number=ast_node.col_offset,
107
+ violation_code=ViolationCode.NAME_LENGTH,
108
+ )
109
+ )
110
+
111
+ def validate_function_name(
112
+ self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef, parent_class: ast.ClassDef | None
113
+ ) -> None:
114
+ if ast_node.name == "main":
115
+ return
116
+ if check_is_ignored_name(ast_node.name):
117
+ return
118
+ if parent_class and check_inherits_from_whitelisted_class(parent_class):
119
+ return
120
+ if check_is_pytest_fixture(ast_node):
121
+ return
122
+ if len(ast_node.name) < MIN_NAME_LENGTH:
123
+ self.violations.append(
124
+ Violation(
125
+ line_number=ast_node.lineno,
126
+ column_number=ast_node.col_offset,
127
+ violation_code=ViolationCode.NAME_LENGTH,
128
+ )
129
+ )
130
+
131
+ def validate_function_args(self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
132
+ arguments: typing.Final = ast_node.args
133
+ for argument in arguments.posonlyargs + arguments.args + arguments.kwonlyargs:
134
+ self.validate_argument_name_length(argument)
135
+ if arguments.vararg is not None:
136
+ self.validate_argument_name_length(arguments.vararg)
137
+ if arguments.kwarg is not None:
138
+ self.validate_argument_name_length(arguments.kwarg)
139
+
140
+ def validate_argument_name_length(self, argument: ast.arg) -> None:
141
+ if argument.arg in {"self", "cls"}:
142
+ return
143
+ if check_is_ignored_name(argument.arg):
144
+ return
145
+ if check_is_whitelisted_annotation(argument.annotation):
146
+ return
147
+ if len(argument.arg) < MIN_NAME_LENGTH:
148
+ self.violations.append(
149
+ Violation(
150
+ line_number=argument.lineno,
151
+ column_number=argument.col_offset,
152
+ violation_code=ViolationCode.NAME_LENGTH,
153
+ )
154
+ )
155
+
156
+ def validate_class_name_length(self, ast_node: ast.ClassDef) -> None:
157
+ if check_is_ignored_name(ast_node.name):
158
+ return
159
+ if len(ast_node.name) < MIN_NAME_LENGTH:
160
+ self.violations.append(
161
+ Violation(
162
+ line_number=ast_node.lineno,
163
+ column_number=ast_node.col_offset,
164
+ violation_code=ViolationCode.NAME_LENGTH,
165
+ )
166
+ )
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.constants import FINAL_CLASS_EXCLUDED_BASES, VERB_PREFIXES
6
+ from community_of_python_flake8_plugin.utils import find_parent_class_definition
7
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
8
+ from community_of_python_flake8_plugin.violations import Violation
9
+
10
+
11
+ def check_is_ignored_name(identifier: str) -> bool:
12
+ if identifier == "_":
13
+ return True
14
+ if identifier.isupper():
15
+ return True
16
+ if identifier in {"value", "values", "pattern"}:
17
+ return True
18
+ if identifier.startswith("__") and identifier.endswith("__"):
19
+ return True
20
+ return bool(identifier.startswith("_"))
21
+
22
+
23
+ def check_is_verb_name(identifier: str) -> bool:
24
+ return any(identifier == verb or identifier.startswith(f"{verb}_") for verb in VERB_PREFIXES)
25
+
26
+
27
+ def check_is_property(ast_node: ast.AST) -> bool:
28
+ if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
29
+ return False
30
+ return any(check_is_property_decorator(decorator) for decorator in ast_node.decorator_list)
31
+
32
+
33
+ def check_is_property_decorator(decorator: ast.expr) -> bool:
34
+ if isinstance(decorator, ast.Name):
35
+ return decorator.id == "property"
36
+ if isinstance(decorator, ast.Attribute) and decorator.attr in {"property", "setter", "cached_property"}:
37
+ if isinstance(decorator.value, ast.Name) and decorator.value.id == "functools":
38
+ return decorator.attr == "cached_property"
39
+ return decorator.attr in {"property", "setter"}
40
+ return False
41
+
42
+
43
+ def check_is_pytest_fixture(ast_node: ast.AST) -> bool:
44
+ if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
45
+ return False
46
+ return any(check_is_fixture_decorator(decorator) for decorator in ast_node.decorator_list)
47
+
48
+
49
+ def check_is_fixture_decorator(decorator: ast.expr) -> bool:
50
+ if isinstance(decorator, ast.Name):
51
+ return decorator.id == "fixture"
52
+ if isinstance(decorator, ast.Attribute):
53
+ return decorator.attr == "fixture" and isinstance(decorator.value, ast.Name) and decorator.value.id == "pytest"
54
+ return False
55
+
56
+
57
+ def retrieve_parent_class(syntax_tree: ast.AST, ast_node: ast.AST) -> ast.ClassDef | None:
58
+ return find_parent_class_definition(syntax_tree, ast_node)
59
+
60
+
61
+ @typing.final
62
+ class COP005Check(ast.NodeVisitor):
63
+ def __init__(self, syntax_tree: ast.AST) -> None:
64
+ self.syntax_tree = syntax_tree
65
+ self.violations: list[Violation] = []
66
+
67
+ def visit_FunctionDef(self, ast_node: ast.FunctionDef) -> None:
68
+ parent_class: typing.Final = retrieve_parent_class(self.syntax_tree, ast_node)
69
+ self.validate_function_name(ast_node, parent_class)
70
+ self.generic_visit(ast_node)
71
+
72
+ def visit_AsyncFunctionDef(self, ast_node: ast.AsyncFunctionDef) -> None:
73
+ parent_class: typing.Final = retrieve_parent_class(self.syntax_tree, ast_node)
74
+ self.validate_function_name(ast_node, parent_class)
75
+ self.generic_visit(ast_node)
76
+
77
+ def validate_function_name(
78
+ self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef, parent_class: ast.ClassDef | None
79
+ ) -> None:
80
+ should_skip: typing.Final = (
81
+ ast_node.name == "main"
82
+ or (ast_node.name.startswith("__") and ast_node.name.endswith("__"))
83
+ or check_is_ignored_name(ast_node.name)
84
+ or (parent_class and self.check_inherits_from_whitelisted_class(parent_class))
85
+ or check_is_property(ast_node)
86
+ or check_is_pytest_fixture(ast_node)
87
+ or check_is_verb_name(ast_node.name)
88
+ )
89
+
90
+ if should_skip:
91
+ return
92
+
93
+ min_acronym_length: typing.Final = 3
94
+ if len(ast_node.name) < min_acronym_length: # Short names are likely acronyms or special cases
95
+ return
96
+
97
+ self.violations.append(
98
+ Violation(
99
+ line_number=ast_node.lineno,
100
+ column_number=ast_node.col_offset,
101
+ violation_code=ViolationCode.FUNCTION_VERB,
102
+ )
103
+ )
104
+
105
+ def check_inherits_from_whitelisted_class(self, ast_node: ast.ClassDef) -> bool:
106
+ for base_class in ast_node.bases:
107
+ if isinstance(base_class, ast.Name) and base_class.id in FINAL_CLASS_EXCLUDED_BASES:
108
+ return True
109
+ if isinstance(base_class, ast.Attribute) and base_class.attr in FINAL_CLASS_EXCLUDED_BASES:
110
+ return True
111
+ return False
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
6
+ from community_of_python_flake8_plugin.violations import Violation
7
+
8
+
9
+ def check_is_ignored_name(identifier: str) -> bool:
10
+ if identifier == "_":
11
+ return True
12
+ if identifier.isupper():
13
+ return True
14
+ if identifier in {"value", "values", "pattern"}:
15
+ return True
16
+ if identifier.startswith("__") and identifier.endswith("__"):
17
+ return True
18
+ return bool(identifier.startswith("_"))
19
+
20
+
21
+ def check_is_property(ast_node: ast.AST) -> bool:
22
+ if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
23
+ return False
24
+ return any(check_is_property_decorator(decorator) for decorator in ast_node.decorator_list)
25
+
26
+
27
+ def check_is_property_decorator(decorator: ast.expr) -> bool:
28
+ if isinstance(decorator, ast.Name):
29
+ return decorator.id == "property"
30
+ if isinstance(decorator, ast.Attribute) and decorator.attr in {"property", "setter", "cached_property"}:
31
+ if isinstance(decorator.value, ast.Name) and decorator.value.id == "functools":
32
+ return decorator.attr == "cached_property"
33
+ return decorator.attr in {"property", "setter"}
34
+ return False
35
+
36
+
37
+ def check_is_pytest_fixture(ast_node: ast.AST) -> bool:
38
+ if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
39
+ return False
40
+ return any(check_is_fixture_decorator(decorator) for decorator in ast_node.decorator_list)
41
+
42
+
43
+ def check_is_fixture_decorator(decorator: ast.expr) -> bool:
44
+ if isinstance(decorator, ast.Name):
45
+ return decorator.id == "fixture"
46
+ if isinstance(decorator, ast.Attribute):
47
+ return decorator.attr == "fixture" and isinstance(decorator.value, ast.Name) and decorator.value.id == "pytest"
48
+ return False
49
+
50
+
51
+ @typing.final
52
+ class COP006Check(ast.NodeVisitor):
53
+ def __init__(self) -> None:
54
+ self.violations: list[Violation] = []
55
+
56
+ def visit_AsyncFunctionDef(self, ast_node: ast.AsyncFunctionDef) -> None:
57
+ self._check_get_prefix(ast_node)
58
+ self.generic_visit(ast_node)
59
+
60
+ def _check_get_prefix(self, ast_node: ast.AsyncFunctionDef) -> None:
61
+ if check_is_property(ast_node) or check_is_pytest_fixture(ast_node):
62
+ return
63
+ if check_is_ignored_name(ast_node.name):
64
+ return
65
+ if ast_node.name.startswith("get_"):
66
+ self.violations.append(
67
+ Violation(
68
+ line_number=ast_node.lineno,
69
+ column_number=ast_node.col_offset,
70
+ violation_code=ViolationCode.ASYNC_GET_PREFIX,
71
+ )
72
+ )
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
6
+ from community_of_python_flake8_plugin.violations import Violation
7
+
8
+
9
+ def collect_assignments(ast_node: ast.AST) -> dict[str, list[ast.AST]]:
10
+ assigned: typing.Final[dict[str, list[ast.AST]]] = {}
11
+ for child in ast.walk(ast_node):
12
+ if isinstance(child, ast.Assign):
13
+ for target in child.targets:
14
+ if isinstance(target, ast.Name):
15
+ assigned.setdefault(target.id, []).append(child)
16
+ if isinstance(child, ast.AnnAssign) and isinstance(child.target, ast.Name):
17
+ assigned.setdefault(child.target.id, []).append(child)
18
+ return assigned
19
+
20
+
21
+ def collect_load_counts(ast_node: ast.AST) -> dict[str, int]:
22
+ load_counts_dict: typing.Final[dict[str, int]] = {}
23
+ for child in ast.walk(ast_node):
24
+ if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Load):
25
+ load_counts_dict[child.id] = load_counts_dict.get(child.id, 0) + 1
26
+ return load_counts_dict
27
+
28
+
29
+ @typing.final
30
+ class COP007Check(ast.NodeVisitor):
31
+ def __init__(self) -> None:
32
+ self.violations: list[Violation] = []
33
+
34
+ def visit_FunctionDef(self, ast_node: ast.FunctionDef) -> None:
35
+ self._check_temporary_variables(ast_node)
36
+ self.generic_visit(ast_node)
37
+
38
+ def visit_AsyncFunctionDef(self, ast_node: ast.AsyncFunctionDef) -> None:
39
+ self._check_temporary_variables(ast_node)
40
+ self.generic_visit(ast_node)
41
+
42
+ def _check_temporary_variables(self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
43
+ assigned: typing.Final = collect_assignments(ast_node)
44
+ load_counts: typing.Final = collect_load_counts(ast_node)
45
+ for statement in ast_node.body:
46
+ if isinstance(statement, ast.Return) and isinstance(statement.value, ast.Name):
47
+ identifier = statement.value.id
48
+ if len(assigned.get(identifier, [])) == 1 and load_counts.get(identifier, 0) == 1:
49
+ self.violations.append(
50
+ Violation(
51
+ line_number=statement.lineno,
52
+ column_number=statement.col_offset,
53
+ violation_code=ViolationCode.TEMPORARY_VARIABLE,
54
+ )
55
+ )
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.constants import FINAL_CLASS_EXCLUDED_BASES
6
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
7
+ from community_of_python_flake8_plugin.violations import Violation
8
+
9
+
10
+ def check_is_true_literal(ast_node: ast.AST | None) -> bool:
11
+ return isinstance(ast_node, ast.Constant) and ast_node.value is True
12
+
13
+
14
+ def contains_final_decorator(ast_node: ast.ClassDef) -> bool:
15
+ for decorator in ast_node.decorator_list:
16
+ target_name = decorator.func if isinstance(decorator, ast.Call) else decorator
17
+ if isinstance(target_name, ast.Name) and target_name.id == "final":
18
+ return True
19
+ if isinstance(target_name, ast.Attribute) and target_name.attr == "final":
20
+ return True
21
+ return False
22
+
23
+
24
+ def check_inherits_from_whitelisted_class(ast_node: ast.ClassDef) -> bool:
25
+ for base_class in ast_node.bases:
26
+ if isinstance(base_class, ast.Name) and base_class.id in FINAL_CLASS_EXCLUDED_BASES:
27
+ return True
28
+ if isinstance(base_class, ast.Attribute) and base_class.attr in FINAL_CLASS_EXCLUDED_BASES:
29
+ return True
30
+ return False
31
+
32
+
33
+ def retrieve_dataclass_decorator(ast_node: ast.ClassDef) -> ast.expr | None:
34
+ for decorator in ast_node.decorator_list:
35
+ target_name = decorator.func if isinstance(decorator, ast.Call) else decorator
36
+ if isinstance(target_name, ast.Name) and target_name.id == "dataclass":
37
+ return decorator
38
+ if isinstance(target_name, ast.Attribute) and target_name.attr == "dataclass":
39
+ return decorator
40
+ return None
41
+
42
+
43
+ def check_is_dataclass(ast_node: ast.ClassDef) -> bool:
44
+ return retrieve_dataclass_decorator(ast_node) is not None
45
+
46
+
47
+ @typing.final
48
+ class COP008Check(ast.NodeVisitor):
49
+ def __init__(self) -> None:
50
+ self.violations: list[Violation] = []
51
+
52
+ def visit_ClassDef(self, ast_node: ast.ClassDef) -> None:
53
+ self._check_final_decorator(ast_node)
54
+ self.generic_visit(ast_node)
55
+
56
+ def _check_final_decorator(self, ast_node: ast.ClassDef) -> None:
57
+ if (
58
+ not check_is_dataclass(ast_node)
59
+ and not contains_final_decorator(ast_node)
60
+ and not ast_node.name.startswith("Test")
61
+ and not check_inherits_from_whitelisted_class(ast_node)
62
+ ):
63
+ self.violations.append(
64
+ Violation(
65
+ line_number=ast_node.lineno,
66
+ column_number=ast_node.col_offset,
67
+ violation_code=ViolationCode.FINAL_CLASS,
68
+ )
69
+ )
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.constants import MAPPING_PROXY_TYPES
6
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
7
+ from community_of_python_flake8_plugin.violations import Violation
8
+
9
+
10
+ def check_is_mapping_literal(value: ast.AST | None) -> bool:
11
+ if isinstance(value, ast.Dict):
12
+ return True
13
+ if isinstance(value, ast.Call):
14
+ if check_is_typed_dict_call(value):
15
+ return False
16
+ return any(isinstance(argument, ast.Dict) for argument in value.args)
17
+ return False
18
+
19
+
20
+ def check_is_typed_dict_call(value: ast.Call) -> bool:
21
+ if isinstance(value.func, ast.Name) and value.func.id == "TypedDict":
22
+ return True
23
+ if isinstance(value.func, ast.Attribute) and value.func.attr == "TypedDict":
24
+ return isinstance(value.func.value, ast.Name) and value.func.value.id in {
25
+ "typing",
26
+ "typing_extensions",
27
+ }
28
+ return False
29
+
30
+
31
+ def check_is_mapping_proxy_call(value: ast.AST | None) -> bool:
32
+ if not isinstance(value, ast.Call):
33
+ return False
34
+ if isinstance(value.func, ast.Name):
35
+ return value.func.id in MAPPING_PROXY_TYPES
36
+ if isinstance(value.func, ast.Attribute):
37
+ return value.func.attr in MAPPING_PROXY_TYPES
38
+ return False
39
+
40
+
41
+ @typing.final
42
+ class COP009Check(ast.NodeVisitor):
43
+ def __init__(self) -> None:
44
+ self.violations: list[Violation] = []
45
+
46
+ def visit_Module(self, ast_node: ast.Module) -> None:
47
+ for statement in ast_node.body:
48
+ self._check_module_assignment(statement)
49
+ self.generic_visit(ast_node)
50
+
51
+ def _check_module_assignment(self, statement: ast.stmt) -> None:
52
+ value = None
53
+ if isinstance(statement, (ast.Assign, ast.AnnAssign)):
54
+ value = statement.value
55
+
56
+ if value and check_is_mapping_literal(value) and not check_is_mapping_proxy_call(value):
57
+ self.violations.append(
58
+ Violation(
59
+ line_number=statement.lineno,
60
+ column_number=statement.col_offset,
61
+ violation_code=ViolationCode.MAPPING_PROXY,
62
+ )
63
+ )
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import typing
4
+
5
+ from community_of_python_flake8_plugin.violation_codes import ViolationCodes as ViolationCode
6
+ from community_of_python_flake8_plugin.violations import Violation
7
+
8
+
9
+ def check_is_true_literal(ast_node: ast.AST | None) -> bool:
10
+ return isinstance(ast_node, ast.Constant) and ast_node.value is True
11
+
12
+
13
+ def retrieve_dataclass_decorator(ast_node: ast.ClassDef) -> ast.expr | None:
14
+ for decorator in ast_node.decorator_list:
15
+ target_name = decorator.func if isinstance(decorator, ast.Call) else decorator
16
+ if isinstance(target_name, ast.Name) and target_name.id == "dataclass":
17
+ return decorator
18
+ if isinstance(target_name, ast.Attribute) and target_name.attr == "dataclass":
19
+ return decorator
20
+ return None
21
+
22
+
23
+ def check_is_dataclass(ast_node: ast.ClassDef) -> bool:
24
+ return retrieve_dataclass_decorator(ast_node) is not None
25
+
26
+
27
+ def check_is_exception_class(_node: ast.ClassDef) -> bool:
28
+ return False
29
+
30
+
31
+ def check_is_inheriting(ast_node: ast.ClassDef) -> bool:
32
+ return len(ast_node.bases) > 0
33
+
34
+
35
+ def check_dataclass_has_keyword(decorator: ast.expr, identifier: str, value: bool | None = None) -> bool:
36
+ if not isinstance(decorator, ast.Call):
37
+ return False
38
+ for keyword in decorator.keywords:
39
+ if keyword.arg != identifier:
40
+ continue
41
+ if value is None:
42
+ return True
43
+ return isinstance(keyword.value, ast.Constant) and keyword.value.value is value
44
+ return False
45
+
46
+
47
+ def check_dataclass_has_required_args(decorator: ast.expr, *, require_slots: bool, require_frozen: bool) -> bool:
48
+ if not isinstance(decorator, ast.Call):
49
+ return False
50
+ keywords: typing.Final = {keyword.arg: keyword.value for keyword in decorator.keywords if keyword.arg}
51
+ if not check_is_true_literal(keywords.get("kw_only")):
52
+ return False
53
+ if require_slots and not check_is_true_literal(keywords.get("slots")):
54
+ return False
55
+ return not (require_frozen and not check_is_true_literal(keywords.get("frozen")))
56
+
57
+
58
+ @typing.final
59
+ class COP010Check(ast.NodeVisitor):
60
+ def __init__(self) -> None:
61
+ self.violations: list[Violation] = []
62
+
63
+ def visit_ClassDef(self, ast_node: ast.ClassDef) -> None:
64
+ self._check_dataclass_config(ast_node)
65
+ self.generic_visit(ast_node)
66
+
67
+ def _check_dataclass_config(self, ast_node: ast.ClassDef) -> None:
68
+ if not check_is_dataclass(ast_node):
69
+ return
70
+ decorator: typing.Final = retrieve_dataclass_decorator(ast_node)
71
+ if decorator is None:
72
+ return
73
+ if check_is_inheriting(ast_node):
74
+ return
75
+ require_slots: typing.Final = not check_dataclass_has_keyword(decorator, "init", value=False)
76
+ require_frozen: typing.Final = require_slots and not check_is_exception_class(ast_node)
77
+ if not check_dataclass_has_required_args(decorator, require_slots=require_slots, require_frozen=require_frozen):
78
+ self.violations.append(
79
+ Violation(
80
+ line_number=ast_node.lineno,
81
+ column_number=ast_node.col_offset,
82
+ violation_code=ViolationCode.DATACLASS_CONFIG,
83
+ )
84
+ )
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+ import typing
3
+
4
+
5
+ MIN_NAME_LENGTH: typing.Final = 8
6
+
7
+ VERB_PREFIXES: typing.Final = {
8
+ "validate",
9
+ "test",
10
+ "execute",
11
+ "visit",
12
+ "get",
13
+ "cancel",
14
+ "retrieve",
15
+ "lock",
16
+ "assert",
17
+ "extract",
18
+ "enrich",
19
+ "run",
20
+ "patch",
21
+ "build",
22
+ "start",
23
+ "ping",
24
+ "prepare",
25
+ "publish",
26
+ "request",
27
+ "ack",
28
+ "nack",
29
+ "reject",
30
+ "stop",
31
+ "route",
32
+ "begin",
33
+ "subscribe",
34
+ "connect",
35
+ "close",
36
+ "enter",
37
+ "exit",
38
+ "take",
39
+ "dump",
40
+ "iter",
41
+ "escape",
42
+ "unescape",
43
+ "add",
44
+ "contains",
45
+ "unsubscribe",
46
+ "resubscribe",
47
+ "commit",
48
+ "raise",
49
+ "mock",
50
+ "produce",
51
+ "consume",
52
+ "track",
53
+ "wait",
54
+ "bootstrap",
55
+ "calculate",
56
+ "check",
57
+ "collect",
58
+ "compute",
59
+ "convert",
60
+ "create",
61
+ "delete",
62
+ "fetch",
63
+ "find",
64
+ "format",
65
+ "generate",
66
+ "handle",
67
+ "has",
68
+ "is",
69
+ "list",
70
+ "load",
71
+ "make",
72
+ "parse",
73
+ "process",
74
+ "read",
75
+ "receive",
76
+ "remove",
77
+ "render",
78
+ "resolve",
79
+ "save",
80
+ "send",
81
+ "set",
82
+ "should",
83
+ "update",
84
+ "write",
85
+ }
86
+
87
+ SCALAR_ANNOTATIONS: typing.Final = {"int", "str", "float", "bool", "bytes", "complex"}
88
+
89
+ MAPPING_PROXY_TYPES: typing.Final = {"MappingProxyType"}
90
+
91
+ ALLOWED_STDLIB_FROM_IMPORTS: typing.Final = {"collections.abc"}
92
+
93
+ FINAL_CLASS_EXCLUDED_BASES: typing.Final = {"BaseModel", "RootModel", "ModelFactory"}
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+ import typing
3
+
4
+
5
+ if typing.TYPE_CHECKING:
6
+ import ast
7
+ from collections.abc import Iterable
8
+
9
+
10
+ from community_of_python_flake8_plugin.checks import execute_all_validations
11
+
12
+
13
+ @typing.final
14
+ class CommunityOfPythonFlake8Plugin:
15
+ name = "community-of-python-flake8-plugin" # noqa: COP004
16
+ version = "0.1.27" # noqa: COP004
17
+
18
+ def __init__(self, tree: ast.AST) -> None: # noqa: COP004
19
+ self.ast_tree = tree
20
+
21
+ def run(self) -> Iterable[tuple[int, int, str, type[object]]]: # noqa: COP004
22
+ violations_list: typing.Final = execute_all_validations(self.ast_tree)
23
+ for violation in violations_list:
24
+ violation_message = f"{violation.violation_code.code} {violation.violation_code.description}"
25
+ yield violation.line_number, violation.column_number, violation_message, type(self)
File without changes
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+ import ast
3
+
4
+
5
+ def check_module_has_all_declaration(ast_node: ast.Module) -> bool:
6
+ for statement in ast_node.body:
7
+ if isinstance(statement, ast.Assign) and any(
8
+ isinstance(target, ast.Name) and target.id == "__all__" for target in statement.targets
9
+ ):
10
+ return True
11
+ if (
12
+ isinstance(statement, ast.AnnAssign)
13
+ and isinstance(statement.target, ast.Name)
14
+ and statement.target.id == "__all__"
15
+ ):
16
+ return True
17
+ return False
18
+
19
+
20
+ def find_parent_class_definition(syntax_tree: ast.AST, ast_node: ast.AST) -> ast.ClassDef | None:
21
+ for potential_parent in ast.walk(syntax_tree):
22
+ if isinstance(potential_parent, ast.ClassDef):
23
+ for child in ast.walk(potential_parent):
24
+ if child is ast_node:
25
+ return potential_parent
26
+ return None
27
+
28
+
29
+ def find_parent_function(syntax_tree: ast.AST, ast_node: ast.AST) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
30
+ for potential_parent in ast.walk(syntax_tree):
31
+ if isinstance(potential_parent, (ast.FunctionDef, ast.AsyncFunctionDef)):
32
+ for child in ast.walk(potential_parent):
33
+ if child is ast_node:
34
+ return potential_parent
35
+ return None
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+ import dataclasses
3
+ import typing
4
+
5
+
6
+ @typing.final
7
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
8
+ class ViolationCode:
9
+ code: str # noqa: COP004
10
+ description: str
11
+
12
+
13
+ @typing.final
14
+ class ViolationCodes:
15
+ MODULE_IMPORT = ViolationCode(code="COP001", description="Use module import when importing more than two names")
16
+ STDLIB_IMPORT = ViolationCode(code="COP002", description="Import standard library modules as whole modules")
17
+ SCALAR_ANNOTATION = ViolationCode(code="COP003", description="Avoid explicit scalar type annotations")
18
+ NAME_LENGTH = ViolationCode(code="COP004", description="Name must be at least 8 characters")
19
+ FUNCTION_VERB = ViolationCode(code="COP005", description="Function identifier must be a verb")
20
+ ASYNC_GET_PREFIX = ViolationCode(code="COP006", description="Avoid get_ prefix in async function names")
21
+ TEMPORARY_VARIABLE = ViolationCode(code="COP007", description="Avoid temporary variables used only once")
22
+ FINAL_CLASS = ViolationCode(code="COP008", description="Classes should be marked typing.final")
23
+ MAPPING_PROXY = ViolationCode(code="COP009", description="Wrap module dictionaries with types.MappingProxyType")
24
+ DATACLASS_CONFIG = ViolationCode(
25
+ code="COP010", description="Use dataclasses with kw_only=True, slots=True, frozen=True"
26
+ )
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+ import dataclasses
3
+ import typing
4
+
5
+
6
+ if typing.TYPE_CHECKING:
7
+ from .violation_codes import ViolationCode
8
+
9
+
10
+ @typing.final
11
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
12
+ class Violation:
13
+ line_number: int
14
+ column_number: int
15
+ violation_code: ViolationCode
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.3
2
+ Name: community-of-python-flake8-plugin
3
+ Version: 0.1.0
4
+ Summary: Community of Python flake8 plugin
5
+ Author: Lev Vereshchagin
6
+ Author-email: Lev Vereshchagin <mail@vrslev.com>
7
+ Requires-Dist: flake8
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # community-of-python-flake8-plugin
12
+
13
+ Community of Python flake8 plugin with custom code style checks.
14
+
15
+ ## Run with uv
16
+
17
+ ```bash
18
+ uv run --with flake8 --with "community-of-python-flake8-plugin @ git+https://github.com/community-of-python/pylines.git@code-style-tests#subdirectory=community-of-python-flake8-plugin" -- flake8 --select COP --exclude .venv .
19
+ ```
20
+
21
+ ## flake8 config (pyproject.toml)
22
+
23
+ ```toml
24
+ [tool.flake8]
25
+ select = ["COP"]
26
+ exclude = [".venv"]
27
+ ```
@@ -0,0 +1,23 @@
1
+ community_of_python_flake8_plugin/__init__.py,sha256=nbK0ThjxTd-7g9SrYc_YWtuqyfl9OkXcJca5nAxYG3s,129
2
+ community_of_python_flake8_plugin/checks/README.md,sha256=FHhhvoL-73kcgrjnogiGhXSaBixgamJs1pKyZgTFkl0,1528
3
+ community_of_python_flake8_plugin/checks/__init__.py,sha256=A0n1YctyMM36vm-3SODvVULWBO2HeE4C7OwA8lCrmbw,3458
4
+ community_of_python_flake8_plugin/checks/cop001.py,sha256=6fsyF-4F9So0p-uHgVW7_AZZKnUX7ovNtHyFtkkiw7c,1855
5
+ community_of_python_flake8_plugin/checks/cop002.py,sha256=SPM3WDOSGBhcXa5NkYkYcFZXQZntbt7clDGHzuOH2PU,1839
6
+ community_of_python_flake8_plugin/checks/cop003.py,sha256=1qoEOaih3takr9NbdUoCJNjmaHCKoFO00luyQBqRd60,3074
7
+ community_of_python_flake8_plugin/checks/cop004.py,sha256=8yiTYJ980BbuMjWu2KxOS0bn523Ys1tvUEbgkLGKnGI,6840
8
+ community_of_python_flake8_plugin/checks/cop005.py,sha256=XYUPbYbimU23tPHpGbuvHAe4ioGnlZ0j6Jm-ARWnUgo,4503
9
+ community_of_python_flake8_plugin/checks/cop006.py,sha256=yQdIC1iIAn-rO28h5_uYaY7-nj27aNMQy0UrrqxOeZQ,2698
10
+ community_of_python_flake8_plugin/checks/cop007.py,sha256=EpGP9oNETtURswA4M7mAIfJYjGxFLuqmahoyZu96_d8,2390
11
+ community_of_python_flake8_plugin/checks/cop008.py,sha256=JBqWPya04QepAwQ3uYcSpWxgMr58airTYEK7SHZm9Mg,2661
12
+ community_of_python_flake8_plugin/checks/cop009.py,sha256=08TbX_7HzshNWVpbR_276PH-ZkCTgVuEKtJEfwQ4yoQ,2223
13
+ community_of_python_flake8_plugin/checks/cop010.py,sha256=-SnaeNVADDl5_FhsPUriekaWMz7kn--7-tKn4g1JssM,3260
14
+ community_of_python_flake8_plugin/constants.py,sha256=Xp7jJRBZx1hKeSqhG22APKNc3DgwsHwMVrYYl4F1ZHs,1467
15
+ community_of_python_flake8_plugin/plugin.py,sha256=sqGiWRUiROV4Ooxvn-87BE1c47V2oP3xylpTQQvCWkE,866
16
+ community_of_python_flake8_plugin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ community_of_python_flake8_plugin/utils.py,sha256=Mee2tp9ps-zRHEC3muxYf5HqlrKHum6FQgPOXRCEMxY,1318
18
+ community_of_python_flake8_plugin/violation_codes.py,sha256=ZwQPTLGJHHTSeUcNzvj1XuRcgoBNoTKLSkuTXQnu7jo,1365
19
+ community_of_python_flake8_plugin/violations.py,sha256=gIXWHs4QEQ1zh-u5uMBJ4jXo4ERb8MwyZfmI4iNChTE,315
20
+ community_of_python_flake8_plugin-0.1.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
21
+ community_of_python_flake8_plugin-0.1.0.dist-info/entry_points.txt,sha256=TVYG2EjuPUWGB7OMdNP3d2shi9sRmDK_K92mDYDpxkQ,97
22
+ community_of_python_flake8_plugin-0.1.0.dist-info/METADATA,sha256=4hiQa_diUQTWfY5Ejag1wpaNHgLwJM_BhlDRKVpO9cI,737
23
+ community_of_python_flake8_plugin-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.28
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [flake8.extension]
2
+ COP = community_of_python_flake8_plugin.plugin:CommunityOfPythonFlake8Plugin
3
+