Skip to content

Commit 19ca197

Browse files
lesnik512claude
andauthored
refactor: move the Card HTTP surface into its own module (#56)
The four card handlers lived in app/api/decks.py, scattering the Card concept across model / schema / repository / a misnamed handler file. To read the Card HTTP surface you had to open decks.py. Move all four card handlers into app/api/cards.py with its own ROUTER. The deck-nested /decks/{deck_id}/cards/ routes move too -- they operate on Card rows (CardsRepository, return schemas.Cards); the /decks prefix is just scoping, not ownership. Handlers keep their full paths, so the URL tree is unchanged. include_routers now registers both decks.ROUTER and cards.ROUTER; each mounts at /api and resolves to distinct paths -- no collision. Ports modern-python/litestar-sqlalchemy-template#31 to FastAPI. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 976b2fc commit 19ca197

4 files changed

Lines changed: 54 additions & 43 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ CI (`.github/workflows/main.yml`) runs `ruff format --check`, `ruff check --no-f
3333
1. Creates a `modern_di.Container` with the `Dependencies` group from `app/ioc.py`.
3434
2. Builds a `FastAPIBootstrapper` from `settings.api_bootstrapper_config`, injecting SQLAlchemy + asyncpg OpenTelemetry instrumentors.
3535
3. `modern_di_fastapi.setup_di(app, container)` wires DI scopes onto the FastAPI app.
36-
4. Includes `app.api.decks.ROUTER` under `/api`.
36+
4. Includes one `ROUTER` per resource (`app.api.decks.ROUTER`, `app.api.cards.ROUTER`) under `/api` via `include_routers`. Add a new resource by creating `app/api/<name>.py` with its own `APIRouter`-based `ROUTER` and registering it there.
3737
5. Registers `DuplicateKeyError` → 422 handler from `app/exceptions.py`.
3838

3939
### DI scopes (modern-di)

app/api/cards.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import typing
2+
3+
import fastapi
4+
from modern_di_fastapi import FromDI
5+
6+
from app import models, schemas
7+
from app.repositories import CardsRepository
8+
9+
10+
ROUTER: typing.Final = fastapi.APIRouter()
11+
12+
13+
@ROUTER.get("/decks/{deck_id}/cards/")
14+
async def list_cards(
15+
deck_id: int,
16+
cards_repository: CardsRepository = FromDI(CardsRepository),
17+
) -> schemas.Cards:
18+
objects = await cards_repository.list_for_deck(deck_id)
19+
return schemas.Cards.from_models(objects)
20+
21+
22+
@ROUTER.get("/cards/{card_id}/")
23+
async def get_card(
24+
card_id: int,
25+
cards_repository: CardsRepository = FromDI(CardsRepository),
26+
) -> schemas.Card:
27+
instance = await cards_repository.get_one(models.Card.id == card_id)
28+
return schemas.Card.model_validate(instance)
29+
30+
31+
@ROUTER.post("/decks/{deck_id}/cards/")
32+
async def create_cards(
33+
deck_id: int,
34+
data: list[schemas.CardCreate],
35+
cards_repository: CardsRepository = FromDI(CardsRepository),
36+
) -> schemas.Cards:
37+
objects = await cards_repository.add_cards(deck_id, data)
38+
return schemas.Cards.from_models(objects)
39+
40+
41+
@ROUTER.put("/decks/{deck_id}/cards/")
42+
async def update_cards(
43+
deck_id: int,
44+
data: list[schemas.Card],
45+
cards_repository: CardsRepository = FromDI(CardsRepository),
46+
) -> schemas.Cards:
47+
objects = await cards_repository.upsert_cards(deck_id, data)
48+
return schemas.Cards.from_models(objects)

app/api/decks.py

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import fastapi
44
from modern_di_fastapi import FromDI
55

6-
from app import models, schemas
7-
from app.repositories import CardsRepository, DecksRepository
6+
from app import schemas
7+
from app.repositories import DecksRepository
88

99

1010
ROUTER: typing.Final = fastapi.APIRouter()
@@ -44,41 +44,3 @@ async def create_deck(
4444
) -> schemas.Deck:
4545
instance = await decks_repository.create(data.model_dump())
4646
return schemas.Deck.model_validate(instance)
47-
48-
49-
@ROUTER.get("/decks/{deck_id}/cards/")
50-
async def list_cards(
51-
deck_id: int,
52-
cards_repository: CardsRepository = FromDI(CardsRepository),
53-
) -> schemas.Cards:
54-
objects = await cards_repository.list_for_deck(deck_id)
55-
return schemas.Cards.from_models(objects)
56-
57-
58-
@ROUTER.get("/cards/{card_id}/")
59-
async def get_card(
60-
card_id: int,
61-
cards_repository: CardsRepository = FromDI(CardsRepository),
62-
) -> schemas.Card:
63-
instance = await cards_repository.get_one(models.Card.id == card_id)
64-
return schemas.Card.model_validate(instance)
65-
66-
67-
@ROUTER.post("/decks/{deck_id}/cards/")
68-
async def create_cards(
69-
deck_id: int,
70-
data: list[schemas.CardCreate],
71-
cards_repository: CardsRepository = FromDI(CardsRepository),
72-
) -> schemas.Cards:
73-
objects = await cards_repository.add_cards(deck_id, data)
74-
return schemas.Cards.from_models(objects)
75-
76-
77-
@ROUTER.put("/decks/{deck_id}/cards/")
78-
async def update_cards(
79-
deck_id: int,
80-
data: list[schemas.Card],
81-
cards_repository: CardsRepository = FromDI(CardsRepository),
82-
) -> schemas.Cards:
83-
objects = await cards_repository.upsert_cards(deck_id, data)
84-
return schemas.Cards.from_models(objects)

app/application.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
1010

1111
from app import exceptions, ioc
12-
from app.api.decks import ROUTER
12+
from app.api import cards, decks
1313
from app.settings import settings
1414

1515

@@ -18,7 +18,8 @@
1818

1919

2020
def include_routers(app: fastapi.FastAPI) -> None:
21-
app.include_router(ROUTER, prefix="/api")
21+
app.include_router(decks.ROUTER, prefix="/api")
22+
app.include_router(cards.ROUTER, prefix="/api")
2223

2324

2425
def build_app() -> fastapi.FastAPI:

0 commit comments

Comments
 (0)