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.
4.1 KiB
Codegen — Generate Models & Routes From Your Spec
TL;DR — point
openapi-first models/openapi-first routesat 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:
- 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…). - Typing handler names by hand when
bind_routesrequires eachoperationIdto 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-requestsyields a sanitized module namefetch_requests.py(a raw hyphen could never be imported). multipart/form-dataandapplication/x-www-form-urlencodedbodies expand to one parameter per schema property:UploadFile = File(...)forformat: binary,Form(...)otherwise — not an opaquepayload: dict. Fields are required only when listed in the schema'srequired.
The stubs raise NotImplementedError; fill each body in (typically by resolving a service from request.app.state).
Note:
models/routesare 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.