From fastapi-consistency
Write, review, refactor, or debug FastAPI code (endpoints, dependencies, Pydantic request/response models, routers, middleware, async handlers) using one canonical, modern idiom set. Use this skill whenever code builds or fixes FastAPI services, migrates off deprecated patterns (@app.on_event, Pydantic v1 models), or when the user hits an API that freezes under load (blocking I/O in async def), "on_event is deprecated" warnings, response models leaking internal fields, dependency override confusion in tests, or asks async def vs def or Depends with Annotated. Trigger it even when the user just says "add an endpoint for X" or "build a REST API in Python" with FastAPI in the stack — without saying the words "FastAPI idioms."
How this skill is triggered — by the user, by Claude, or both
Slash command
/fastapi-consistency:fastapi-consistencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
FastAPI is stable, but training data mixes three eras: default-value parameter style
FastAPI is stable, but training data mixes three eras: default-value parameter style
(q: str = Query(None)) vs Annotated, Pydantic v1 vs v2 models, and the deprecated
@app.on_event lifecycle vs lifespan. The most expensive generated bug is invisible:
blocking I/O inside async def, which freezes the whole event loop under load.
This skill pins the modern idiom set — FastAPI 0.100+ with Pydantic v2.
| Always | Never | Why |
|---|---|---|
| `q: Annotated[str | None, Query(max_length=50)] = None` | q: str = Query(None, max_length=50) |
db: Annotated[Session, Depends(get_db)] (alias the common ones: DbDep = Annotated[...]) | repeating = Depends(get_db) defaults | Same reason; aliases remove the boilerplate entirely. |
async def only when everything awaited inside is async | async def + requests/blocking DB calls | Blocking the event loop stalls every request; plain def runs in the threadpool and is the right tool for sync libraries. |
@asynccontextmanager lifespan passed to FastAPI(lifespan=...) | @app.on_event("startup"/"shutdown") | on_event is deprecated; lifespan pairs setup/teardown in one place. |
resource dependencies with yield: open → yield → close | opening sessions per-handler ad hoc | The dependency owns the lifecycle, including on exceptions, and is testable/overridable. |
separate Pydantic models per direction (UserCreate, UserOut) + response_model/return annotation | returning ORM objects or one do-everything model | Response models filter fields — the difference between "hashed_password leaked" and not. |
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=...) | returning error dicts with 200, or bare ints | Status constants + exceptions keep OpenAPI and behavior honest. |
APIRouter(prefix="/orders", tags=["orders"]) per resource module | one giant main.py of routes | Routers are the unit of organization and reuse. |
settings via pydantic-settings BaseSettings + a cached dependency | os.environ[...] scattered at import time | Typed, validated, overridable in tests. |
TestClient/httpx AsyncClient + app.dependency_overrides in tests | monkeypatching internals of handlers | Overrides are the supported seam — swap the DB dependency, not the function body. |
House style:
from contextlib import asynccontextmanager
from typing import Annotated
from fastapi import APIRouter, Depends, FastAPI, HTTPException, status
from pydantic import BaseModel
from sqlalchemy.orm import Session
@asynccontextmanager
async def lifespan(app: FastAPI):
init_pool()
yield
close_pool()
app = FastAPI(lifespan=lifespan)
router = APIRouter(prefix="/users", tags=["users"])
def get_db():
with Session(engine) as session:
yield session
DbDep = Annotated[Session, Depends(get_db)]
class UserCreate(BaseModel):
email: str
password: str
class UserOut(BaseModel):
id: int
email: str
model_config = {"from_attributes": True} # ORM objects → model
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(payload: UserCreate, db: DbDep): # sync DB → plain def
if get_by_email(db, payload.email):
raise HTTPException(status.HTTP_409_CONFLICT, detail="email taken")
return insert_user(db, payload)
app.include_router(router)
async def + a blocking call (requests,
psycopg2, time.sleep, heavy CPU) blocks the event loop — all requests stall, but
only under concurrency, so tests pass and production melts. Plain def handlers run
in a threadpool (bounded, ~40 threads) — fine for sync libraries. Fully async stacks
(httpx, asyncpg, SQLAlchemy async) earn async def.Annotated[..., Body()] —
otherwise it silently becomes a required query param (422s confuse clients).response_model vs return annotation: either works (annotation preferred in
modern code); without any, the raw object serializes — leaking fields the moment
someone returns an ORM entity.use_cache=False when you genuinely need two instances. Across requests
there is no caching — don't put per-process state in dependencies; that's lifespan's
job (or a module singleton).yield-dependency teardown order is reverse of setup, after the response is
sent — code after yield cannot change the response; use exception handlers for
error mapping, not dependency teardown.app.add_middleware(CORSMiddleware, ...) with explicit
origins; wildcards + credentials don't combine — browsers reject it silently./users/me must be declared before
/users/{user_id} or "me" parses as an ID.Target FastAPI 0.100+ (Pydantic v2 era; current 0.11x). Deprecated-but-generated:
@app.on_event → lifespan; Pydantic v1 idioms inside FastAPI models (see the
pydantic-consistency skill — orm_mode is from_attributes, .dict() is
model_dump()); regex= param → pattern=. The Annotated style is the documented
default since 0.95.
def vs async def by the libraries inside; declare
params via Annotated; pick request/response models and status codes explicitly.Annotated[User, Depends(current_user)], pagination params as a dependency class).HTTPExceptions (or registered exception handlers) at the
edge — handlers stay thin.async def, on_event, mixed param styles,
ORM objects returned bare, missing response models, env access at import time,
route-order shadowing.For the async decision tree, dependency patterns (auth, pagination, sub-dependencies),
testing recipes, and error-handling architecture, read
references/fastapi-patterns.md.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub guidogl/fastapi-consistency --plugin fastapi-consistency