ballpython 2.0.4__tar.gz → 2.0.5__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.4 → ballpython-2.0.5}/PKG-INFO +2 -2
  2. {ballpython-2.0.4 → ballpython-2.0.5}/README.md +1 -1
  3. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython.egg-info/PKG-INFO +2 -2
  4. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/__init__.py +1 -1
  5. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/security_scanner.py +37 -2
  6. {ballpython-2.0.4 → ballpython-2.0.5}/pyproject.toml +1 -1
  7. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_security_scanner.py +26 -0
  8. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython/__init__.py +0 -0
  9. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython/__main__.py +0 -0
  10. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython/cli.py +0 -0
  11. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython/py.typed +0 -0
  12. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython.egg-info/SOURCES.txt +0 -0
  13. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython.egg-info/dependency_links.txt +0 -0
  14. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython.egg-info/entry_points.txt +0 -0
  15. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython.egg-info/requires.txt +0 -0
  16. {ballpython-2.0.4 → ballpython-2.0.5}/ballpython.egg-info/top_level.txt +0 -0
  17. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/__main__.py +0 -0
  18. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/baseline.py +0 -0
  19. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/cache.py +0 -0
  20. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/cli.py +0 -0
  21. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/complexity_analyzer.py +0 -0
  22. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/config.py +0 -0
  23. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/dead_code_detector.py +0 -0
  24. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/dependency_auditor.py +0 -0
  25. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/discovery.py +0 -0
  26. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/explanations.py +0 -0
  27. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/frameworks/__init__.py +0 -0
  28. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/frameworks/plugins.py +0 -0
  29. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/import_resolver.py +0 -0
  30. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/linter_formatter.py +0 -0
  31. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/modernizer.py +0 -0
  32. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/pipeline.py +0 -0
  33. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/py.typed +0 -0
  34. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/syntax_healer.py +0 -0
  35. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/taint_engine.py +0 -0
  36. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/test_generator.py +0 -0
  37. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/type_checker.py +0 -0
  38. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/typeshed_resolver.py +0 -0
  39. {ballpython-2.0.4 → ballpython-2.0.5}/pycleaner/verifier.py +0 -0
  40. {ballpython-2.0.4 → ballpython-2.0.5}/setup.cfg +0 -0
  41. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_baseline_and_explain.py +0 -0
  42. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_cache.py +0 -0
  43. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_cli.py +0 -0
  44. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_cli_prove.py +0 -0
  45. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_complexity_analyzer.py +0 -0
  46. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_config.py +0 -0
  47. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_dead_code_detector.py +0 -0
  48. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_dead_code_fixer.py +0 -0
  49. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_dependency_auditor.py +0 -0
  50. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_discovery.py +0 -0
  51. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_frameworks.py +0 -0
  52. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_import_resolver.py +0 -0
  53. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_linter_formatter.py +0 -0
  54. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_modernizer.py +0 -0
  55. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_mutation_test_generator.py +0 -0
  56. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_pipeline.py +0 -0
  57. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_syntax_healer.py +0 -0
  58. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_taint_engine.py +0 -0
  59. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_test_generator.py +0 -0
  60. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_type_checker.py +0 -0
  61. {ballpython-2.0.4 → ballpython-2.0.5}/tests/test_verifier.py +0 -0
  62. {ballpython-2.0.4 → ballpython-2.0.5}/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.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"]
@@ -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.4
62
+ rev: v2.0.5
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.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"]
@@ -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:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ballpython"
7
- version = "2.0.4"
7
+ version = "2.0.5"
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"
@@ -210,3 +210,29 @@ class TestSecurityScanner:
210
210
  def test_security_report_by_category(self) -> None:
211
211
  report = SecurityReport(findings=[])
212
212
  assert len(report.by_category("dangerous-eval")) == 0
213
+
214
+ def test_assert_suppressed_in_module_importing_pytest(self) -> None:
215
+ source = textwrap.dedent("""
216
+ import pytest
217
+
218
+ def test_feature():
219
+ assert 1 + 1 == 2
220
+ """)
221
+ scanner = SecurityScanner(severity_threshold="LOW")
222
+ report = scanner.scan_source(source, filename="test_root_file.py")
223
+ categories = {f.category for f in report.findings}
224
+ assert "assert-in-production" not in categories
225
+
226
+ def test_safe_sql_composition_not_flagged(self) -> None:
227
+ source = textwrap.dedent("""
228
+ from psycopg import sql
229
+
230
+ def get_data(conn, tbl):
231
+ with conn.cursor() as cur:
232
+ cur.execute(sql.SQL("SELECT count(*) FROM {}").format(sql.Identifier(tbl)))
233
+ """)
234
+ scanner = SecurityScanner()
235
+ report = scanner.scan_source(source, filename="query.py")
236
+ categories = {f.category for f in report.findings}
237
+ assert "sql-injection" not in categories
238
+
File without changes
File without changes
File without changes
File without changes