Skip to content

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

1
2
3
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
--template-style Emit async def handlers with a JSON body parameter named body (the standards-template convention) plus a service-resolution hint, instead of sync def handlers taking payload

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).

Handler shapes

Both shapes bind equally β€” the router resolves the handler by operationId and introspects the FastAPI signature, so it never inspects the request body:

  • Default (sync stub): def create_item(payload: ItemInput, response: Response): β€” the body parameter is payload.
  • --template-style: async def create_item(request: Request, body: ItemInput, response: Response): β€” matches the standards template (async def, body parameter, a request.app.state.db_resources["<resource>_svc"] hint).

Use --template-style when generating against the standards template so the scaffold matches the hand-written handlers you will keep.

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.