Files
openapi-first/docs/wiki/03_use_cases/02_templates.md

6.0 KiB
Raw Permalink Blame History

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 /healthoperationId: 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 codescreate_item/delete_item take response: Response and set 201/204; get_item/update_item raise HTTPException(404) on KeyError
  • Handlers as plain callables — no FastAPI decorators, routing comes solely from the spec
  • Mock data storedata.py is 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 operationId set as crud_app, so the two are interchangeable
  • Models in the client too — the same spec drives OpenAPIClient body handling
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 unionsProcedureNotes uses oneOf + discriminator.noteType mapping; Pydantic models use Literal[...] discriminator fields
  • SSE streaming — a GET /pets/{id}/actions operation streaming text/event-stream via StreamingResponse with background task workers
  • File uploadUploadFile handler setting a multi-part body
  • CORS + middlewareadd_middleware(CORSMiddleware, ...) alongside the spec-driven setup
  • Response injection — handlers set 201/204 explicitly via injected Response

🚀 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_TEMPLATE is health_app, so openapi-first scaffold with 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.