What problem does this solve?
Every router package follows the same shape (__init__.py registers routes via add_api_route, core.py holds handlers, _schemas.py/_helpers.py for models/helpers — per CLAUDE.md). That consistency is good, but some boilerplate is literally duplicated across packages rather than shared:
- The DB-session lifecycle is the biggest one:
db = SessionLocal() / try: … / finally: db.close() appears in ~32 handler functions across the routers. Every handler hand-rolls open/close, which is repetitive and easy to get subtly wrong (e.g. forgetting finally, or not closing on an early return).
- Several
_helpers.py modules define near-identical _serialize(...) shapes (5 packages define a _serialize), and small schema patterns recur.
What would you like to see?
Factor the repeated boilerplate into a small shared base where it's genuinely duplicated, without flattening the per-package structure that keeps the codebase navigable.
- DB session: replace the hand-rolled
SessionLocal()/try/finally with a single shared mechanism — most idiomatically a FastAPI dependency (Depends(get_db)) that yields a session and closes it in a finally, so handlers just take db: Session = Depends(get_db) and drop the boilerplate. (This is also a prerequisite-friendly cleanup for testing.) Migrate handlers incrementally.
- Serialization/helpers: where
_serialize and similar helpers are duplicated with the same shape, consider a shared helper module — but only collapse what's actually the same; don't force-fit genuinely different serializers together.
Proposed implementation sketch
- Add a
get_db dependency (in backend/config.py or a shared deps.py) that yields SessionLocal() and closes in finally.
- Migrate router handlers from manual
db = SessionLocal() blocks to the dependency, package by package (keeps PRs reviewable).
- Audit the duplicated
_serialize/schema patterns and extract only the truly-shared ones.
Testing
- Backend: existing endpoint tests must continue to pass unchanged (behavior-preserving refactor). Verify sessions are still closed (no leaked connections) — the dependency's
finally covers this.
- Coverage gate: touched files stay ≥80%.
Docs
- Update the "router shape" guidance in
CLAUDE.md to reflect the shared get_db dependency once adopted.
What problem does this solve?
Every router package follows the same shape (
__init__.pyregisters routes viaadd_api_route,core.pyholds handlers,_schemas.py/_helpers.pyfor models/helpers — per CLAUDE.md). That consistency is good, but some boilerplate is literally duplicated across packages rather than shared:db = SessionLocal()/try: … / finally: db.close()appears in ~32 handler functions across the routers. Every handler hand-rolls open/close, which is repetitive and easy to get subtly wrong (e.g. forgettingfinally, or not closing on an early return)._helpers.pymodules define near-identical_serialize(...)shapes (5 packages define a_serialize), and small schema patterns recur.What would you like to see?
Factor the repeated boilerplate into a small shared base where it's genuinely duplicated, without flattening the per-package structure that keeps the codebase navigable.
SessionLocal()/try/finallywith a single shared mechanism — most idiomatically a FastAPI dependency (Depends(get_db)) that yields a session and closes it in afinally, so handlers just takedb: Session = Depends(get_db)and drop the boilerplate. (This is also a prerequisite-friendly cleanup for testing.) Migrate handlers incrementally._serializeand similar helpers are duplicated with the same shape, consider a shared helper module — but only collapse what's actually the same; don't force-fit genuinely different serializers together.Proposed implementation sketch
get_dbdependency (inbackend/config.pyor a shareddeps.py) that yieldsSessionLocal()and closes infinally.db = SessionLocal()blocks to the dependency, package by package (keeps PRs reviewable)._serialize/schema patterns and extract only the truly-shared ones.Testing
finallycovers this.Docs
CLAUDE.mdto reflect the sharedget_dbdependency once adopted.