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.
@@ -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
+ [![github-workflow](https://github.com/sahargavriely/flake8-kwargs-spaces/actions/workflows/github-action.yml/badge.svg)](https://github.com/sahargavriely/flake8-kwargs-spaces/actions/workflows/github-action.yml)
2
+ [![codecov](https://codecov.io/gh/sahargavriely/flake8-kwargs-spaces/graph/badge.svg?token=W0V7MR7T8S)](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,2 @@
1
+ [flake8.extension]
2
+ EKS = flake8_kwargs_spaces:Plugin
@@ -0,0 +1,4 @@
1
+ flake8>=3.7
2
+
3
+ [:python_version < "3.8"]
4
+ importlib-metadata>=0.9
@@ -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,3 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
@@ -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
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+
4
+ setup()