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.
Files changed (65) hide show
  1. ballpython-2.0.4/PKG-INFO +103 -0
  2. ballpython-2.0.4/README.md +84 -0
  3. ballpython-2.0.4/ballpython.egg-info/PKG-INFO +103 -0
  4. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/__init__.py +1 -1
  5. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/cli.py +1 -1
  6. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/frameworks/__init__.py +2 -0
  7. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/frameworks/plugins.py +78 -4
  8. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/security_scanner.py +20 -15
  9. {ballpython-2.0.2 → ballpython-2.0.4}/pyproject.toml +2 -2
  10. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_cli.py +18 -0
  11. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_frameworks.py +51 -0
  12. ballpython-2.0.2/PKG-INFO +0 -328
  13. ballpython-2.0.2/README.md +0 -309
  14. ballpython-2.0.2/ballpython.egg-info/PKG-INFO +0 -328
  15. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/__init__.py +0 -0
  16. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/__main__.py +0 -0
  17. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/cli.py +0 -0
  18. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython/py.typed +0 -0
  19. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/SOURCES.txt +0 -0
  20. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/dependency_links.txt +0 -0
  21. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/entry_points.txt +0 -0
  22. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/requires.txt +0 -0
  23. {ballpython-2.0.2 → ballpython-2.0.4}/ballpython.egg-info/top_level.txt +0 -0
  24. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/__main__.py +0 -0
  25. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/baseline.py +0 -0
  26. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/cache.py +0 -0
  27. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/complexity_analyzer.py +0 -0
  28. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/config.py +0 -0
  29. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/dead_code_detector.py +0 -0
  30. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/dependency_auditor.py +0 -0
  31. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/discovery.py +0 -0
  32. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/explanations.py +0 -0
  33. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/import_resolver.py +0 -0
  34. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/linter_formatter.py +0 -0
  35. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/modernizer.py +0 -0
  36. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/pipeline.py +0 -0
  37. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/py.typed +0 -0
  38. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/syntax_healer.py +0 -0
  39. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/taint_engine.py +0 -0
  40. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/test_generator.py +0 -0
  41. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/type_checker.py +0 -0
  42. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/typeshed_resolver.py +0 -0
  43. {ballpython-2.0.2 → ballpython-2.0.4}/pycleaner/verifier.py +0 -0
  44. {ballpython-2.0.2 → ballpython-2.0.4}/setup.cfg +0 -0
  45. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_baseline_and_explain.py +0 -0
  46. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_cache.py +0 -0
  47. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_cli_prove.py +0 -0
  48. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_complexity_analyzer.py +0 -0
  49. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_config.py +0 -0
  50. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_dead_code_detector.py +0 -0
  51. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_dead_code_fixer.py +0 -0
  52. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_dependency_auditor.py +0 -0
  53. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_discovery.py +0 -0
  54. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_import_resolver.py +0 -0
  55. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_linter_formatter.py +0 -0
  56. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_modernizer.py +0 -0
  57. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_mutation_test_generator.py +0 -0
  58. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_pipeline.py +0 -0
  59. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_security_scanner.py +0 -0
  60. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_syntax_healer.py +0 -0
  61. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_taint_engine.py +0 -0
  62. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_test_generator.py +0 -0
  63. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_type_checker.py +0 -0
  64. {ballpython-2.0.2 → ballpython-2.0.4}/tests/test_verifier.py +0 -0
  65. {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
+ [![PyPI version](https://img.shields.io/pypi/v/ballpython.svg)](https://pypi.org/project/ballpython/)
23
+ [![Python Versions](https://img.shields.io/pypi/pyversions/ballpython.svg)](https://pypi.org/project/ballpython/)
24
+ [![CI](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml/badge.svg)](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml)
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/ballpython.svg)](https://pypi.org/project/ballpython/)
4
+ [![Python Versions](https://img.shields.io/pypi/pyversions/ballpython.svg)](https://pypi.org/project/ballpython/)
5
+ [![CI](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml/badge.svg)](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/ballpython.svg)](https://pypi.org/project/ballpython/)
23
+ [![Python Versions](https://img.shields.io/pypi/pyversions/ballpython.svg)](https://pypi.org/project/ballpython/)
24
+ [![CI](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml/badge.svg)](https://github.com/moonrox420/ball-python/actions/workflows/ci.yml)
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.
@@ -18,7 +18,7 @@ from pycleaner.discovery import (
18
18
  is_protected_file,
19
19
  )
20
20
 
21
- __version__ = "2.0.2"
21
+ __version__ = "2.0.4"
22
22
  __all__ = [
23
23
  "BaselineFingerprint",
24
24
  "BaselineManager",
@@ -567,7 +567,7 @@ def _resolve_cli_command(args: argparse.Namespace) -> str:
567
567
  return "all"
568
568
  if getattr(args, "check", False):
569
569
  return "check"
570
- return "ultimate"
570
+ return "fix"
571
571
 
572
572
 
573
573
  def _run_all_command(
@@ -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 Dataclasses.
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] == "pydantic":
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] == "pydantic":
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(self, lineno: int, category: str) -> bool:
200
- if 1 <= lineno <= len(self.source_lines):
201
- line = self.source_lines[lineno - 1]
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
- if lineno > 1:
212
- prev_line = self.source_lines[lineno - 2]
213
- if prev_line.strip().startswith("#"):
214
- prev_comment = prev_line.strip().lstrip("#").strip().lower()
215
- if (
216
- "nosec" in prev_comment
217
- or "noqa" in prev_comment
218
- or "pycleaner: ignore" in prev_comment
219
- or f"ignore[{category.lower()}]" in prev_comment
220
- ):
221
- return True
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.2"
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 --basetemp=.pytest_temp"
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
+