FastAPI Best Practices
Full reference: /Users/henry/hermes-wiki/concepts/fastapi-best-practices.md (435 lines, sourced from tiangolo/full-stack-fastapi-template + zhanymkanov/fastapi-best-practices).
Trigger
Scaffolding or refactoring a FastAPI service: project layout, DI, async/sync, Pydantic v2, deployment, or security.
Core Rules (from wiki TL;DR)
- Prefer
async deffor I/O-bound routes; never block the event loop withtime.sleepor sync SDKs inside async handlers. - Use Pydantic v2 with a custom base model,
model_config, andfield_validatorfor all request/response schemas. - Organize code by domain inside
src/, not by file type; keep routers, schemas, models, services, and dependencies colocated per module. - Use FastAPI's dependency injection (
Depends) for reusable request validation, auth, and DB lookups—dependency results are cached per request. - For production, run behind a reverse proxy (Traefik/Caddy/nginx) with multiple Uvicorn workers; use a real task queue (Celery/ARQ) for anything longer than a second.
Audit Checklist (use when reviewing an existing FastAPI app)
- All routes
async def? (sync routes run in threadpool — acceptable but prefer async) - No
time.sleep/ syncrequests./urllib.requestinside async handlers? - Sync SDK calls offloaded via
run_in_threadpoolorasyncio.to_thread? - Pydantic v2 (not v1):
model_config = ConfigDict(...),field_validator,model_validate? - DB sessions scoped per-request via
Depends(get_session)? No global session? - Connection pool sized per worker (
pool_size5-10,pool_pre_ping=True)? - CORS explicit origins (never
*+ credentials)? TrustedHost for internal? - Background tasks:
BackgroundTasksonly for <1s fire-and-forget; Celery/ARQ for durable work? - Production: gunicorn + uvicorn workers behind reverse proxy? Not raw
uvicorn --workers?
Common Pitfalls
- Blocking event loop with sync code in async (symptom: all requests stall)
- Pydantic v1→v2 migration breakpoints (
BaseSettings→pydantic_settings,validator→field_validator,orm_mode→from_attributes) - SQLAlchemy async session lifecycle (never share across requests/tasks)
- OpenAPI schema traps (circular refs,
response_modeldouble instantiation)
Verification
# Import check
PYTHONPATH=src python -c "import <app>"
# Full test suite
python -m pytest tests/ -x -q
# Runtime probe (if app has one)
curl localhost:8000/healthz
Promotion History
- 2026-08-09: stub → full skill after 2 real-task successes:
- Audited anchor repo FastAPI gateway (src/anchor/server.py) against checklist
- Fixed verify_runtime sync-blocking in async lifespan (src/anchor/lifespan.py:280) via
run_in_threadpool— commit 9d3d4d9