"""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())