ballpython 2.0.4__py3-none-any.whl → 2.0.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ballpython
3
- Version: 2.0.4
3
+ Version: 2.0.5
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.4
81
+ rev: v2.0.5
82
82
  hooks:
83
83
  - id: pycleaner
84
84
  args: ["check"]
@@ -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=AbIZyqCYVTi-fjqPCgWZmTeSth_8ma-8sYbl9SghNnQ,2894
5
+ pycleaner/__init__.py,sha256=G8pQ2M9iH32OK4bKf5Koao554EdGiiHue5rdZ5AS7sM,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=h7WSPYf40X-PBlJVOsJL-KaknDaOcWCMhcPwXkUvlbA,21986
21
+ pycleaner/security_scanner.py,sha256=cWf4cAGQrgwsGslSQDfZv0XUN3sWvlcOIQaogpmv6C0,23422
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=-W24zqvug8eUkkwqla5oh7rFOqQR-WI4TohQhbXGYoo,3472
29
29
  pycleaner/frameworks/plugins.py,sha256=sMhP9mZ82sx10OI3opgXIEPXNAWFZRe2I__6yh2Uq_E,11453
30
- ballpython-2.0.4.dist-info/METADATA,sha256=vIeFxyxog9s1kF_UlbDp4yJg-UCiSho2HQe3qhC4odE,3466
31
- ballpython-2.0.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
32
- ballpython-2.0.4.dist-info/entry_points.txt,sha256=6ux-5UxoVSmlK5p9M-GDVt24sCdwTQq9wgRDkpMIYU8,81
33
- ballpython-2.0.4.dist-info/top_level.txt,sha256=u6lmy8RWZ_saxCZAQTL7EijB4sunegOiVyk5fHjpwLk,21
34
- ballpython-2.0.4.dist-info/RECORD,,
30
+ ballpython-2.0.5.dist-info/METADATA,sha256=LqjBIVBjanDL5rdXJ7gES5vF4ezSqbG2Y1SKMG6rXx4,3466
31
+ ballpython-2.0.5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
32
+ ballpython-2.0.5.dist-info/entry_points.txt,sha256=6ux-5UxoVSmlK5p9M-GDVt24sCdwTQq9wgRDkpMIYU8,81
33
+ ballpython-2.0.5.dist-info/top_level.txt,sha256=u6lmy8RWZ_saxCZAQTL7EijB4sunegOiVyk5fHjpwLk,21
34
+ ballpython-2.0.5.dist-info/RECORD,,
pycleaner/__init__.py CHANGED
@@ -18,7 +18,7 @@ from pycleaner.discovery import (
18
18
  is_protected_file,
19
19
  )
20
20
 
21
- __version__ = "2.0.4"
21
+ __version__ = "2.0.5"
22
22
  __all__ = [
23
23
  "BaselineFingerprint",
24
24
  "BaselineManager",
@@ -190,6 +190,24 @@ class _DangerousCallDetector(ast.NodeVisitor):
190
190
  self.filepath = filepath
191
191
  self.source_lines = source_lines
192
192
  self.findings: list[SecurityFinding] = []
193
+ self._is_test_module: bool = False
194
+
195
+ def check_tree(self, tree: ast.AST) -> None:
196
+ """Inspect imports to determine if module is a test file."""
197
+ for node in ast.walk(tree):
198
+ if isinstance(node, ast.Import):
199
+ for alias in node.names:
200
+ if alias.name.split(".")[0] in ("pytest", "unittest", "mock"):
201
+ self._is_test_module = True
202
+ return
203
+ elif isinstance(node, ast.ImportFrom):
204
+ if node.module and node.module.split(".")[0] in (
205
+ "pytest",
206
+ "unittest",
207
+ "mock",
208
+ ):
209
+ self._is_test_module = True
210
+ return
193
211
 
194
212
  def _get_snippet(self, lineno: int) -> str:
195
213
  if 1 <= lineno <= len(self.source_lines):
@@ -245,8 +263,11 @@ class _DangerousCallDetector(ast.NodeVisitor):
245
263
  parts = [p.lower() for p in Path(filepath).parts[:-1]]
246
264
  return any(p in ("tests", "test", "testing") for p in parts)
247
265
 
266
+ def _is_test(self) -> bool:
267
+ return self._is_test_module or self._is_test_path(self.filepath)
268
+
248
269
  def visit_Assert(self, node: ast.Assert) -> None:
249
- if self._is_test_path(self.filepath):
270
+ if self._is_test():
250
271
  return
251
272
  self._add_finding(
252
273
  SecurityFinding(
@@ -429,7 +450,20 @@ class _DangerousCallDetector(ast.NodeVisitor):
429
450
  if isinstance(arg, ast.BinOp) and isinstance(arg.op, ast.Add):
430
451
  return True
431
452
  if isinstance(arg, ast.Call):
432
- return isinstance(arg.func, ast.Attribute) and arg.func.attr == "format"
453
+ if isinstance(arg.func, ast.Attribute) and arg.func.attr == "format":
454
+ # Check for psycopg safe sql.SQL(...).format(...) query composition
455
+ val = arg.func.value
456
+ if isinstance(val, ast.Call):
457
+ func = val.func
458
+ func_id = ""
459
+ if isinstance(func, ast.Name):
460
+ func_id = func.id
461
+ elif isinstance(func, ast.Attribute):
462
+ func_id = func.attr
463
+ if func_id == "SQL":
464
+ return False
465
+ return True
466
+ return False
433
467
  if isinstance(arg, ast.BinOp) and isinstance(arg.op, ast.Mod):
434
468
  return isinstance(arg.left, ast.Constant) and isinstance(
435
469
  arg.left.value, str
@@ -483,6 +517,7 @@ class SecurityScanner:
483
517
  tree = ast.parse(source, filename=filename)
484
518
  source_lines = source.splitlines()
485
519
  detector = _DangerousCallDetector(filename, source_lines)
520
+ detector.check_tree(tree)
486
521
  detector.visit(tree)
487
522
  findings.extend(detector.findings)
488
523
  except SyntaxError: