Skip to content

DRY Route Handlers with ProjectDep

With SessionDep defined in dependencies.py, every route that touches the database has a clean way to ask for a session. That handles half of the boilerplate. The other half is the lookup-or-404 dance that shows up on most routes.

The repeating 404 pattern

The endpoints for fetching, updating, and deleting projects all take a project_id, query the database, and raise a 404 if the project doesn't exist:

Python
project = crud.get_project(session, project_id)
if not project:
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
    )

Three routes, three copies of the same five lines. As more routes get added (and as a Task model joins the API), that boilerplate compounds.

We can collapse it by writing one dependency that handles the lookup and the 404, then using it everywhere a project is needed. Call it get_project_or_404.

src/release_tracker/dependencies.py
from typing import Annotated

from fastapi import Depends, HTTPException, status
from sqlmodel import Session

from . import crud
from .database import get_session
from .models import Project

SessionDep = Annotated[Session, Depends(get_session)]


def get_project_or_404(project_id: int, session: SessionDep) -> Project:
    project = crud.get_project(session, project_id)
    if project is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
        )
    return project


ProjectDep = Annotated[Project, Depends(get_project_or_404)]

Typing a route handler argument as ProjectDep removes the 404 boilerplate from the route entirely. The route only deals with the happy path:

Python
@app.get("/projects/{project_id}", response_model=ProjectRead)
def get_project(project: ProjectDep):
    return project

If the project doesn't exist, the request never reaches the route. FastAPI raises the 404 from inside get_project_or_404 and returns the response.


Future Refactoring

For now, get_project_or_404 is hardcoded for the Project model. We'll add get_task_or_404 when we add the Task model.

For larger APIs there are patterns to avoid one dependency per model, but they're out of scope for this course.