Files
openapi-first/docs/wiki/01_overview.md

5.6 KiB
Raw Permalink Blame History

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:

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:

# routes.py
def get_health():
    """Health check operation handler."""
    return {"status": "ok"}

openapi_first resolves operationId: get_healthroutes.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

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

# 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):

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

# 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. Want to copy a runnable app? Jump to 02 Templates. Digging into internals? See Design.