# Overview โ€” The OpenAPI-First Mental Model `openapi-first` inverts the usual FastAPI workflow. Instead of decorating routes in code and letting FastAPI *invent* an OpenAPI document for you, you write **one OpenAPI document first** and let the library assemble both the application **and** the client from it. The spec is the contract; code is the implementation. --- ## ๐Ÿง  1. The Mental Model ### 1.1 One source of truth Your OpenAPI document (`openapi.yaml` or `openapi.json`) is the **single authoritative contract**: ```yaml paths: /health: get: operationId: get_health responses: "200": description: OK content: application/json: schema: type: object ``` Every route, method, parameter, schema, and security requirement lives **here** โ€” and only here. Code never declares routes. ### 1.2 `operationId` is the binding key The only bridge between the spec and your Python code is the `operationId`. Each operation maps, **by name**, to exactly one plain callable: ```python # routes.py def get_health(): """Health check operation handler.""" return {"status": "ok"} ``` `openapi_first` resolves `operationId: get_health` โ†’ `routes.get_health` and registers the route. **No decorators, no `@app.get`, no routing metadata in code.** Guarantees this binding provides: - ๐Ÿšซ No undocumented route can exist โ€” every route *must* be in the spec - ๐Ÿšซ No spec operation can go unhandled โ€” every `operationId` *must* resolve at startup - ๐Ÿ”’ No auth can be bypassed โ€” security is injected from `securitySchemes` + per-operation `security`, spec-driven - ๐Ÿงฑ No drift possible โ€” the server and client are built from the *same* document ### 1.3 Fail-fast by design Contract violations are detected **at application startup** (or client construction), never silently at request time: | What goes wrong | When it fails | |------------------------------------------|----------------------| | Invalid / unloadable spec | `load_openapi` at startup | | Spec fails OpenAPI 3.x validation | `load_openapi` at startup | | `operationId` missing a handler | `bind_routes` at startup | | Operation declared but no `operationId` | `bind_routes` at startup | | Missing / duplicate `operationId` in client | `OpenAPIClient(...)` construction | --- ## โš™๏ธ 2. What It Looks Like ### 2.1 Scaffold an application ```bash openapi-first scaffold health_app my-health-service openapi-first scaffold --list ``` `scaffold` copies a bundled template (verbatim โ€” no code generation, no mutation) into a directory of your choice. ### 2.2 Bootstrap the server ```python # main.py from openapi_first.app import OpenAPIFirstApp import routes app = OpenAPIFirstApp( openapi_path="openapi.yaml", routes_module=routes, title="My Service", ) ``` Run with your FastAPI-compatible server (ASGI): ```bash uvicorn main:app --reload ``` FastAPI itself drives the server; every route, response model, and security dependency comes from the spec. `/openapi.json` and Swagger UI always reflect the spec, byte-for-byte. ### 2.3 Talk to it with the client ```python # client-side from openapi_first.loader import load_openapi from openapi_first.client import OpenAPIClient spec = load_openapi("openapi.yaml") client = OpenAPIClient(spec) response = client.get_health() # operationId-driven call print(response.status_code) # 200 print(response.json()) # {"status": "ok"} ``` `OpenAPIClient` builds one callable per `operationId` from the *same* spec, so the client can never drift from the server. --- ## ๐Ÿงฉ 3. Where Things Live | Concern | Module | |------------------------|---------------------| | Load + validate spec | `openapi_first.loader` | | Boot FastAPI app | `openapi_first.app` | | Bind routes by opId | `openapi_first.binder` | | HTTP client | `openapi_first.client` | | Error hierarchy | `openapi_first.errors` | | Security dependencies | `openapi_first.security` | | Pydantic model codegen | `openapi_first.codegen` (uses `datamodel_code_generator`) | | Route stub codegen | `openapi_first.codegen_routes` | | Models / routes CLI | `openapi_first.cli` | | Bundled templates | `openapi_first.templates` | Jurisdictions: - `loader`, `app`, `binder`, `client`, `errors`, `security` are the **library API surface** โ€” stable, tested, documented. - `templates` are **copyable scaffolds** โ€” not part of the library API; excluded from lint/format/type gates, never imported at runtime. --- ## ๐Ÿ“„ 4. The Non-Goals `openapi-first` deliberately does **not**: - Parse decorators to generate an OpenAPI schema (that's default FastAPI behavior โ€” the inverse) - Generate models from code at runtime (only at **build time** via CLI, from spec โ†’ Pydantic) - Validate request/response bodies against the spec at runtime (contract is enforced at startup / client construction; FastAPI + Pydantic handle runtime coercion) - Invent routing from path conventions โ€” `operationId` binding only - Ship a production feature set in the bundled templates (they're demos/scaffolds: in-memory stores, no concurrency, no auth configured) --- ## ๐Ÿงญ 5. Path Forward New here? Start with [01 โ€“ Quickstart](03_use_cases/01_quickstart.md). Want to copy a runnable app? Jump to [02 โ€“ Templates](03_use_cases/02_templates.md). Digging into internals? See [Design](04_design.md). --- ## Related - [01 โ€“ Quickstart](03_use_cases/01_quickstart.md) ยท [02 โ€“ Components](02_components.md) ยท [01 โ€“ Overview](01_overview.md)