# Python FastAPI with Pydantic v2 — Cursor Rules

You are an expert Python developer building APIs with FastAPI and Pydantic v2, using async patterns throughout.

## Code Style

- Use Python 3.11+ features: `match` statements, `StrEnum`, `Self` type, exception groups.
- Type-annotate every function signature — parameters and return types. Never use `Any` unless interfacing with genuinely dynamic data.
- Use `snake_case` for functions, variables, and modules. `PascalCase` for classes. `UPPER_SNAKE_CASE` for constants.
- Prefer f-strings over `format()` or `%` formatting.
- Line length: 88 characters (Black default). Use Black for formatting, Ruff for linting.
- Import order: stdlib, third-party, local. Separate with blank lines. Use `isort` profile for Black.
- Use `from __future__ import annotations` at the top of every file for forward reference support.
- Prefer `pathlib.Path` over `os.path` for filesystem operations.
- Use `dataclasses` for simple data containers without validation. Use Pydantic models when validation is needed.

## FastAPI Patterns

- Define routers in separate files, one per resource domain: `routers/users.py`, `routers/items.py`.
- Use `APIRouter` with a prefix and tags: `router = APIRouter(prefix="/users", tags=["users"])`.
- Use dependency injection for shared logic: database sessions, auth, pagination, rate limiting.
- Prefer `async def` for all route handlers. Use `def` (sync) only for CPU-bound operations that can't be easily made async.
- Return Pydantic response models explicitly: `@router.get("/users/{id}", response_model=UserOut)`.
- Use `status_code` parameter: `@router.post("/users", status_code=status.HTTP_201_CREATED)`.
- Use `HTTPException` for expected errors. Use exception handlers for unexpected errors.
- Document all endpoints with docstrings — they appear in the OpenAPI schema.

## Pydantic v2 Models

- Use Pydantic v2 syntax exclusively. Never use v1 deprecated patterns.
- Use `model_validator(mode='before')` instead of deprecated `@validator`.
- Use `field_validator` instead of `@validator` with `@classmethod`.
- Use `model_config = ConfigDict(...)` instead of inner `class Config`.
- Define separate models for input and output: `UserCreate`, `UserUpdate`, `UserOut`.
- Use `Field()` for validation constraints: `Field(min_length=1, max_length=100, description="...")`.
- Use `Annotated[int, Field(gt=0)]` pattern for reusable field types.
- Prefer `Enum` or `Literal` types for fields with fixed allowed values.
- Use `model_dump()` instead of deprecated `dict()`. Use `model_validate()` instead of `parse_obj()`.

## Async Patterns

- Use `asyncio` for I/O-bound operations: database queries, HTTP calls, file I/O.
- Use `httpx.AsyncClient` for async HTTP requests, not `requests`.
- Use `asyncio.gather()` for concurrent I/O operations when tasks are independent.
- Never use blocking I/O (e.g., `open()`, `requests.get()`) inside async functions. Use `aiofiles` or `run_in_executor`.
- For database access, use async drivers: `asyncpg` for PostgreSQL, `motor` for MongoDB, `aiosqlite` for SQLite.
- Use `async for` with async iterators/generators for streaming responses.
- Handle task cancellation gracefully with try/finally blocks.

## Dependency Injection

- Define dependencies as async functions that `yield` (for cleanup) or return values.
- Use `Depends()` in route handler signatures for dependency injection.
- Compose dependencies: a dependency can depend on other dependencies.
- Use `Annotated` types for cleaner dependency signatures:
  `CurrentUser = Annotated[User, Depends(get_current_user)]`
- For database sessions, use a dependency that yields the session and closes it after the request.
- Create a `deps.py` file in each router module for module-specific dependencies.

## Error Handling

- Raise `HTTPException` with appropriate status codes and descriptive detail messages.
- Create custom exception classes for domain-specific errors. Map them to HTTP responses with exception handlers.
- Use `@app.exception_handler(CustomError)` to centralize error response formatting.
- Return consistent error response shapes: `{"detail": "message", "code": "ERROR_CODE"}`.
- Log all 5xx errors with full context (request path, user, traceback). Never log sensitive data (passwords, tokens).
- Use `try/except` with specific exception types. Never use bare `except:`.
- For validation errors, let Pydantic/FastAPI handle them automatically — they return 422 with detailed error info.

## Database (SQLAlchemy Async)

- Use SQLAlchemy 2.0 style with `select()`, `insert()`, `update()`, `delete()` statements.
- Use `AsyncSession` from `sqlalchemy.ext.asyncio`. Never use synchronous sessions.
- Define models in `models/` directory, one file per domain entity.
- Use Alembic for migrations. Always create a migration for schema changes.
- Use repository pattern: encapsulate database queries in repository classes or functions.
- Prefer `selectinload` or `subqueryload` for eager loading relationships. Avoid N+1 queries.

## Testing

- Use `pytest` with `pytest-asyncio` for async test support.
- Use `httpx.AsyncClient` with `ASGITransport` for testing FastAPI apps without starting a server.
- Create a test database fixture that sets up and tears down the database per test session.
- Use factories (with `factory_boy` or custom functions) for creating test data.
- Test each endpoint: happy path, validation errors, auth errors, not found, edge cases.
- Place tests in a `tests/` directory mirroring the source structure.
- Name test files `test_*.py` and test functions `test_*`.

## File Structure

```
app/
  main.py            — FastAPI app instance, middleware, startup/shutdown
  config.py          — Settings with pydantic-settings (BaseSettings)
  deps.py            — Global dependencies (DB session, auth)
  routers/
    users.py         — User endpoints
    items.py         — Item endpoints
  models/
    user.py          — SQLAlchemy ORM models
    item.py
  schemas/
    user.py          — Pydantic request/response schemas
    item.py
  services/
    user_service.py  — Business logic layer
  repositories/
    user_repo.py     — Database access layer
  middleware/
    logging.py
    cors.py
  utils/
    security.py      — Password hashing, JWT
    pagination.py    — Pagination helpers
tests/
  conftest.py
  test_users.py
  test_items.py
alembic/
  versions/
```

## Security

- Use OAuth2 with JWT tokens for authentication. Use `python-jose` for JWT encoding/decoding.
- Hash passwords with `bcrypt` via `passlib`. Never store plaintext passwords.
- Validate and sanitize all input through Pydantic models — do not trust raw request data.
- Use CORS middleware with explicit allowed origins. Never use `allow_origins=["*"]` in production.
- Rate limit sensitive endpoints (login, registration, password reset).
- Use parameterized queries (SQLAlchemy handles this). Never concatenate user input into SQL strings.
- Store secrets in environment variables. Use `pydantic-settings` with `.env` files for configuration.
- Set secure headers: HSTS, X-Content-Type-Options, X-Frame-Options.

## Performance

- Use connection pooling for database connections (SQLAlchemy default with `create_async_engine`).
- Use Redis for caching frequently accessed data. Use `aioredis` for async Redis access.
- Implement pagination for all list endpoints. Default page size of 20-50 items.
- Use background tasks (`BackgroundTasks`) for non-blocking operations like sending emails.
- Profile slow endpoints with middleware that logs request duration.
- Use streaming responses (`StreamingResponse`) for large file downloads.
