6.0 KiB
Use Case 2: Templates — Copyable Reference Applications
openapi-first ships four runnable, copyable applications under openapi_first/templates/. They are not part of the library API — they are bundled scaffold examples you copy into your own project and build on.
🎬 1. What Templates Are
A template is a complete, self-contained OpenAPI-first service:
- A bundled directory inside
openapi_first/templates/<name>/ - Copyable verbatim — no code generation, no mutation — via the CLI
- Each one demonstrates a specific set of OpenAPI-first behaviors and FastAPI features
All templates share the same skeleton:
<name>_app/
├── __init__.py # explains the template + how to scaffold it
├── openapi.yaml # the contract (source of truth)
├── main.py # assembles OpenAPIFirstApp from the spec
├── routes.py # operationId-bound handler functions
└── data.py # in-memory data store (demo only)
📋 2. The Four Templates
2.1 health_app — minimal liveness probe
openapi-first scaffold health_app
# or into a custom directory:
openapi-first scaffold health_app my-health-service
| File | Purpose |
|---|---|
openapi.yaml |
GET /health → operationId: get_health |
routes.py |
get_health() returns {"status": "ok"} |
main.py |
OpenAPIFirstApp(openapi_path="openapi.yaml", routes_module=routes) |
Why it exists: the absolute minimal OpenAPI-first round trip — one operation, one handler, zero moving parts. The best starting point to internalize the mental model.
Smoke test:
pip install -e .
uvicorn main:app
curl http://localhost:8000/health
# → {"status": "ok"}
2.2 crud_app — dict-based CRUD
openapi-first scaffold crud_app my-crud-service
| File | Purpose |
|---|---|
openapi.yaml |
Full CRUD over /items (list/get/create/update/delete) |
routes.py |
Handlers bound via operationIds list_items, get_item, create_item, update_item, delete_item |
data.py |
In-memory dict store with auto-incrementing id |
Behaviors you learn:
- Explicit status codes —
create_item/delete_itemtakeresponse: Responseand set201/204;get_item/update_itemraiseHTTPException(404)onKeyError - Handlers as plain callables — no FastAPI decorators, routing comes solely from the spec
- Mock data store —
data.pyis a copyable in-memory store, explicitly not production-ready
2.3 model_app — Pydantic model CRUD
openapi-first scaffold model_app my-model-service
| File | Purpose |
|---|---|
openapi.yaml |
Same CRUD surface, schemas reference models |
models.py |
Pydantic Item, ItemCreate, ItemBase (request/response models) |
routes.py |
Handlers type-annotated with the models; create_item sets 201 |
data.py |
In-memory store returning real model instances |
Behaviors you learn:
- Pydantic request/response models — payloads validated and serialized via FastAPI
- Same handler contracts — identical
operationIdset ascrud_app, so the two are interchangeable - Models in the client too — the same spec drives
OpenAPIClientbody handling
2.4 vet_app — the full-featured demo
openapi-first scaffold vet_app my-vet-clinic
| File | Purpose |
|---|---|
openapi.yaml |
Five resources (parents, vets, treatments, pets, appointments) + SSE + upload + discriminated unions |
models.py |
Pydantic models incl. discriminated unions (noteType literal fields) |
routes.py |
~20 handlers across all resources, incl. pagination, filtering, photo upload, SSE streaming |
sse.py |
Server-Sent Events helper (StreamingResponse, per-pet subscriber queues, background asyncio workers) |
data.py |
Larger in-memory store (parents → vets → treatments → pets → appointments) |
main.py |
App + CORS + lifespan example |
Behaviors you learn — the advanced tier:
- Discriminated unions —
ProcedureNotesusesoneOf+discriminator.noteTypemapping; Pydantic models useLiteral[...]discriminator fields - SSE streaming — a
GET /pets/{id}/actionsoperation streamingtext/event-streamviaStreamingResponsewith background task workers - File upload —
UploadFilehandler setting a multi-part body - CORS + middleware —
add_middleware(CORSMiddleware, ...)alongside the spec-driven setup - Response injection — handlers set
201/204explicitly via injectedResponse
🚀 3. CLI Reference
# List available templates
openapi-first scaffold --list
# Copy a template into its default directory (template name, dashes)
openapi-first scaffold health_app
# Copy into a custom target directory
openapi-first scaffold crud_app my-project/crud
Protip:
DEFAULT_TEMPLATEishealth_app, soopenapi-first scaffoldwith no template name scaffolds the health app.
🧩 4. Anatomy of a Scaffolded Service
After openapi-first scaffold health_app my-health-service, your directory contains a drop-in FastAPI service:
my-health-service/
├── openapi.yaml # THE contract
├── main.py # `app = OpenAPIFirstApp(openapi_path=..., routes_module=routes)`
└── routes.py # `def get_health(): ...`
Run it:
cd my-health-service
pip install -e .
uvicorn main:app --reload
/docs, /openapi.json, and every declared route now exist — all derived from openapi.yaml.
⚠️ 5. Production Disclaimer
Templates use in-memory, non-persistent, non-concurrency-safe data stores. They are learning scaffolds — not production references. Swap in a real data layer (SQL/REDIS/object store) the moment you go beyond a demo.
See the __init__.py of each template for detailed client examples, CLI examples, and design notes.