ballpython 2.0.2__py3-none-any.whl → 2.0.3__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.
- ballpython-2.0.3.dist-info/METADATA +103 -0
- {ballpython-2.0.2.dist-info → ballpython-2.0.3.dist-info}/RECORD +7 -7
- pycleaner/__init__.py +1 -1
- pycleaner/security_scanner.py +20 -15
- ballpython-2.0.2.dist-info/METADATA +0 -328
- {ballpython-2.0.2.dist-info → ballpython-2.0.3.dist-info}/WHEEL +0 -0
- {ballpython-2.0.2.dist-info → ballpython-2.0.3.dist-info}/entry_points.txt +0 -0
- {ballpython-2.0.2.dist-info → ballpython-2.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ballpython
|
|
3
|
+
Version: 2.0.3
|
|
4
|
+
Summary: The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite
|
|
5
|
+
Author: Developer
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: ruff>=0.1.0
|
|
10
|
+
Requires-Dist: rich>=13.0.0
|
|
11
|
+
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
15
|
+
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
16
|
+
Provides-Extra: security
|
|
17
|
+
Requires-Dist: cryptography>=41.0.0; extra == "security"
|
|
18
|
+
Requires-Dist: defusedxml>=0.7.1; extra == "security"
|
|
19
|
+
|
|
20
|
+
# ballpython
|
|
21
|
+
|
|
22
|
+
[](https://pypi.org/project/ballpython/)
|
|
23
|
+
[](https://pypi.org/project/ballpython/)
|
|
24
|
+
[](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml)
|
|
25
|
+
[](https://opensource.org/licenses/MIT)
|
|
26
|
+
|
|
27
|
+
**The zero-config Python code cleaner, syntax healer, and quality suite.**
|
|
28
|
+
|
|
29
|
+
`ballpython` automatically repairs broken syntax, resolves missing imports, prunes dead code, scans for security vulnerabilities, and formats your code in a single command. Zero LLM dependencies, 100% offline, and fast.
|
|
30
|
+
|
|
31
|
+
Both `ballpython` and `pycleaner` CLI commands are identical.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install ballpython
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
### 1. Fix and format your code
|
|
46
|
+
Run `ballpython` on any file or directory. It automatically heals syntax errors, resolves missing imports, removes unused variables, and applies canonical formatting:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ballpython .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Check code in CI (Dry Run)
|
|
53
|
+
Check for syntax errors, security vulnerabilities, dead code, and linting violations without modifying any files on disk:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
ballpython check .
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What It Does Automatically
|
|
62
|
+
|
|
63
|
+
When you run `ballpython`, all engines run under the hood in one pass:
|
|
64
|
+
|
|
65
|
+
* **Syntax Healing:** Repairs missing statement colons (`def`, `class`, `if`), accidental `=` assignments in conditionals, unclosed parentheses/brackets, tab/space mixups, and legacy Python 2 except/print statements.
|
|
66
|
+
* **Smart Import Resolution:** Resolves undefined symbols to standard library modules and common aliases, inserts `TYPE_CHECKING` guards, and prunes unused imports.
|
|
67
|
+
* **Formatting & Modernization:** Formats code to PEP 8 standards (powered by `ruff`), modernizes legacy type annotations (PEP 585/604), and removes redundant `object` inheritance.
|
|
68
|
+
* **Dead Code Pruning:** Discovers and eliminates unreachable code, empty pass blocks, and unused private definitions.
|
|
69
|
+
* **Security & Taint Audit:** Detects hardcoded API secrets, SQL/command injection patterns, and unsafe `eval`/`exec`/`pickle` calls.
|
|
70
|
+
* **Type Verification:** Verifies function call signatures against bundled Typeshed stubs.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Pre-Commit Hook
|
|
75
|
+
|
|
76
|
+
Add `ballpython` to your `.pre-commit-config.yaml`:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
repos:
|
|
80
|
+
- repo: https://github.com/moonrox420/ball-python
|
|
81
|
+
rev: v2.0.3
|
|
82
|
+
hooks:
|
|
83
|
+
- id: pycleaner
|
|
84
|
+
args: ["check"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
Optional configuration can be placed in `pyproject.toml` under `[tool.pycleaner]`:
|
|
92
|
+
|
|
93
|
+
```toml
|
|
94
|
+
[tool.pycleaner]
|
|
95
|
+
exclude = ["migrations/", "generated/"]
|
|
96
|
+
line-length = 88
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
MIT License.
|
|
@@ -2,7 +2,7 @@ ballpython/__init__.py,sha256=d8ONxgaz9jml_fHPVvg867Vt5jGRhGTqzWPtux3WJOc,206
|
|
|
2
2
|
ballpython/__main__.py,sha256=aCRr0vnZfKYHDrPHBsYSjX5xTxtFJpTTSPVUSZTBK-I,136
|
|
3
3
|
ballpython/cli.py,sha256=LllCLRAQQ-fRR_lIXwmd2e3Q7DLCIvf-5jHBXsAhJWs,125
|
|
4
4
|
ballpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
pycleaner/__init__.py,sha256=
|
|
5
|
+
pycleaner/__init__.py,sha256=dK4jaytHQuXqaK3pqum6o8z8JrRLU-XHUfv_NBOtdxs,2894
|
|
6
6
|
pycleaner/__main__.py,sha256=8QBFrPyxId563F6cipPMNVEBTG8TIZUgxQfp8gk1VSI,135
|
|
7
7
|
pycleaner/baseline.py,sha256=DS32C8_G8khYYNJo42FVv6Cc58lJj7NUmPOzWNfQwlU,3856
|
|
8
8
|
pycleaner/cache.py,sha256=WhCiMkuL6ryWVr3l3XrYLLoI7A3bRF8jNxpOyedNW8M,10761
|
|
@@ -18,7 +18,7 @@ pycleaner/linter_formatter.py,sha256=LB5j66OCASU8WG8QgRFpSX7C2c7_UNMBgkJh2hSWxAY
|
|
|
18
18
|
pycleaner/modernizer.py,sha256=L9hxXXfdyTwf8itDO58y6bW5LobGo6dg8q537QdiYbM,12946
|
|
19
19
|
pycleaner/pipeline.py,sha256=ZG7_9PNz0jBadFrZoiI7juLtUJXglGQhLgQhrLJqEac,20612
|
|
20
20
|
pycleaner/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
pycleaner/security_scanner.py,sha256=
|
|
21
|
+
pycleaner/security_scanner.py,sha256=h7WSPYf40X-PBlJVOsJL-KaknDaOcWCMhcPwXkUvlbA,21986
|
|
22
22
|
pycleaner/syntax_healer.py,sha256=TlIMKIJhaX9GED3e2JNzNMJ5aXmL9eLJMCu7xKSjdv8,26711
|
|
23
23
|
pycleaner/taint_engine.py,sha256=96cZdDpSPbo5b13zIZY18f_4GJm_tw0clqCZMBtjXMQ,26481
|
|
24
24
|
pycleaner/test_generator.py,sha256=TJsNJSojc6eIsaKZkwMlpnb-MKHuDpNLZCPnMlO-a8M,19663
|
|
@@ -27,8 +27,8 @@ pycleaner/typeshed_resolver.py,sha256=N-kWU_CI5DG59lQMkt7wMW9sHYjNCBdfFS42b4y2Kr
|
|
|
27
27
|
pycleaner/verifier.py,sha256=pOfSLRNHe4qDUYJG8mxDxjY6Ph8QKBdJZmqBtoJX87w,17937
|
|
28
28
|
pycleaner/frameworks/__init__.py,sha256=7MLgg469hCNtQR6dsE5gZcAZkCywJHz9zOPrbH8NmrE,3415
|
|
29
29
|
pycleaner/frameworks/plugins.py,sha256=K3J4mVJw2MR_pa6Mhmo5oZOX8pUesL8lx9LUo4RLrNo,9390
|
|
30
|
-
ballpython-2.0.
|
|
31
|
-
ballpython-2.0.
|
|
32
|
-
ballpython-2.0.
|
|
33
|
-
ballpython-2.0.
|
|
34
|
-
ballpython-2.0.
|
|
30
|
+
ballpython-2.0.3.dist-info/METADATA,sha256=K1nMS6fIKnHpTsJ0mRLaKNv_o1TFuzb7SI_KgywGCaw,3466
|
|
31
|
+
ballpython-2.0.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
32
|
+
ballpython-2.0.3.dist-info/entry_points.txt,sha256=6ux-5UxoVSmlK5p9M-GDVt24sCdwTQq9wgRDkpMIYU8,81
|
|
33
|
+
ballpython-2.0.3.dist-info/top_level.txt,sha256=u6lmy8RWZ_saxCZAQTL7EijB4sunegOiVyk5fHjpwLk,21
|
|
34
|
+
ballpython-2.0.3.dist-info/RECORD,,
|
pycleaner/__init__.py
CHANGED
pycleaner/security_scanner.py
CHANGED
|
@@ -196,9 +196,14 @@ class _DangerousCallDetector(ast.NodeVisitor):
|
|
|
196
196
|
return self.source_lines[lineno - 1].strip()
|
|
197
197
|
return ""
|
|
198
198
|
|
|
199
|
-
def _is_suppressed(
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
def _is_suppressed(
|
|
200
|
+
self, lineno: int, category: str, end_lineno: int | None = None
|
|
201
|
+
) -> bool:
|
|
202
|
+
start = max(1, lineno)
|
|
203
|
+
end = max(start, end_lineno or lineno)
|
|
204
|
+
max_line = len(self.source_lines)
|
|
205
|
+
for ln in range(start, min(end + 1, max_line + 1)):
|
|
206
|
+
line = self.source_lines[ln - 1]
|
|
202
207
|
if "#" in line:
|
|
203
208
|
comment = line.split("#", 1)[1].strip().lower()
|
|
204
209
|
if (
|
|
@@ -208,21 +213,21 @@ class _DangerousCallDetector(ast.NodeVisitor):
|
|
|
208
213
|
or f"ignore[{category.lower()}]" in comment
|
|
209
214
|
):
|
|
210
215
|
return True
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
if start > 1:
|
|
217
|
+
prev_line = self.source_lines[start - 2]
|
|
218
|
+
if prev_line.strip().startswith("#"):
|
|
219
|
+
prev_comment = prev_line.strip().lstrip("#").strip().lower()
|
|
220
|
+
if (
|
|
221
|
+
"nosec" in prev_comment
|
|
222
|
+
or "noqa" in prev_comment
|
|
223
|
+
or "pycleaner: ignore" in prev_comment
|
|
224
|
+
or f"ignore[{category.lower()}]" in prev_comment
|
|
225
|
+
):
|
|
226
|
+
return True
|
|
222
227
|
return False
|
|
223
228
|
|
|
224
229
|
def _add_finding(self, finding: SecurityFinding) -> None:
|
|
225
|
-
if not self._is_suppressed(finding.lineno, finding.category):
|
|
230
|
+
if not self._is_suppressed(finding.lineno, finding.category, finding.end_lineno):
|
|
226
231
|
self.findings.append(finding)
|
|
227
232
|
|
|
228
233
|
def visit_Call(self, node: ast.Call) -> None:
|
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ballpython
|
|
3
|
-
Version: 2.0.2
|
|
4
|
-
Summary: The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite
|
|
5
|
-
Author: Developer
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: ruff>=0.1.0
|
|
10
|
-
Requires-Dist: rich>=13.0.0
|
|
11
|
-
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
12
|
-
Provides-Extra: dev
|
|
13
|
-
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
14
|
-
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
15
|
-
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
16
|
-
Provides-Extra: security
|
|
17
|
-
Requires-Dist: cryptography>=41.0.0; extra == "security"
|
|
18
|
-
Requires-Dist: defusedxml>=0.7.1; extra == "security"
|
|
19
|
-
|
|
20
|
-
# pycleaner — Ultimate Static Python Code Quality Suite
|
|
21
|
-
|
|
22
|
-
A production-grade, offline Python static analysis, syntax healing, linting, formatting, security scanning, complexity evaluation, dead code detection, and dependency auditing suite.
|
|
23
|
-
|
|
24
|
-
Zero external LLM dependencies, zero mock modes, and built for deterministic developer workflows and CI/CD pipelines.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Key Capabilities
|
|
29
|
-
|
|
30
|
-
1. **Syntax Healer (`SyntaxHealer`)**:
|
|
31
|
-
- Statically repairs missing colons on compound statement headers (`def`, `class`, `if`, `elif`, `else`, `for`, `while`, `try`, `except`, `finally`, `with`, `async def`, `match`, `case`).
|
|
32
|
-
- Corrects accidental single `=` assignments in conditionals (`if x = 1:` $\to$ `if x == 1:`) without corrupting keyword arguments (`func(x=1)`), augmented assignments, or walrus expressions (`x := 1`).
|
|
33
|
-
- Modernizes legacy Python 2 statements (`print "..."` $\to$ `print(...)`, `except Error, e:` $\to$ `except Error as e:`, and multi-exception tuples `except (E1, E2), e:` on Python 3.12+).
|
|
34
|
-
- Automatically closes unclosed parentheses, brackets, and braces.
|
|
35
|
-
- Normalizes mixed tab characters into 4 spaces.
|
|
36
|
-
|
|
37
|
-
2. **Missing Import Resolver (`ImportResolver`)**:
|
|
38
|
-
- Walks Python AST to extract undefined loaded symbols across local, function, class, and comprehension scopes.
|
|
39
|
-
- Resolves undefined identifiers to standard library modules (`os`, `sys`, `json`, `re`, `subprocess`, `pathlib`, etc.), collections, typing constructs, dataclasses, concurrent primitives (`ThreadPoolExecutor`, `ProcessPoolExecutor`, `Lock`, `Queue`), and popular aliases (`np`, `pd`, `plt`, `sns`, `tf`, `nn`).
|
|
40
|
-
- **`TYPE_CHECKING` Awareness**: Injects typing-only imports into `if TYPE_CHECKING:` guards and adds `from __future__ import annotations` when symbols are used strictly in type annotations.
|
|
41
|
-
- **Custom Import Mapping**: Configurable mapping from symbol names to exact import statements.
|
|
42
|
-
|
|
43
|
-
3. **Linter & Formatter (`LinterFormatter`)**:
|
|
44
|
-
- Integrates Rust-based `ruff` via in-memory stream processing (`--stdin-filename`).
|
|
45
|
-
- Automatically removes unused imports (`F401`) and unused variables (`F841`).
|
|
46
|
-
- Sorts and groups imports via `isort` / `ruff` or an internal pure-Python 3-tier sorter (stdlib $\to$ third-party $\to$ local).
|
|
47
|
-
- Modernizes deprecated syntax via `pyupgrade` (`UP`).
|
|
48
|
-
- Applies deterministic PEP 8 formatting (`ruff format` / `black` / pure-Python formatter).
|
|
49
|
-
- Wraps long `from ... import (...)` statements exceeding configured `line-length`.
|
|
50
|
-
- Modernizes legacy typing to PEP 585/604 syntax (`List[T]` -> `list[T]`, `Optional[T]` -> `T | None`), singleton comparisons, and `class X(object)` inheritance via `Modernizer` (disable with `--no-modernize`).
|
|
51
|
-
|
|
52
|
-
4. **Dead Code Detector (`DeadCodeDetector`)**:
|
|
53
|
-
- Builds a cross-file symbol definition and reference graph across a project.
|
|
54
|
-
- Identifies unused functions, methods, classes, module-level constants, and class attributes.
|
|
55
|
-
- Respects public exports in `__all__` and framework decorators (`@app.route`, `@pytest.fixture`, `@abstractmethod`, etc.).
|
|
56
|
-
- Detects unreachable code following unconditional `return`, `raise`, `break`, `continue`, or `sys.exit()`.
|
|
57
|
-
- Identifies empty pass blocks with no comments.
|
|
58
|
-
- Auto-prunes unreachable code, redundant `pass` statements, and `if False:` branches via `DeadCodeFixer` (enable with `dead-code --fix`, disable in the pipeline with `--no-dead-code`).
|
|
59
|
-
|
|
60
|
-
5. **Static Security Scanner (`SecurityScanner`)**:
|
|
61
|
-
- Detects dangerous function calls: `eval()`, `exec()`, `compile()`, `pickle.loads()`, `os.system()`, and unsafe `yaml.load()` lacking `SafeLoader`.
|
|
62
|
-
- Flags SQL injection patterns (string concatenation, f-strings, `%`-formatting, or `.format()` inside `cursor.execute()`).
|
|
63
|
-
- Flags subprocess command injection (`shell=True`).
|
|
64
|
-
- Flags insecure transport (`verify=False` in `requests` or `httpx`).
|
|
65
|
-
- Scans for hardcoded secrets: AWS access/secret keys, GitHub tokens, Slack tokens, JWT tokens, private key headers, and generic passwords/API keys.
|
|
66
|
-
- Flags production `assert` statements used for input validation (which get stripped under `python -O`).
|
|
67
|
-
|
|
68
|
-
6. **Complexity Analyzer (`ComplexityAnalyzer`)**:
|
|
69
|
-
- McCabe Cyclomatic Complexity per function ($E - N + 2$).
|
|
70
|
-
- Sonar-style Cognitive Complexity scoring (penalizing nested control flow, compound boolean conditions, and break in linear flow).
|
|
71
|
-
- Metrics tracked: line count, argument count, return statement count, and maximum nesting depth.
|
|
72
|
-
- Configurable thresholds with tabular and JSON reporting.
|
|
73
|
-
|
|
74
|
-
7. **Dependency Auditor (`DependencyAuditor`)**:
|
|
75
|
-
- Statically scans all `.py` files across a repository to discover external third-party imports.
|
|
76
|
-
- Maps module import names to PyPI distribution package names (e.g., `yaml` $\to$ `PyYAML`, `PIL` $\to$ `pillow`, `cv2` $\to$ `opencv-python`).
|
|
77
|
-
- Compares imported dependencies against `requirements.txt` and `pyproject.toml`.
|
|
78
|
-
- Synchronizes `requirements.txt` with `--fix-deps` and prunes unused packages with `--prune-deps`.
|
|
79
|
-
|
|
80
|
-
8. **Safety & Concurrency**:
|
|
81
|
-
- Backup creation (`.pycleaner.bak`) before overwriting files (enabled by default).
|
|
82
|
-
- Parallel multi-core file processing via `ProcessPoolExecutor`.
|
|
83
|
-
- Continuous file watching (`watch` mode) with automatic re-healing on save.
|
|
84
|
-
- Pre-commit configuration generator (`hook` subcommand).
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## Installation
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
# Editable install
|
|
92
|
-
pip install -e .
|
|
93
|
-
|
|
94
|
-
# Core dependencies
|
|
95
|
-
pip install ruff rich
|
|
96
|
-
|
|
97
|
-
# Optional security and development packages
|
|
98
|
-
pip install -e ".[dev,security]"
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## CLI Usage
|
|
104
|
-
|
|
105
|
-
### Subcommands
|
|
106
|
-
|
|
107
|
-
#### `fix` (Default Action)
|
|
108
|
-
Heals syntax, modernizes legacy typing (PEP 585/604), prunes dead code, resolves imports, fixes lint violations, and formats code:
|
|
109
|
-
```bash
|
|
110
|
-
py -m pycleaner fix src/
|
|
111
|
-
py -m pycleaner fix --diff path/to/script.py
|
|
112
|
-
py -m pycleaner fix --no-backup src/
|
|
113
|
-
py -m pycleaner fix --parallel --workers 4 src/
|
|
114
|
-
py -m pycleaner fix --no-modernize --no-dead-code src/
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
#### `check` (Dry-Run CI Verification)
|
|
118
|
-
Inspects files without writing modifications to disk. Exits with code `1` if changes or errors are detected:
|
|
119
|
-
```bash
|
|
120
|
-
py -m pycleaner check src/
|
|
121
|
-
py -m pycleaner check --diff src/
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
#### `scan` (Security Vulnerability Audit)
|
|
125
|
-
Scans Python files for hardcoded secrets, dangerous calls, and injection vulnerabilities:
|
|
126
|
-
```bash
|
|
127
|
-
py -m pycleaner scan .
|
|
128
|
-
py -m pycleaner scan --severity HIGH .
|
|
129
|
-
py -m pycleaner scan --json .
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
#### `complexity` (Function Complexity Metrics)
|
|
133
|
-
Calculates Cyclomatic Complexity, Cognitive Complexity, and architectural metrics:
|
|
134
|
-
```bash
|
|
135
|
-
py -m pycleaner complexity .
|
|
136
|
-
py -m pycleaner complexity --max-cyclomatic 10 --max-cognitive 15 .
|
|
137
|
-
py -m pycleaner complexity --json .
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
#### `dead-code` (Unused Symbols & Unreachable Branches)
|
|
141
|
-
Finds unused functions, unused classes, empty pass branches, and dead code:
|
|
142
|
-
```bash
|
|
143
|
-
py -m pycleaner dead-code .
|
|
144
|
-
py -m pycleaner dead-code --json .
|
|
145
|
-
py -m pycleaner dead-code --fix .
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
#### `audit` (Project Dependency Verification)
|
|
149
|
-
Audits imported third-party libraries against `requirements.txt`:
|
|
150
|
-
```bash
|
|
151
|
-
# Audit only
|
|
152
|
-
py -m pycleaner audit .
|
|
153
|
-
|
|
154
|
-
# Automatically append missing dependencies
|
|
155
|
-
py -m pycleaner audit --fix-deps .
|
|
156
|
-
|
|
157
|
-
# Append missing and remove unimported dependencies
|
|
158
|
-
py -m pycleaner audit --fix-deps --prune-deps .
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### `all` (Complete Quality Sweep)
|
|
162
|
-
Executes all engines in one command (fix + dependency audit + security scan + complexity + dead code):
|
|
163
|
-
```bash
|
|
164
|
-
py -m pycleaner all .
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
#### `watch` (Continuous File Watcher)
|
|
168
|
-
Watches files for filesystem modifications and auto-cleans on save:
|
|
169
|
-
```bash
|
|
170
|
-
py -m pycleaner watch src/ --interval 1.0
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
#### `hook` (Pre-Commit Integration)
|
|
174
|
-
Outputs a ready-to-use `.pre-commit-hooks.yaml` configuration block:
|
|
175
|
-
```bash
|
|
176
|
-
py -m pycleaner hook
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
### Backward-Compatible Flat Invocations
|
|
182
|
-
|
|
183
|
-
Legacy invocations continue to work seamlessly:
|
|
184
|
-
```bash
|
|
185
|
-
pycleaner -a . # Equivalent to: pycleaner all .
|
|
186
|
-
pycleaner --check --diff src/ # Equivalent to: pycleaner check --diff src/
|
|
187
|
-
pycleaner --deps-only . # Equivalent to: pycleaner audit .
|
|
188
|
-
pycleaner path/to/file.py # Equivalent to: pycleaner fix path/to/file.py
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Configuration
|
|
194
|
-
|
|
195
|
-
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`. An explicit file can be forced with `pycleaner --config path/to/pyproject.toml <command> ...`, which overrides target-path auto-discovery.
|
|
196
|
-
|
|
197
|
-
### `pyproject.toml` Example
|
|
198
|
-
|
|
199
|
-
```toml
|
|
200
|
-
[tool.pycleaner]
|
|
201
|
-
# File targeting
|
|
202
|
-
exclude = ["migrations/", "generated/", "*_pb2.py", "*_pb2_grpc.py"]
|
|
203
|
-
include = ["src/", "tests/"]
|
|
204
|
-
|
|
205
|
-
# Syntax healing
|
|
206
|
-
fix-py2-syntax = true
|
|
207
|
-
fix-conditional-assignments = true
|
|
208
|
-
|
|
209
|
-
# Import resolution
|
|
210
|
-
custom-import-map = { "logger" = "from myapp.core.logging import logger" }
|
|
211
|
-
auto-add-future-annotations = false
|
|
212
|
-
|
|
213
|
-
# Linting & formatting
|
|
214
|
-
line-length = 88
|
|
215
|
-
select-rules = "F401,F841,I,UP,E,W,B,SIM,RUF"
|
|
216
|
-
|
|
217
|
-
# Dead code
|
|
218
|
-
ignore-decorators = ["@app.route", "@pytest.fixture", "@override"]
|
|
219
|
-
ignore-names = ["_*", "test_*"]
|
|
220
|
-
|
|
221
|
-
# Security
|
|
222
|
-
security-severity-threshold = "LOW"
|
|
223
|
-
ignore-security-rules = []
|
|
224
|
-
|
|
225
|
-
# Complexity thresholds
|
|
226
|
-
max-cyclomatic-complexity = 10
|
|
227
|
-
max-cognitive-complexity = 15
|
|
228
|
-
max-function-length = 50
|
|
229
|
-
max-arguments = 5
|
|
230
|
-
|
|
231
|
-
# Behavior
|
|
232
|
-
backup = true
|
|
233
|
-
parallel = false
|
|
234
|
-
max-workers = 4
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Programmatic API
|
|
240
|
-
|
|
241
|
-
### 1. Cleaning Source Code or Files
|
|
242
|
-
```python
|
|
243
|
-
from pathlib import Path
|
|
244
|
-
from pycleaner import CleanPipeline, load_config
|
|
245
|
-
|
|
246
|
-
# Load config with project overrides
|
|
247
|
-
config = load_config(project_root=".")
|
|
248
|
-
pipeline = CleanPipeline(config=config)
|
|
249
|
-
|
|
250
|
-
# In-memory code processing
|
|
251
|
-
dirty_code = """
|
|
252
|
-
import math
|
|
253
|
-
|
|
254
|
-
def calculate(x)
|
|
255
|
-
if x = 0
|
|
256
|
-
return Path('.')
|
|
257
|
-
return Path(str(x))
|
|
258
|
-
"""
|
|
259
|
-
result = pipeline.process_source(dirty_code, filename="example.py")
|
|
260
|
-
print("Cleaned code:\n", result.cleaned_code)
|
|
261
|
-
print("Repairs applied:", result.syntax_repairs)
|
|
262
|
-
print("Imports added:", result.resolved_imports)
|
|
263
|
-
|
|
264
|
-
# File processing with automatic backup
|
|
265
|
-
file_result = pipeline.process_file("example.py", apply_changes=True, backup=True)
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### 2. Security Vulnerability Scanning
|
|
269
|
-
```python
|
|
270
|
-
from pycleaner import SecurityScanner
|
|
271
|
-
|
|
272
|
-
scanner = SecurityScanner(severity_threshold="MEDIUM")
|
|
273
|
-
report = scanner.scan_project("src/")
|
|
274
|
-
|
|
275
|
-
for finding in report.findings:
|
|
276
|
-
print(f"[{finding.severity}] {finding.category} at {finding.filepath}:{finding.lineno}")
|
|
277
|
-
print(f" Message: {finding.message}")
|
|
278
|
-
print(f" Fix: {finding.suggestion}")
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### 3. Complexity Analysis
|
|
282
|
-
```python
|
|
283
|
-
from pycleaner import ComplexityAnalyzer
|
|
284
|
-
|
|
285
|
-
analyzer = ComplexityAnalyzer()
|
|
286
|
-
report = analyzer.analyze_project("src/")
|
|
287
|
-
|
|
288
|
-
print(f"Scanned {report.files_scanned} files, {report.count} functions.")
|
|
289
|
-
print(f"Average Cyclomatic: {report.average_cyclomatic:.2f}")
|
|
290
|
-
|
|
291
|
-
violations = report.above_threshold(max_cyclomatic=10, max_cognitive=15)
|
|
292
|
-
for func in violations:
|
|
293
|
-
print(f"{func.qualified_name} (CC={func.cyclomatic}, Cog={func.cognitive})")
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### 4. Dead Code Detection
|
|
297
|
-
```python
|
|
298
|
-
from pycleaner import DeadCodeDetector
|
|
299
|
-
|
|
300
|
-
detector = DeadCodeDetector()
|
|
301
|
-
report = detector.scan_project("src/")
|
|
302
|
-
|
|
303
|
-
print(f"{report.count} dead code item(s) across {report.files_scanned} file(s).")
|
|
304
|
-
|
|
305
|
-
for item in report.items:
|
|
306
|
-
print(f"Dead {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
307
|
-
|
|
308
|
-
for item in report.by_kind("unreachable"):
|
|
309
|
-
print(f"Unreachable code at {item.filepath}:{item.lineno} ({item.reason})")
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## Exit Codes
|
|
315
|
-
|
|
316
|
-
| Exit Code | Meaning |
|
|
317
|
-
|:---:|:---|
|
|
318
|
-
| `0` | Clean run: all files valid, no errors, or changes cleanly written |
|
|
319
|
-
| `1` | Issues found: check mode detected modifications, errors occurred, or critical security findings flagged |
|
|
320
|
-
| `2` | Configuration error: invalid TOML syntax, invalid configuration keys, or mismatched types |
|
|
321
|
-
|
|
322
|
-
---
|
|
323
|
-
|
|
324
|
-
## License
|
|
325
|
-
|
|
326
|
-
MIT License.
|
|
327
|
-
# ball-python
|
|
328
|
-
# ball-python
|
|
File without changes
|
|
File without changes
|
|
File without changes
|