---
trigger: glob
globs: **/*.py
---

# FastAPI

- Routers per feature in `app/<feature>/router.py` with `APIRouter(prefix="/orders", tags=["orders"])`; schemas in `schemas.py`; logic in `service.py`.
- Request and response models are Pydantic v2 (`model_config = ConfigDict(from_attributes=True)`); set `response_model` or a return annotation on every endpoint so output is filtered.
- Use `Annotated` dependencies: `SessionDep = Annotated[AsyncSession, Depends(get_session)]`, `CurrentUser = Annotated[User, Depends(get_current_user)]`.
- Async endpoints only call async libraries (async SQLAlchemy, `httpx.AsyncClient`). Blocking work goes in sync `def` endpoints or `run_in_threadpool`.
- SQLAlchemy 2.0 style `select()` with `scalars()`; `selectinload` for relations. Migrations with Alembic (`alembic revision --autogenerate -m ...`, then review).
- Errors: raise `HTTPException` with correct codes, or domain exceptions mapped by `app.exception_handler`.
- Settings via `pydantic-settings` `BaseSettings`, loaded once.
- Startup/shutdown in a `lifespan` context manager, not `@app.on_event`.
- Auth via OAuth2/JWT dependencies; never trust ids from the body for ownership.

## Done when
`ruff check . && pyright && pytest -q` pass, with endpoint tests using `httpx.AsyncClient(transport=ASGITransport(app))` and dependency overrides.
