# Copilot instructions: FastAPI + SQLAlchemy 2.0 + Pydantic v2 + pytest This repository is a Python web API. Follow these conventions when generating or changing code. ## Language and tooling - Python 3.11 or newer. Use modern syntax: `X | None` instead of `Optional[X]`, built-in generics (`list[str]`), `match` where it reads better than an if-chain. - Every function and method has type hints, including return types. Code must pass the type checker and linter configured in `pyproject.toml`. - Do not add new dependencies unless the task asks for one. If one is needed, say so and explain why. ## Project layout - `app/main.py` creates the FastAPI app and includes routers. - `app/api/.py` holds routers. Keep route functions thin: parse input, call a service, return a response model. - `app/services/` holds business logic. Services take a database session and plain arguments; they do not import FastAPI. - `app/models/` holds SQLAlchemy ORM models. `app/schemas/` holds Pydantic request and response models. - `tests/` mirrors `app/`. ## FastAPI - Declare `response_model` (or a return type annotation) on every route so responses are validated and documented. - Use dependency injection (`Depends`) for the database session, the current user and settings. Use `Annotated[Session, Depends(get_session)]` style aliases. - Raise `HTTPException` with a specific status code and a short, safe `detail`. Never put stack traces, SQL or internal IDs in error details. - Use `async def` routes only when everything awaited inside is truly async. With a synchronous SQLAlchemy session, write `def` routes so FastAPI runs them in a thread pool. - Validate all input through Pydantic models or typed path and query parameters. Do not read `request.json()` by hand. ## Pydantic v2 - Use v2 APIs: `model_validate`, `model_dump`, `ConfigDict`, `field_validator`, `model_validator`. Do not use v1 names such as `parse_obj`, `dict()`, `class Config` or `@validator`. - Response models that are built from ORM objects set `model_config = ConfigDict(from_attributes=True)`. - Keep separate models for create, update and read. Update models make every field optional and the service applies only fields that were set (`model_dump(exclude_unset=True)`). - Settings come from a `pydantic-settings` `BaseSettings` class, read once and injected. Never read `os.environ` throughout the code. ## SQLAlchemy 2.0 - Use the 2.0 style: `DeclarativeBase`, `Mapped[...]` and `mapped_column(...)` for models; `select()` with `session.execute(...)` or `session.scalars(...)` for queries. Do not use the legacy `session.query(...)` API. - Never build SQL with f-strings or string concatenation. Use bound parameters or the expression API. - Avoid N+1 queries: use `selectinload` or `joinedload` when a response includes related objects. - Transactions are managed at the service boundary. Do not call `commit()` inside helper functions that other services reuse. - Schema changes go through Alembic migrations. When you change a model, generate or write the matching migration and mention it. ## Security - Every route that touches user data checks that the current user may access that specific record, not only that they are logged in. - Hash passwords with the hashing library already in the project. Never log passwords, tokens, API keys or full request bodies. - Secrets come from settings, never from source code or test fixtures. ## Tests (pytest) - Write tests for new behavior and for every bug fix (the test fails before the fix). - Use FastAPI's `TestClient` (or `httpx.AsyncClient` with an ASGI transport for async tests) and override dependencies with `app.dependency_overrides` instead of patching internals. - Use fixtures for the database session and test data. Each test starts from a clean state. - Test names describe behavior: `test_create_invoice_rejects_negative_amount`. - Cover the error paths: 401, 403, 404 and 422 cases for each new route. ## Style of changes - Keep changes focused on the task. Do not rename or reformat unrelated code. - Prefer small, pure functions in services; they are easier to test. - Add a docstring to public service functions that explains what it does and which errors it raises.