""" fastapi_openapi_first.binder ============================ OpenAPI-driven route binding for FastAPI. This module is responsible for translating an OpenAPI 3.x specification into concrete FastAPI routes. It enforces a strict one-to-one mapping between OpenAPI operations and Python handler functions using operationId. Core responsibility ------------------- - Read path + method definitions from an OpenAPI specification - Resolve each operationId to a Python callable - Register routes with FastAPI using APIRoute - Fail fast when contract violations are detected Design constraints ------------------ - All routes MUST be declared in the OpenAPI specification. - All OpenAPI operations MUST define an operationId. - Every operationId MUST resolve to a handler function. - Handlers are plain Python callables (no decorators required). - No implicit route creation or inference is allowed. This module intentionally does NOT: ------------------------------- - Perform request or response validation - Generate Pydantic models - Modify FastAPI dependency injection - Interpret OpenAPI semantics beyond routing metadata Those concerns belong to other layers or tooling. """ from fastapi.routing import APIRoute from .errors import MissingOperationHandler def bind_routes(app, spec: dict, routes_module) -> None: """ Bind OpenAPI operations to FastAPI routes. Iterates through the OpenAPI specification paths and methods, resolves each operationId to a handler function, and registers a corresponding APIRoute on the FastAPI application. Parameters ---------- app : fastapi.FastAPI The FastAPI application instance to which routes will be added. spec : dict Parsed OpenAPI 3.x specification dictionary. routes_module : module Python module containing handler functions. Each handler's name MUST exactly match an OpenAPI operationId. Raises ------ MissingOperationHandler If an operationId is missing from the spec or if no corresponding handler function exists in the routes module. Behavior guarantees ------------------- - Route registration is deterministic and spec-driven. - No route decorators are required or supported. - Handler resolution errors surface at application startup. """ paths = spec.get("paths", {}) for path, methods in paths.items(): for http_method, operation in methods.items(): operation_id = operation.get("operationId") if not operation_id: raise MissingOperationHandler( path=path, method=http_method, ) try: endpoint = getattr(routes_module, operation_id) except AttributeError: raise MissingOperationHandler( path=path, method=http_method, operation_id=operation_id, ) route = APIRoute( path=path, endpoint=endpoint, methods=[http_method.upper()], name=operation_id, ) app.router.routes.append(route)