ballpython 2.0.2__tar.gz → 2.0.4__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.
- ballpython-2.0.4/PKG-INFO +103 -0
- ballpython-2.0.4/README.md +84 -0
- ballpython-2.0.4/ballpython.egg-info/PKG-INFO +103 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/__init__.py +1 -1
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/cli.py +1 -1
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/frameworks/__init__.py +2 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/frameworks/plugins.py +78 -4
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/security_scanner.py +20 -15
- {ballpython-2.0.2 → ballpython-2.0.4}/pyproject.toml +2 -2
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_cli.py +18 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_frameworks.py +51 -0
- ballpython-2.0.2/PKG-INFO +0 -328
- ballpython-2.0.2/README.md +0 -309
- ballpython-2.0.2/ballpython.egg-info/PKG-INFO +0 -328
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/__init__.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/__main__.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/cli.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/py.typed +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/SOURCES.txt +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/dependency_links.txt +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/entry_points.txt +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/requires.txt +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/top_level.txt +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/__main__.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/baseline.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/cache.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/complexity_analyzer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/config.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/dead_code_detector.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/dependency_auditor.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/discovery.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/explanations.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/import_resolver.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/linter_formatter.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/modernizer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/pipeline.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/py.typed +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/syntax_healer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/taint_engine.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/test_generator.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/type_checker.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/typeshed_resolver.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/verifier.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/setup.cfg +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_baseline_and_explain.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_cache.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_cli_prove.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_complexity_analyzer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_config.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_dead_code_detector.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_dead_code_fixer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_dependency_auditor.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_discovery.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_import_resolver.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_linter_formatter.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_modernizer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_mutation_test_generator.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_pipeline.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_security_scanner.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_syntax_healer.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_taint_engine.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_test_generator.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_type_checker.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_verifier.py +0 -0
- {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_verifier_pipeline.py +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ballpython
|
|
3
|
+
Version: 2.0.4
|
|
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.4
|
|
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.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ballpython
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ballpython/)
|
|
4
|
+
[](https://pypi.org/project/ballpython/)
|
|
5
|
+
[](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
**The zero-config Python code cleaner, syntax healer, and quality suite.**
|
|
9
|
+
|
|
10
|
+
`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.
|
|
11
|
+
|
|
12
|
+
Both `ballpython` and `pycleaner` CLI commands are identical.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install ballpython
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
### 1. Fix and format your code
|
|
27
|
+
Run `ballpython` on any file or directory. It automatically heals syntax errors, resolves missing imports, removes unused variables, and applies canonical formatting:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ballpython .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Check code in CI (Dry Run)
|
|
34
|
+
Check for syntax errors, security vulnerabilities, dead code, and linting violations without modifying any files on disk:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
ballpython check .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## What It Does Automatically
|
|
43
|
+
|
|
44
|
+
When you run `ballpython`, all engines run under the hood in one pass:
|
|
45
|
+
|
|
46
|
+
* **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.
|
|
47
|
+
* **Smart Import Resolution:** Resolves undefined symbols to standard library modules and common aliases, inserts `TYPE_CHECKING` guards, and prunes unused imports.
|
|
48
|
+
* **Formatting & Modernization:** Formats code to PEP 8 standards (powered by `ruff`), modernizes legacy type annotations (PEP 585/604), and removes redundant `object` inheritance.
|
|
49
|
+
* **Dead Code Pruning:** Discovers and eliminates unreachable code, empty pass blocks, and unused private definitions.
|
|
50
|
+
* **Security & Taint Audit:** Detects hardcoded API secrets, SQL/command injection patterns, and unsafe `eval`/`exec`/`pickle` calls.
|
|
51
|
+
* **Type Verification:** Verifies function call signatures against bundled Typeshed stubs.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Pre-Commit Hook
|
|
56
|
+
|
|
57
|
+
Add `ballpython` to your `.pre-commit-config.yaml`:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
repos:
|
|
61
|
+
- repo: https://github.com/moonrox420/ball-python
|
|
62
|
+
rev: v2.0.4
|
|
63
|
+
hooks:
|
|
64
|
+
- id: pycleaner
|
|
65
|
+
args: ["check"]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
Optional configuration can be placed in `pyproject.toml` under `[tool.pycleaner]`:
|
|
73
|
+
|
|
74
|
+
```toml
|
|
75
|
+
[tool.pycleaner]
|
|
76
|
+
exclude = ["migrations/", "generated/"]
|
|
77
|
+
line-length = 88
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT License.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ballpython
|
|
3
|
+
Version: 2.0.4
|
|
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.4
|
|
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.
|
|
@@ -103,6 +103,7 @@ def get_default_registry() -> FrameworkRegistry:
|
|
|
103
103
|
FastAPIPlugin,
|
|
104
104
|
PydanticPlugin,
|
|
105
105
|
PytestPlugin,
|
|
106
|
+
PyTorchPlugin,
|
|
106
107
|
SQLAlchemyPlugin,
|
|
107
108
|
)
|
|
108
109
|
|
|
@@ -112,4 +113,5 @@ def get_default_registry() -> FrameworkRegistry:
|
|
|
112
113
|
reg.register(FastAPIPlugin())
|
|
113
114
|
reg.register(SQLAlchemyPlugin())
|
|
114
115
|
reg.register(DataclassPlugin())
|
|
116
|
+
reg.register(PyTorchPlugin())
|
|
115
117
|
return reg
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
pycleaner.frameworks.plugins
|
|
3
3
|
============================
|
|
4
4
|
|
|
5
|
-
Concrete Framework Plugins for Pydantic, Pytest, FastAPI, SQLAlchemy, and
|
|
5
|
+
Concrete Framework Plugins for Pydantic, Pytest, FastAPI, SQLAlchemy, Dataclasses, and PyTorch.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
@@ -50,15 +50,18 @@ class PydanticPlugin:
|
|
|
50
50
|
for node in ast.walk(tree):
|
|
51
51
|
if isinstance(node, ast.Import):
|
|
52
52
|
for alias in node.names:
|
|
53
|
-
if alias.name.split(".")[0]
|
|
53
|
+
if alias.name.split(".")[0] in ("pydantic", "pydantic_settings"):
|
|
54
54
|
return True
|
|
55
55
|
elif isinstance(node, ast.ImportFrom):
|
|
56
|
-
if node.module and node.module.split(".")[0]
|
|
56
|
+
if node.module and node.module.split(".")[0] in (
|
|
57
|
+
"pydantic",
|
|
58
|
+
"pydantic_settings",
|
|
59
|
+
):
|
|
57
60
|
return True
|
|
58
61
|
return False
|
|
59
62
|
|
|
60
63
|
def get_protected_names(self, tree: ast.AST) -> set[str]:
|
|
61
|
-
return {"model_config", "Config", "ConfigDict"}
|
|
64
|
+
return {"model_config", "Config", "ConfigDict", "model_post_init"}
|
|
62
65
|
|
|
63
66
|
def get_protected_decorators(self) -> set[str]:
|
|
64
67
|
return self._VALIDATOR_DECORATORS
|
|
@@ -288,3 +291,74 @@ class DataclassPlugin:
|
|
|
288
291
|
tree: ast.AST,
|
|
289
292
|
) -> bool:
|
|
290
293
|
return name in self.get_protected_names(tree)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class PyTorchPlugin:
|
|
297
|
+
"""Understands PyTorch nn.Module, LightningModule, HuggingFace models, and datasets."""
|
|
298
|
+
|
|
299
|
+
name = "pytorch"
|
|
300
|
+
_MODULE_BASES = {
|
|
301
|
+
"Module",
|
|
302
|
+
"LightningModule",
|
|
303
|
+
"PreTrainedModel",
|
|
304
|
+
"Dataset",
|
|
305
|
+
"IterableDataset",
|
|
306
|
+
"Sampler",
|
|
307
|
+
}
|
|
308
|
+
_PROTECTED_METHODS = {
|
|
309
|
+
"forward",
|
|
310
|
+
"backward",
|
|
311
|
+
"training_step",
|
|
312
|
+
"validation_step",
|
|
313
|
+
"test_step",
|
|
314
|
+
"predict_step",
|
|
315
|
+
"configure_optimizers",
|
|
316
|
+
"optimizer_step",
|
|
317
|
+
"compute_loss",
|
|
318
|
+
"reset_parameters",
|
|
319
|
+
"extra_repr",
|
|
320
|
+
"state_dict",
|
|
321
|
+
"load_state_dict",
|
|
322
|
+
"collate_fn",
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
def is_applicable(self, tree: ast.AST, filepath: Path | str) -> bool:
|
|
326
|
+
torch_pkgs = {
|
|
327
|
+
"torch",
|
|
328
|
+
"torchvision",
|
|
329
|
+
"torchaudio",
|
|
330
|
+
"pytorch_lightning",
|
|
331
|
+
"lightning",
|
|
332
|
+
"transformers",
|
|
333
|
+
"accelerate",
|
|
334
|
+
"timm",
|
|
335
|
+
}
|
|
336
|
+
for node in ast.walk(tree):
|
|
337
|
+
if isinstance(node, ast.Import):
|
|
338
|
+
for alias in node.names:
|
|
339
|
+
if alias.name.split(".")[0] in torch_pkgs:
|
|
340
|
+
return True
|
|
341
|
+
elif isinstance(node, ast.ImportFrom):
|
|
342
|
+
if node.module and node.module.split(".")[0] in torch_pkgs:
|
|
343
|
+
return True
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
def get_protected_names(self, tree: ast.AST) -> set[str]:
|
|
347
|
+
return self._PROTECTED_METHODS
|
|
348
|
+
|
|
349
|
+
def get_protected_decorators(self) -> set[str]:
|
|
350
|
+
return set()
|
|
351
|
+
|
|
352
|
+
def is_protected_field(self, node: ast.AST, class_node: ast.ClassDef) -> bool:
|
|
353
|
+
return _has_base_named(class_node, self._MODULE_BASES)
|
|
354
|
+
|
|
355
|
+
def should_ignore_definition(
|
|
356
|
+
self,
|
|
357
|
+
name: str,
|
|
358
|
+
kind: str,
|
|
359
|
+
node: ast.AST,
|
|
360
|
+
context: str,
|
|
361
|
+
tree: ast.AST,
|
|
362
|
+
) -> bool:
|
|
363
|
+
return name in self._PROTECTED_METHODS
|
|
364
|
+
|
|
@@ -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:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ballpython"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.4"
|
|
8
8
|
description = "The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -44,7 +44,7 @@ ballpython = ["py.typed"]
|
|
|
44
44
|
[tool.pytest.ini_options]
|
|
45
45
|
testpaths = ["tests"]
|
|
46
46
|
python_files = ["test_*.py"]
|
|
47
|
-
addopts = "-p no:pytestqt
|
|
47
|
+
addopts = "-p no:pytestqt"
|
|
48
48
|
|
|
49
49
|
[tool.pycleaner]
|
|
50
50
|
exclude = [
|
|
@@ -357,6 +357,24 @@ class TestCLI(unittest.TestCase):
|
|
|
357
357
|
self.assertIn("types: [python]", output)
|
|
358
358
|
self.assertIn("https://github.com/moonrox420/ball-python", output)
|
|
359
359
|
|
|
360
|
+
def test_default_invocation_runs_fix_not_ultimate(self) -> None:
|
|
361
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
362
|
+
folder = Path(tmp_dir)
|
|
363
|
+
target = folder / "app.py"
|
|
364
|
+
target.write_text("def run(x)\n return x\n", encoding="utf-8")
|
|
365
|
+
|
|
366
|
+
stdout_buf = io.StringIO()
|
|
367
|
+
with redirect_stdout(stdout_buf):
|
|
368
|
+
exit_code = main([str(folder)])
|
|
369
|
+
output = stdout_buf.getvalue()
|
|
370
|
+
|
|
371
|
+
self.assertEqual(exit_code, 0)
|
|
372
|
+
self.assertIn("def run(x):", target.read_text(encoding="utf-8"))
|
|
373
|
+
# Must NOT invoke 6-phase analytical ultimate runner
|
|
374
|
+
self.assertNotIn("Full Spectrum Analysis & Healing", output)
|
|
375
|
+
self.assertNotIn("Phase 2: Project Dependency Audit", output)
|
|
376
|
+
|
|
360
377
|
|
|
361
378
|
if __name__ == "__main__":
|
|
362
379
|
unittest.main()
|
|
380
|
+
|
|
@@ -131,3 +131,54 @@ class Config:
|
|
|
131
131
|
assert "timeout" not in names_flagged
|
|
132
132
|
assert "retries" not in names_flagged
|
|
133
133
|
assert "__post_init__" not in names_flagged
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_pytorch_model_methods_not_flagged(tmp_path: Path) -> None:
|
|
137
|
+
code = """
|
|
138
|
+
import torch
|
|
139
|
+
import torch.nn as nn
|
|
140
|
+
|
|
141
|
+
class TransformerModel(nn.Module):
|
|
142
|
+
def __init__(self):
|
|
143
|
+
super().__init__()
|
|
144
|
+
self.linear = nn.Linear(10, 10)
|
|
145
|
+
|
|
146
|
+
def forward(self, x):
|
|
147
|
+
return self.linear(x)
|
|
148
|
+
|
|
149
|
+
def compute_loss(self, pred, target):
|
|
150
|
+
return (pred - target).sum()
|
|
151
|
+
"""
|
|
152
|
+
f = tmp_path / "model.py"
|
|
153
|
+
f.write_text(code, encoding="utf-8")
|
|
154
|
+
|
|
155
|
+
detector = DeadCodeDetector()
|
|
156
|
+
report = detector.scan_project(tmp_path)
|
|
157
|
+
|
|
158
|
+
names_flagged = {item.name for item in report.items}
|
|
159
|
+
assert "forward" not in names_flagged
|
|
160
|
+
assert "compute_loss" not in names_flagged
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_pydantic_settings_and_model_post_init_not_flagged(tmp_path: Path) -> None:
|
|
164
|
+
code = """
|
|
165
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
166
|
+
|
|
167
|
+
class AppConfig(BaseSettings):
|
|
168
|
+
api_key: str = "secret"
|
|
169
|
+
model_config = SettingsConfigDict(env_file=".env")
|
|
170
|
+
|
|
171
|
+
def model_post_init(self, context) -> None:
|
|
172
|
+
pass
|
|
173
|
+
"""
|
|
174
|
+
f = tmp_path / "settings.py"
|
|
175
|
+
f.write_text(code, encoding="utf-8")
|
|
176
|
+
|
|
177
|
+
detector = DeadCodeDetector()
|
|
178
|
+
report = detector.scan_project(tmp_path)
|
|
179
|
+
|
|
180
|
+
names_flagged = {item.name for item in report.items}
|
|
181
|
+
assert "api_key" not in names_flagged
|
|
182
|
+
assert "model_config" not in names_flagged
|
|
183
|
+
assert "model_post_init" not in names_flagged
|
|
184
|
+
|