ballpython 2.0.3__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 (62) hide show
  1. {ballpython-2.0.3 → ballpython-2.0.4}/PKG-INFO +2 -2
  2. {ballpython-2.0.3 → ballpython-2.0.4}/README.md +1 -1
  3. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython.egg-info/PKG-INFO +2 -2
  4. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/__init__.py +1 -1
  5. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/cli.py +1 -1
  6. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/frameworks/__init__.py +2 -0
  7. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/frameworks/plugins.py +78 -4
  8. {ballpython-2.0.3 → ballpython-2.0.4}/pyproject.toml +2 -2
  9. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_cli.py +18 -0
  10. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_frameworks.py +51 -0
  11. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython/__init__.py +0 -0
  12. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython/__main__.py +0 -0
  13. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython/cli.py +0 -0
  14. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython/py.typed +0 -0
  15. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython.egg-info/SOURCES.txt +0 -0
  16. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython.egg-info/dependency_links.txt +0 -0
  17. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython.egg-info/entry_points.txt +0 -0
  18. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython.egg-info/requires.txt +0 -0
  19. {ballpython-2.0.3 → ballpython-2.0.4}/ballpython.egg-info/top_level.txt +0 -0
  20. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/__main__.py +0 -0
  21. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/baseline.py +0 -0
  22. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/cache.py +0 -0
  23. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/complexity_analyzer.py +0 -0
  24. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/config.py +0 -0
  25. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/dead_code_detector.py +0 -0
  26. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/dependency_auditor.py +0 -0
  27. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/discovery.py +0 -0
  28. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/explanations.py +0 -0
  29. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/import_resolver.py +0 -0
  30. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/linter_formatter.py +0 -0
  31. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/modernizer.py +0 -0
  32. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/pipeline.py +0 -0
  33. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/py.typed +0 -0
  34. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/security_scanner.py +0 -0
  35. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/syntax_healer.py +0 -0
  36. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/taint_engine.py +0 -0
  37. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/test_generator.py +0 -0
  38. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/type_checker.py +0 -0
  39. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/typeshed_resolver.py +0 -0
  40. {ballpython-2.0.3 → ballpython-2.0.4}/pycleaner/verifier.py +0 -0
  41. {ballpython-2.0.3 → ballpython-2.0.4}/setup.cfg +0 -0
  42. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_baseline_and_explain.py +0 -0
  43. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_cache.py +0 -0
  44. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_cli_prove.py +0 -0
  45. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_complexity_analyzer.py +0 -0
  46. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_config.py +0 -0
  47. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_dead_code_detector.py +0 -0
  48. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_dead_code_fixer.py +0 -0
  49. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_dependency_auditor.py +0 -0
  50. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_discovery.py +0 -0
  51. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_import_resolver.py +0 -0
  52. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_linter_formatter.py +0 -0
  53. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_modernizer.py +0 -0
  54. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_mutation_test_generator.py +0 -0
  55. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_pipeline.py +0 -0
  56. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_security_scanner.py +0 -0
  57. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_syntax_healer.py +0 -0
  58. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_taint_engine.py +0 -0
  59. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_test_generator.py +0 -0
  60. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_type_checker.py +0 -0
  61. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_verifier.py +0 -0
  62. {ballpython-2.0.3 → ballpython-2.0.4}/tests/test_verifier_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ballpython
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite
5
5
  Author: Developer
6
6
  License-Expression: MIT
@@ -78,7 +78,7 @@ Add `ballpython` to your `.pre-commit-config.yaml`:
78
78
  ```yaml
79
79
  repos:
80
80
  - repo: https://github.com/moonrox420/ball-python
81
- rev: v2.0.3
81
+ rev: v2.0.4
82
82
  hooks:
83
83
  - id: pycleaner
84
84
  args: ["check"]
@@ -59,7 +59,7 @@ Add `ballpython` to your `.pre-commit-config.yaml`:
59
59
  ```yaml
60
60
  repos:
61
61
  - repo: https://github.com/moonrox420/ball-python
62
- rev: v2.0.3
62
+ rev: v2.0.4
63
63
  hooks:
64
64
  - id: pycleaner
65
65
  args: ["check"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ballpython
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite
5
5
  Author: Developer
6
6
  License-Expression: MIT
@@ -78,7 +78,7 @@ Add `ballpython` to your `.pre-commit-config.yaml`:
78
78
  ```yaml
79
79
  repos:
80
80
  - repo: https://github.com/moonrox420/ball-python
81
- rev: v2.0.3
81
+ rev: v2.0.4
82
82
  hooks:
83
83
  - id: pycleaner
84
84
  args: ["check"]
@@ -18,7 +18,7 @@ from pycleaner.discovery import (
18
18
  is_protected_file,
19
19
  )
20
20
 
21
- __version__ = "2.0.3"
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
+
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ballpython"
7
- version = "2.0.3"
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
+
File without changes
File without changes