One Deck schema served two endpoints with different card-loading
strategies, leaking the difference into the type:
class Deck(DeckBase):
id: PositiveInt
cards: list[Card] | None
- get_deck -> fetch_with_cards (selectinload) -> cards populated.
- list_decks / create_deck / update_deck -> relationship is
lazy="noload", so .cards returns [] without loading.
So list_decks reported cards: [] for every deck even when the deck has
cards. The | None was also dead: noload yields [], never None.
Split the one schema into two:
class Deck(DeckBase):
"""Light deck view for lists and writes; cards are not loaded."""
id: PositiveInt
class DeckWithCards(Deck):
"""Deck detail view; cards are eager-loaded and always present."""
cards: list[Card]
- get_deck -> DeckWithCards (cards always present, non-optional)
- list_decks -> Decks (Collection[Deck]), create_deck / update_deck -> Deck
Each endpoint's return type now states exactly what it loads.
Contract change (intentional): the cards key disappears from list /
create / update responses (they never loaded cards anyway). get_deck is
unchanged.
Ports modern-python/litestar-sqlalchemy-template#30.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
One Deck schema served two endpoints with different card-loading strategies, leaking the difference into the type:
So list_decks reported cards: [] for every deck even when the deck has cards — the list contract was lying. The | None was also dead: noload yields [], never None.
Change
Split the one schema into two:
Each endpoint's return type now states exactly what it loads. The dead | None is gone. The two-shape convention is documented in CLAUDE.md (Conventions) and in docstrings on the schemas.
Contract change ⚠️
Intentional: the cards key disappears from list / create / update responses (they never loaded cards anyway). get_deck is unchanged. Any client reading cards off a list response is affected.
Tests
test_get_one_deck locks DeckWithCards (asserts cards); list/create/update tests never asserted cards, so they stay green. 19 passed, 100% coverage. ruff format --check, ruff check --no-fix, ty check all pass.
Ports modern-python/litestar-sqlalchemy-template#30.
🤖 Generated with Claude Code