""" # Summary OpenAPI-first application bootstrap for FastAPI. This module provides `OpenAPIFirstApp`, a thin but strict abstraction that enforces OpenAPI as the single source of truth for a FastAPI service. Notes: **Core Principles:** - The OpenAPI specification (JSON or YAML) defines the entire API surface. - Every `operationId` in the OpenAPI spec must have a corresponding Python handler function. - Handlers are plain Python callables (no FastAPI decorators). - FastAPI route registration is derived exclusively from the spec. - FastAPI's autogenerated OpenAPI schema is fully overridden. **Responsibilities:** - Loads and validates an OpenAPI 3.x specification. - Dynamically binds HTTP routes to handler functions using `operationId`. - Registers routes with FastAPI at application startup. - Ensures runtime behavior matches the OpenAPI contract exactly. **Constraints:** - This module intentionally does NOT: - Generate OpenAPI specs. - Generate client code. - Introduce a new framework or lifecycle. - Alter FastAPI dependency injection semantics. """ import os import re from fastapi import FastAPI from .binder import bind_routes from .loader import load_openapi from .security import make_security_dependencies, parse_security_schemes _env_pattern = re.compile(r"\{(\w+)\}") def _resolve_env(value: str) -> str: """Replace {ENV_VAR} placeholders with values from os.environ.""" def _replace(m: re.Match) -> str: return os.environ.get(m.group(1), m.group(0)) return _env_pattern.sub(_replace, value) def _resolve_spec_env_vars(obj): """Recursively resolve {ENV_VAR} in all string values of the spec.""" if isinstance(obj, str): return _resolve_env(obj) if isinstance(obj, dict): return {k: _resolve_spec_env_vars(v) for k, v in obj.items()} if isinstance(obj, list): return [_resolve_spec_env_vars(v) for v in obj] return obj class OpenAPIFirstApp(FastAPI): """ FastAPI application enforcing OpenAPI-first design. Notes: **Responsibilities:** - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route registration with OpenAPI-driven binding. - All routes are derived from the provided OpenAPI specification, and each ``operationId`` is mapped to a Python function in the supplied routes module. - Auth dependencies are auto-injected from the spec's ``securitySchemes`` and per-operation ``security`` fields. **Guarantees:** - No route can exist without an OpenAPI declaration. - No OpenAPI operation can exist without a handler. - Swagger UI and ``/openapi.json`` always reflect the provided spec. - Handler functions remain framework-agnostic and testable. - Auth enforcement is driven entirely by the spec — no manual middleware or decorators required. Example: ```python from openapi_first import OpenAPIFirstApp import app.routes as routes app = OpenAPIFirstApp( openapi_path="app/openapi.json", routes_module=routes, title="Example Service", ) ``` """ def __init__( self, *, openapi_path: str, routes_module, **fastapi_kwargs, ): """ Initialize the application. Args: openapi_path (str): Filesystem path to the OpenAPI 3.x specification file. This specification is treated as the authoritative API contract. routes_module (module): Python module containing handler functions whose names correspond exactly to OpenAPI ``operationId`` values. **fastapi_kwargs (Any): Additional keyword arguments passed directly to ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan handlers). Raises: OpenAPIFirstError: If the OpenAPI specification is invalid, or if any declared ``operationId`` does not have a corresponding handler function. """ # Initialize FastAPI normally super().__init__(**fastapi_kwargs) # Load and validate OpenAPI specification self._openapi_spec = _resolve_spec_env_vars(load_openapi(openapi_path)) # Parse security schemes and build per-route dependencies security_schemes = parse_security_schemes(self._openapi_spec) security_deps = make_security_dependencies(self._openapi_spec, security_schemes) # Bind routes strictly from OpenAPI spec (with security deps) bind_routes( app=self, spec=self._openapi_spec, routes_module=routes_module, security_deps=security_deps, ) # Override FastAPI's OpenAPI generation self.openapi = lambda: self._openapi_spec