Skip to content

FastAPI Project Rules

Cursor.cursorrulesCoding

FastAPI conventions for Pydantic v2 schemas, Depends-based wiring, async discipline, and a predictable router layout.

Use when: Use in FastAPI services with Pydantic v2 to keep routers thin, dependencies explicit, and blocking calls out of the event loop.

@library0 upvotes0 remixes0 comments

Get this skill

Add the skill file with the CLI, by hand, or over MCP.

Save this to your project or home directory so Cursor can load it.

npx goodprompts add python-fastapi-cursorrules
Saves to./.cursorrules
npx goodprompts add python-fastapi-cursorrules
Saves to ./.cursorrules

Skill file

.cursorrules3,356 characters
You are working on a FastAPI service with Pydantic v2, SQLAlchemy 2.0 async, and Python 3.12.
## Router layout
- One router per resource in `app/routers/<resource>.py`, created as `router = APIRouter(prefix="/items", tags=["items"])` and included in `app/main.py`. No routes defined on the app object directly.
- Path functions stay under ~15 lines: validate via types, call one service function, return a schema. Business logic lives in `app/services/`, DB access in `app/repositories/`.
- Every route declares `response_model` (or a return annotation) and explicit `status_code` for non-200 responses (`201` for creates, `204` for deletes).
- Use module structure `app/routers`, `app/services`, `app/repositories`, `app/schemas`, `app/models`, `app/core` (config, security, deps). No `helpers.py` grab-bag.
## Pydantic models
- Pydantic models are the API contract only — never return SQLAlchemy models from a route. Convert with `model_config = ConfigDict(from_attributes=True)`.
- Three schemas per resource, suffixed by role: `ItemCreate`, `ItemUpdate` (all fields optional), `ItemRead`. Never reuse `ItemCreate` as a response model.
- Constrain fields at the schema, not in handler code: `name: str = Field(min_length=1, max_length=120)`, `Annotated[int, Field(ge=1, le=100)]` for page sizes.
- Pydantic v2 idioms only: `model_validate`, `model_dump(mode="json")`, `field_validator`. Flag any `.dict()`, `.parse_obj()`, or `class Config` as legacy and rewrite.
- Settings come from one `Settings(BaseSettings)` class in `app/core/config.py`, accessed through a cached `get_settings()` dependency — no `os.environ` reads elsewhere.
## Dependency injection
- Anything a route needs (DB session, current user, settings, pagination) arrives via `Depends`, declared with `Annotated` aliases defined once in `app/core/deps.py`: `SessionDep = Annotated[AsyncSession, Depends(get_session)]`.
- `get_session` is an async generator that yields the session and commits/rolls back in `finally`. Routes and services never call `commit()` on their own session lifecycle.
- Auth is a dependency (`CurrentUser = Annotated[User, Depends(get_current_user)]`), never an `if` check inside a handler. Authorization failures raise `HTTPException(403)` inside the dependency.
- No module-level singletons constructed at import time (DB engines excepted). Construct clients in `lifespan` and expose them through dependencies so tests can override with `app.dependency_overrides`.
## Async discipline
- All path functions are `async def`. Inside them, never call blocking I/O (`requests`, `time.sleep`, sync DB drivers, `open()` on large files) — use `httpx.AsyncClient`, `asyncio.sleep`, and the async session.
- Unavoidably sync/CPU-bound work goes through `await run_in_threadpool(fn, ...)`, not inline.
- Fire independent awaits concurrently with `asyncio.gather`; sequential awaits on independent calls are a defect.
- Background work that must survive the response uses a task queue (or at minimum `BackgroundTasks`) — never a bare `asyncio.create_task` that nothing supervises.
## Errors
- Raise `HTTPException` only in routers and dependencies. Services raise domain exceptions (`ItemNotFoundError`), translated by exception handlers registered in `main.py`.
- Error responses always carry `{"detail": ...}`; never return a 200 with an error payload inside.
Comments

Comments

Sign in to commentSign in

Remixes

Loading remixes…