mitai-jinkendo/backend/scripts/audit_insert_placeholders.py
Lars 532e17c4cd
All checks were successful
Deploy Development / deploy (push) Successful in 1m5s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 24s
feat: add Jinkendo Foundation design principles documentation
- Introduced a new section for the Jinkendo Foundation, detailing design principles for the product family.
- Updated README files to include references to the new design principles documentation.
- Enhanced the overall documentation structure to improve navigation and accessibility of design resources.
- Ensured consistency across documentation related to the Jinkendo Foundation and its principles.
2026-07-22 11:11:07 +02:00

89 lines
2.6 KiB
Python

"""Audit: INSERT %s-Platzhalter vs. Parameter-Anzahl in backend/."""
from __future__ import annotations
import ast
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def _count_tuple_elements(node: ast.AST) -> int | None:
if isinstance(node, ast.Tuple):
return len(node.elts)
if isinstance(node, ast.List):
return len(node.elts)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
if node.func.attr in {"values", "keys"}:
return None
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
left = _count_tuple_elements(node.left)
right = _count_tuple_elements(node.right)
if left is not None and right is not None:
return left + right
if isinstance(node, ast.ListComp):
return None
return None
def audit_file(path: Path) -> list[str]:
issues: list[str] = []
try:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
except SyntaxError:
return issues
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
func = node.func
if not (
isinstance(func, ast.Attribute)
and func.attr == "execute"
and isinstance(func.value, ast.Name)
and func.value.id == "cur"
):
continue
if len(node.args) < 2:
continue
sql_node, params_node = node.args[0], node.args[1]
sql = None
if isinstance(sql_node, ast.Constant) and isinstance(sql_node.value, str):
sql = sql_node.value
elif isinstance(sql_node, ast.JoinedStr):
continue
if not sql or "INSERT" not in sql.upper():
continue
if "VALUES" not in sql.upper():
continue
ph = sql.count("%s")
param_count = _count_tuple_elements(params_node)
if param_count is None:
continue
if ph != param_count:
rel = path.relative_to(ROOT)
issues.append(
f"{rel}:{node.lineno} INSERT placeholders={ph} params={param_count}"
)
return issues
def main() -> int:
issues: list[str] = []
for path in ROOT.rglob("*.py"):
if "venv" in path.parts or "__pycache__" in path.parts:
continue
issues.extend(audit_file(path))
if issues:
print("MISMATCHES:")
for issue in sorted(issues):
print(issue)
return 1
print("OK: keine INSERT-Platzhalter-Mismatches gefunden (statisch prüfbar).")
return 0
if __name__ == "__main__":
raise SystemExit(main())