flake8-kwargs-spaces 0.1.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.
- flake8_kwargs_spaces-0.1.2/LICENSE +21 -0
- flake8_kwargs_spaces-0.1.2/PKG-INFO +9 -0
- flake8_kwargs_spaces-0.1.2/README.md +113 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.egg-info/PKG-INFO +9 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.egg-info/SOURCES.txt +12 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.egg-info/dependency_links.txt +1 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.egg-info/entry_points.txt +2 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.egg-info/requires.txt +4 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.egg-info/top_level.txt +1 -0
- flake8_kwargs_spaces-0.1.2/flake8_kwargs_spaces.py +105 -0
- flake8_kwargs_spaces-0.1.2/pyproject.toml +3 -0
- flake8_kwargs_spaces-0.1.2/setup.cfg +27 -0
- flake8_kwargs_spaces-0.1.2/setup.py +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sahar Gavriely
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flake8-kwargs-spaces
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
License: MIT
|
|
5
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: flake8>=3.7
|
|
8
|
+
Requires-Dist: importlib-metadata>=0.9; python_version < "3.8"
|
|
9
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
[](https://github.com/sahargavriely/flake8-kwargs-spaces/actions/workflows/github-action.yml)
|
|
2
|
+
[](https://codecov.io/gh/sahargavriely/flake8-kwargs-spaces)
|
|
3
|
+
|
|
4
|
+
# Flake8 Keyword Arguments with Spaces Plugin
|
|
5
|
+
|
|
6
|
+
The `flake8-kwargs-spaces` package is a plugin for Flake8 that enforces consistent spacing around the equals sign (`=`) in function arguments, in both definitions and calls. By integrating this plugin, developers can ensure uniform code formatting and adherence to style guidelines.
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
- **Multiline:** When a keyword argument or default is on its own line (e.g. one argument under the `def` or call), use **spaces** around `=` → `key = 'val'`. Violations are reported as **EKS100**.
|
|
11
|
+
- **Inline:** When the argument is on the same line as the `def`/call, or when multiple keyword arguments appear on the same line, use **no spaces** around `=` → `key='val'`. Violations are reported as **EKS251**.
|
|
12
|
+
|
|
13
|
+
## Anti-pattern
|
|
14
|
+
|
|
15
|
+
```py
|
|
16
|
+
def foo(
|
|
17
|
+
key='val'
|
|
18
|
+
):
|
|
19
|
+
return key
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
foo(
|
|
23
|
+
key='val'
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Best practice
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
def foo(
|
|
31
|
+
key = 'val'
|
|
32
|
+
):
|
|
33
|
+
return key
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
foo(
|
|
37
|
+
key = 'val'
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Still anti-pattern
|
|
42
|
+
|
|
43
|
+
```py
|
|
44
|
+
def foo(key = 'val'):
|
|
45
|
+
return key
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
foo(key = 'val')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Still best practice
|
|
52
|
+
|
|
53
|
+
```py
|
|
54
|
+
def foo(key='val'):
|
|
55
|
+
return key
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
foo(key='val')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
**From PyPI** (once published):
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
pip install flake8-kwargs-spaces
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**From source** (development or before first publish):
|
|
70
|
+
|
|
71
|
+
1. Clone the repository and enter it:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
git clone git@github.com:sahargavriely/flake8-kwargs-spaces.git
|
|
75
|
+
cd flake8-kwargs-spaces
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
2. Run the installation script, then activate the virtual environment:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
./scripts/install.sh
|
|
82
|
+
source venv/bin/activate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. (Optional) Run the test suite to verify everything works:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pytest tests/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
4. The install script builds a wheel under `wheels/`. To use the plugin with Flake8, install that wheel in the environment where you want the plugin enforced:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
pip install ./wheels/flake8_kwargs_spaces-0.1.0-py3-none-any.whl
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
5. To avoid conflicts with Flake8’s built-in E251 rule, add a `setup.cfg` (or use your existing one) with:
|
|
98
|
+
|
|
99
|
+
```ini
|
|
100
|
+
[flake8]
|
|
101
|
+
ignore = E251
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT. See [LICENSE](LICENSE).
|
|
107
|
+
|
|
108
|
+
## Thanks
|
|
109
|
+
|
|
110
|
+
- https://www.youtube.com/watch?v=ot5Z4KQPBL8
|
|
111
|
+
- https://www.youtube.com/watch?v=4L0Jb3Ku81s
|
|
112
|
+
- https://www.youtube.com/watch?v=GaWs-LenLYE
|
|
113
|
+
- https://www.youtube.com/watch?v=02aAZ8u3wEQ
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flake8-kwargs-spaces
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
License: MIT
|
|
5
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: flake8>=3.7
|
|
8
|
+
Requires-Dist: importlib-metadata>=0.9; python_version < "3.8"
|
|
9
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
flake8_kwargs_spaces.py
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.cfg
|
|
6
|
+
setup.py
|
|
7
|
+
flake8_kwargs_spaces.egg-info/PKG-INFO
|
|
8
|
+
flake8_kwargs_spaces.egg-info/SOURCES.txt
|
|
9
|
+
flake8_kwargs_spaces.egg-info/dependency_links.txt
|
|
10
|
+
flake8_kwargs_spaces.egg-info/entry_points.txt
|
|
11
|
+
flake8_kwargs_spaces.egg-info/requires.txt
|
|
12
|
+
flake8_kwargs_spaces.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flake8_kwargs_spaces
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
from typing import Any, Dict, Generator, List, Tuple, Type
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
missing_msg = (
|
|
7
|
+
'EKS100 missing whitespace around keyword / parameter equals '
|
|
8
|
+
'(use spaces when the argument is on its own line)'
|
|
9
|
+
)
|
|
10
|
+
unexpected_msg = (
|
|
11
|
+
'EKS251 unexpected whitespace around keyword / parameter equals '
|
|
12
|
+
'(no spaces when inline or multiple args on one line)'
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _default_pairs_from_args(args: ast.arguments) -> List[Tuple[ast.arg, ast.expr]]:
|
|
17
|
+
'''Return (arg, default) for every parameter that has a default.'''
|
|
18
|
+
pairs: List[Tuple[ast.arg, ast.expr]] = []
|
|
19
|
+
# Positional and positional-or-keyword: defaults apply to rightmost of (posonlyargs + args)
|
|
20
|
+
positional_only = getattr(args, 'posonlyargs', [])
|
|
21
|
+
all_positional = positional_only + args.args
|
|
22
|
+
if args.defaults:
|
|
23
|
+
for arg, default in zip(reversed(all_positional), reversed(args.defaults)):
|
|
24
|
+
pairs.append((arg, default))
|
|
25
|
+
# Keyword-only: each kwonlyarg can have a default in kw_defaults (None = no default)
|
|
26
|
+
for arg, default in zip(args.kwonlyargs, args.kw_defaults):
|
|
27
|
+
if default is not None:
|
|
28
|
+
pairs.append((arg, default))
|
|
29
|
+
return pairs
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
LinesMap = Dict[int, List[Tuple[int, ast.expr]]]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Visitor(ast.NodeVisitor):
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self.problems: List[Tuple[int, int, str]] = [] # (lineno, col_offset, message)
|
|
38
|
+
|
|
39
|
+
def visit_Call(self, node: ast.Call) -> Any:
|
|
40
|
+
lines: LinesMap = {}
|
|
41
|
+
for keyword in node.keywords:
|
|
42
|
+
if keyword.arg is None:
|
|
43
|
+
continue
|
|
44
|
+
if keyword.lineno not in lines:
|
|
45
|
+
lines[keyword.lineno] = []
|
|
46
|
+
lines[keyword.lineno].append((len(keyword.arg) + keyword.col_offset, keyword.value))
|
|
47
|
+
self.visit_lines(lines, node.lineno)
|
|
48
|
+
self.generic_visit(node)
|
|
49
|
+
|
|
50
|
+
def _visit_function_def(
|
|
51
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef
|
|
52
|
+
) -> None:
|
|
53
|
+
lines: LinesMap = {}
|
|
54
|
+
for arg, default in _default_pairs_from_args(node.args):
|
|
55
|
+
if arg.lineno not in lines:
|
|
56
|
+
lines[arg.lineno] = []
|
|
57
|
+
# Store start of span (end_col_offset is exclusive in AST, so it's already the first col after arg)
|
|
58
|
+
lines[arg.lineno].append((arg.end_col_offset, default))
|
|
59
|
+
self.visit_lines(lines, node.lineno)
|
|
60
|
+
|
|
61
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> Any:
|
|
62
|
+
self._visit_function_def(node)
|
|
63
|
+
self.generic_visit(node)
|
|
64
|
+
|
|
65
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> Any:
|
|
66
|
+
self._visit_function_def(node)
|
|
67
|
+
self.generic_visit(node)
|
|
68
|
+
|
|
69
|
+
def visit_lines(self, lines: LinesMap, func_line: int) -> None:
|
|
70
|
+
for lineno, line in lines.items():
|
|
71
|
+
if lineno == func_line or len(line) > 1:
|
|
72
|
+
for span_start, value in line:
|
|
73
|
+
self.unexpected_spaces(lineno, span_start, value)
|
|
74
|
+
else:
|
|
75
|
+
span_start, value = line[0]
|
|
76
|
+
self.missing_spaces(lineno, span_start, value)
|
|
77
|
+
|
|
78
|
+
def missing_spaces(
|
|
79
|
+
self, line: int, span_start: int, value: ast.expr
|
|
80
|
+
) -> None:
|
|
81
|
+
if value.col_offset - span_start < 3:
|
|
82
|
+
self.problems.append((line, span_start + 1, missing_msg))
|
|
83
|
+
|
|
84
|
+
def unexpected_spaces(
|
|
85
|
+
self, line: int, span_start: int, value: ast.expr
|
|
86
|
+
) -> None:
|
|
87
|
+
if value.col_offset - span_start > 1:
|
|
88
|
+
self.problems.append((line, span_start + 1, unexpected_msg))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Plugin:
|
|
92
|
+
name = __name__
|
|
93
|
+
try:
|
|
94
|
+
version = importlib.metadata.version(__name__)
|
|
95
|
+
except importlib.metadata.PackageNotFoundError:
|
|
96
|
+
version = '0.0.0'
|
|
97
|
+
|
|
98
|
+
def __init__(self, tree: ast.AST) -> None:
|
|
99
|
+
self._tree = tree
|
|
100
|
+
|
|
101
|
+
def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
|
|
102
|
+
visitor = Visitor()
|
|
103
|
+
visitor.visit(self._tree)
|
|
104
|
+
for line, col, msg in visitor.problems:
|
|
105
|
+
yield line, col, msg, type(self)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = flake8-kwargs-spaces
|
|
3
|
+
version = 0.1.2
|
|
4
|
+
license = MIT
|
|
5
|
+
license_files = LICENSE
|
|
6
|
+
classifiers =
|
|
7
|
+
License :: OSI Approved :: MIT License
|
|
8
|
+
|
|
9
|
+
[options]
|
|
10
|
+
py_modules = flake8_kwargs_spaces
|
|
11
|
+
install_requires =
|
|
12
|
+
flake8>=3.7
|
|
13
|
+
importlib-metadata>=0.9;python_version<"3.8"
|
|
14
|
+
|
|
15
|
+
[options.entry_points]
|
|
16
|
+
flake8.extension =
|
|
17
|
+
EKS=flake8_kwargs_spaces:Plugin
|
|
18
|
+
|
|
19
|
+
[flake8]
|
|
20
|
+
ignore = E251
|
|
21
|
+
extend-ignore = E251,E722,W503
|
|
22
|
+
max-line-length = 132
|
|
23
|
+
|
|
24
|
+
[egg_info]
|
|
25
|
+
tag_build =
|
|
26
|
+
tag_date = 0
|
|
27
|
+
|