Skip to content

Commit 9c49356

Browse files
lesnik512claude
andauthored
refactor: deepen Deck and Card data-access modules (#51)
* refactor: deepen Deck and Card data-access modules The DecksRepository / CardsRepository were empty subclasses of advanced-alchemy's service, so their interface was the entire generic surface and the query knowledge — filters, eager-load options, schema→model mapping — leaked into every HTTP handler. Deepen both modules with a small set of named methods that absorb that knowledge behind the interface: - DecksRepository.fetch_with_cards — id filter + selectinload(cards) - CardsRepository.list_for_deck — deck_id filter - CardsRepository.add_cards — model_dump + deck_id + Card(...) build - CardsRepository.upsert_cards — same mapping, upsert semantics Handlers now name the intent and hand over the request schemas they already hold. Additive only: advanced-alchemy's generic surface stays visible so the template still demonstrates the library. Behaviour is unchanged — fetch_with_cards returns Deck | None and the existing 404 path is byte-for-byte the same. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * update packages --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent af2be40 commit 9c49356

3 files changed

Lines changed: 333 additions & 317 deletions

File tree

‎app/api/decks.py‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import fastapi
44
from advanced_alchemy.exceptions import NotFoundError
55
from modern_di_fastapi import FromDI
6-
from sqlalchemy import orm
76
from starlette import status
87

98
from app import models, schemas
@@ -26,10 +25,7 @@ async def get_deck(
2625
deck_id: int,
2726
decks_repository: DecksRepository = FromDI(DecksRepository),
2827
) -> schemas.Deck:
29-
instance = await decks_repository.get_one_or_none(
30-
models.Deck.id == deck_id,
31-
load=[orm.selectinload(models.Deck.cards)],
32-
)
28+
instance = await decks_repository.fetch_with_cards(deck_id)
3329
if not instance:
3430
raise fastapi.HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Deck is not found")
3531

@@ -64,7 +60,7 @@ async def list_cards(
6460
deck_id: int,
6561
cards_repository: CardsRepository = FromDI(CardsRepository),
6662
) -> schemas.Cards:
67-
objects = await cards_repository.get_many(models.Card.deck_id == deck_id)
63+
objects = await cards_repository.list_for_deck(deck_id)
6864
return typing.cast("schemas.Cards", {"items": objects})
6965

7066

@@ -85,9 +81,7 @@ async def create_cards(
8581
data: list[schemas.CardCreate],
8682
cards_repository: CardsRepository = FromDI(CardsRepository),
8783
) -> schemas.Cards:
88-
objects = await cards_repository.create_many(
89-
data=[models.Card(**card.model_dump(), deck_id=deck_id) for card in data],
90-
)
84+
objects = await cards_repository.add_cards(deck_id, data)
9185
return typing.cast("schemas.Cards", {"items": objects})
9286

9387

@@ -97,7 +91,5 @@ async def update_cards(
9791
data: list[schemas.Card],
9892
cards_repository: CardsRepository = FromDI(CardsRepository),
9993
) -> schemas.Cards:
100-
objects = await cards_repository.upsert_many(
101-
data=[models.Card(**card.model_dump(exclude={"deck_id"}), deck_id=deck_id) for card in data],
102-
)
94+
objects = await cards_repository.upsert_cards(deck_id, data)
10395
return typing.cast("schemas.Cards", {"items": objects})

‎app/repositories.py‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
from typing import TYPE_CHECKING
2+
13
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
24
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
5+
from sqlalchemy import orm
6+
7+
from app import models, schemas
38

4-
from app import models
9+
10+
if TYPE_CHECKING:
11+
from collections.abc import Sequence
512

613

714
class DecksRepository(SQLAlchemyAsyncRepositoryService[models.Deck]):
@@ -10,9 +17,26 @@ class BaseRepository(SQLAlchemyAsyncRepository[models.Deck]):
1017

1118
repository_type = BaseRepository
1219

20+
async def fetch_with_cards(self, deck_id: int) -> models.Deck | None:
21+
return await self.get_one_or_none(
22+
models.Deck.id == deck_id,
23+
load=[orm.selectinload(models.Deck.cards)],
24+
)
25+
1326

1427
class CardsRepository(SQLAlchemyAsyncRepositoryService[models.Card]):
1528
class BaseRepository(SQLAlchemyAsyncRepository[models.Card]):
1629
model_type = models.Card
1730

1831
repository_type = BaseRepository
32+
33+
async def list_for_deck(self, deck_id: int) -> Sequence[models.Card]:
34+
return await self.get_many(models.Card.deck_id == deck_id)
35+
36+
async def add_cards(self, deck_id: int, cards: list[schemas.CardCreate]) -> Sequence[models.Card]:
37+
return await self.create_many([models.Card(**card.model_dump(), deck_id=deck_id) for card in cards])
38+
39+
async def upsert_cards(self, deck_id: int, cards: list[schemas.Card]) -> Sequence[models.Card]:
40+
return await self.upsert_many(
41+
[models.Card(**card.model_dump(exclude={"deck_id"}), deck_id=deck_id) for card in cards],
42+
)

0 commit comments

Comments
 (0)