Three handlers each re-derived the not-found → 404 policy via two
different mechanisms:
- get_deck / get_card: get_one_or_none(...) then
if not instance: raise HTTPException(404, ...)
- update_deck: try: ... except NotFoundError: raise HTTPException(404)
Consolidate that policy into one seam: a NotFoundError → 404 handler
registered in build_app, mirroring the existing DuplicateKeyError
handler. The not-found decision now lives where the data access happens
(the repository's raising get_one), mapped to a response in one place.
- app/exceptions.py — new not_found_error_handler returning
404 {"detail": "Not found"}.
- app/application.py — register NotFoundError handler.
- app/repositories.py — fetch_with_cards tightens to -> models.Deck,
using get_one (raises on miss) instead of get_one_or_none.
- app/api/decks.py — get_deck / update_deck / get_card drop all 404
code; get_card uses get_one. Removed now-unused imports
(NotFoundError, starlette status, HTTPException usage).
404 body is generic {"detail": "Not found"} — advanced-alchemy's
NotFoundError.detail is empty and no test asserts the message.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Three handlers each re-derived the not-found → 404 policy, via two different mechanisms:
This consolidates that policy into one seam: a single NotFoundError → 404 handler registered in build_app, mirroring the existing DuplicateKeyError handler.
Ports modern-python/litestar-sqlalchemy-template#27 to this template, adapted to FastAPI idioms (JSONResponse handler, add_exception_handler, typing.cast responses preserved).
Changes
No handler mentions 404 any more — the not-found decision lives where the data access happens, mapped to a response in one place.
Decisions
Verification
🤖 Generated with Claude Code