Files
openapi-first/docs/wiki/03_use_cases/04_codegen.md
Vishesh 'ironeagle' Bangotra 621f10409e chore(release): 0.0.7
Squash-merge lib-standardisation: adopt repo standards across packaging,
tooling, docs, and CI.

- standardize pyproject.toml (canonical packaging, lint config, extras, GSDFC pydoclint)
- add py.typed marker, modernize type hints and Google-style docstrings
- nest generated docs under docs/lib and docs/mcp; add hand-written wiki
- adopt standard Type A PyPI CI pipeline (tag==version guard + quality gate)
- promote generated CHANGELOG.md; bump version to 0.0.7
2026-10-17 02:16:49 +05:30

96 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```bash
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](../04_design.md) and [06 – Error Handling](../06_error_handling.md).
> **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
```bash
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](../02_components.md)) 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](02_templates.md)) 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.
---
## 🧩 Related
- [01 – Overview](../01_overview.md) · [02 – Templates](02_templates.md) · [03 – Client](03_client.md) · [04 – Design](../04_design.md) · [07 – Testing](../07_testing.md)