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
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
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-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).
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 ispayload. --template-style:async def create_item(request: Request, body: ItemInput, response: Response):β matches the standards template (async def,bodyparameter, arequest.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/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.