#!/usr/bin/env python3 """Validate that every API router endpoint has an explicit `responses={}` dict. This script runs in CI to ensure no endpoint is merged without OpenAPI response documentation. An endpoint without `responses={}` makes status-code branching impossible for frontend clients. Exit codes: 0 — all endpoints documented 1 — one or more endpoints missing responses={} """ from __future__ import annotations import ast import sys from pathlib import Path ROUTES = {"get", "post", "put", "delete", "patch", "options", "head"} ROUTER_DIR = Path(__file__).parent / "app" / "routers" class EndpointVisitor(ast.NodeVisitor): """Walk router files and collect endpoints lacking `responses={}`.""" def __init__(self) -> None: self.errors: list[str] = [] self._current_path = "" def visit_FunctionDef(self, node: ast.FunctionDef) -> None: for decorator in node.decorator_list: if self._is_router_decorator(decorator): self._check_decorator(decorator, node) self.generic_visit(node) def _is_router_decorator(self, node: ast.AST) -> bool: match node: case ast.Name(): return node.id in ROUTES case ast.Attribute(): return node.attr in ROUTES return False def _check_decorator(self, decorator: ast.AST, node: ast.FunctionDef) -> None: found_responses = False for child in ast.walk(decorator): if isinstance(child, ast.keyword) and child.arg == "responses": found_responses = True break if not found_responses: lineno = node.lineno self.errors.append( f"{self._current_path}:{lineno} — " f"endpoint in {node.name}() lacks `responses={{}}`" ) def check_file(path: Path) -> list[str]: """Return list of errors for one router file.""" source = path.read_text() tree = ast.parse(source, filename=str(path)) visitor = EndpointVisitor() visitor._current_path = str(path) visitor.visit(tree) return visitor.errors def main() -> int: errors: list[str] = [] for py_file in sorted(ROUTER_DIR.glob("*.py")): if py_file.name.startswith("_"): continue errors.extend(check_file(py_file)) if errors: print("ERRORS: Endpoints missing `responses={}`:") for e in errors: print(f" {e}") print(f"\n{len(errors)} endpoint(s) lack response documentation.") return 1 print("OK: all router endpoints have `responses={}`") return 0 if __name__ == "__main__": sys.exit(main())