Files
openapi-first/docs/wiki/03_use_cases/04_codegen.md
Vishesh 'ironeagle' Bangotra 51ffb4aae9 feat: codegen routes — sanitize resource names, --inject-request/--package, expand form bodies
codegen_routes.generate_routes now sanitizes resource identifiers (fetch-requests -> fetch_requests), optionally prepends 'request: Request' (inject_request) and emits <resource>/routes.py packages with __init__.py (package). multipart/form-data and x-www-form-urlencoded bodies expand to per-property UploadFile/File/Form params instead of an opaque dict; spec paths are normalized to forward slashes to avoid Windows escape issues. Adds CLI flags --inject-request/--package, 7 focused tests, and refreshes lib/wiki/MCP docs to the real CLI.
2026-10-13 03:26:31 +05:30

4.1 KiB
Raw Blame History

Codegen — Generate Models & Routes From Your Spec

TL;DR — point openapi-first models / openapi-first routes at your spec and get Pydantic models plus operation-matching route stubs. The output is deterministic and is a starting point — not a maintained artifact.


🧭 1. Why Celebrate Codegen?

Two pain points kill OpenAPI projects:

  1. The "write the spec, then write the same thing as Pydantic models" step — exactly where server/client param types drift (your route says item_id: int, your client sends a string…).
  2. Typing handler names by hand when bind_routes requires each operationId to resolve to a function of the exact same name.

Codegen collapses both. One command, one source file, the same shapes everywhere.


🚀 2. Model Generation

openapi-first models openapi.yaml -o app/_gen/models.py

The generated module mirrors the spec's components.schemas by name:

Spec Generated
components.schemas.User class User(BaseModel)
components.schemas.Item class Item(BaseModel)
required + type from schema Pydantic Field(...) / type hints ✓

Never hand-edit those files. If the spec changes, regenerate — just like re-running a compiler after editing source.

Why constructor-time validation still applies

Codegen doesn't change the design: the generated models are plain Pydantic, and the client/server still validate against the spec at startup. Codegen is a convenience accelerator on top of the fail-fast guarantees in 04 – Design and 06 – Error Handling.

Wire model vs. domain model. Generated models are the wire contract. Keep them separate from your persisted/domain model (see the standards CRUD Template): give the wire schema an id: str, but model the Mongo document so the id is optional.


🔁 3. Route Generation

openapi-first routes openapi.yaml -o app/interfaces \
  --use-models --models-module app._gen.models \
  --inject-request --package

One file per resource (derived from the first path segment). Flags:

Flag Effect
--use-models Import request-body $ref models from --models-module
--models-module MOD Dotted module for those imports (default models)
--inject-request Prepend request: Request to every handler (DI convention)
--package Emit <resource>/routes.py packages (with __init__.py) instead of flat <resource>.py

Why bother? Because bind_routes (in 02 – Components) needs an operationId → handler exactly matching the spec. Codegen guarantees you never type a handler name wrong — it wears the same operationId the spec declares.

Resource names and extra media types

  • A path such as /fetch-requests yields a sanitized module name fetch_requests.py (a raw hyphen could never be imported).
  • multipart/form-data and application/x-www-form-urlencoded bodies expand to one parameter per schema property: UploadFile = File(...) for format: binary, Form(...) otherwise — not an opaque payload: dict. Fields are required only when listed in the schema's required.

The stubs raise NotImplementedError; fill each body in (typically by resolving a service from request.app.state).

Note: models / routes are build-time tooling. They do not run at runtime, and templates (the Bake-your-stuff section of 02 – Templates) scaffold a working server out-of-the-box for greenfield projects.


🧪 4. Idempotent Output

Generated output is deterministic w.r.t. the spec:

  • Same spec → byte-identical files (unless you hand-edit — you won't)
  • Ordering follows spec declaration order
  • No timestamps, no machine names, no hidden randomness

That determinism is what lets you git diff after a spec change and see exactly what moved.