diff --git a/CLAUDE.md b/CLAUDE.md index e81f023..d3f6880 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,4 +71,5 @@ Endpoints inject repositories with `FromDI(Repository)` from `modern_di_fastapi` - Type-ignore syntax is `# ty: ignore[error-code]` (this project uses `ty`, not mypy). See `app/application.py:39` for an example. - Ruff is configured with `select = ["ALL"]` and a curated ignore list in `pyproject.toml`. Don't sprinkle `# noqa`; prefer fixing or extending the project ignore list if a rule is genuinely wrong for the codebase. - Routes convert ORM objects to schemas explicitly, never via `typing.cast`. Single objects: `schemas.X.model_validate(instance)`. Collections: `schemas.Xs.from_models(objects)` (the `Collection[T]` seam in `app/schemas.py`). Both rely on `from_attributes=True`. +- Deck responses are deliberately two-shaped: `list_decks`/`create_deck`/`update_deck` return the light `schemas.Deck` (no `cards`), while `get_deck` returns `schemas.DeckWithCards`. The split mirrors loading — lists/writes use `noload` (no cards query), detail uses `selectinload` via `fetch_with_cards` — so the type states exactly what each endpoint loads. - Line length is 120. diff --git a/app/api/decks.py b/app/api/decks.py index 6b80f46..d17c154 100644 --- a/app/api/decks.py +++ b/app/api/decks.py @@ -22,9 +22,9 @@ async def list_decks( async def get_deck( deck_id: int, decks_repository: DecksRepository = FromDI(DecksRepository), -) -> schemas.Deck: +) -> schemas.DeckWithCards: instance = await decks_repository.fetch_with_cards(deck_id) - return schemas.Deck.model_validate(instance) + return schemas.DeckWithCards.model_validate(instance) @ROUTER.put("/decks/{deck_id}/") diff --git a/app/schemas.py b/app/schemas.py index a4b2d5a..2dc630d 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -49,8 +49,15 @@ class DeckCreate(DeckBase): class Deck(DeckBase): + """Light deck view for lists and writes; cards are not loaded.""" + id: PositiveInt - cards: list[Card] | None + + +class DeckWithCards(Deck): + """Deck detail view; cards are eager-loaded (selectinload) and always present.""" + + cards: list[Card] class Decks(Collection[Deck]):