tiangolo/fastapi · error · HTTPException

Hero not found

Error message

Hero not found

What it means

Application-defined HTTPException in the read_hero GET endpoint of the multi-model tutorial (Annotated style). After session.get(Hero, hero_id) returns None for a missing primary key, the handler raises HTTPException(status_code=404, detail="Hero not found"); FastAPI turns that into a 404 response with {"detail":"Hero not found"}.

Source

Thrown at docs_src/sql_databases/tutorial002_an_py310.py:79

    session.refresh(db_hero)
    return db_hero


@app.get("/heroes/", response_model=list[HeroPublic])
def read_heroes(
    session: SessionDep,
    offset: int = 0,
    limit: Annotated[int, Query(le=100)] = 100,
):
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes


@app.get("/heroes/{hero_id}", response_model=HeroPublic)
def read_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero


@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep):
    hero_db = session.get(Hero, hero_id)
    if not hero_db:
        raise HTTPException(status_code=404, detail="Hero not found")
    hero_data = hero.model_dump(exclude_unset=True)
    hero_db.sqlmodel_update(hero_data)
    session.add(hero_db)
    session.commit()
    session.refresh(hero_db)
    return hero_db


@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, session: SessionDep):

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Validate the ID exists before the request.
  2. Point the app at the correct seeded SQLite database file.
  3. Have the client treat a 404 as a normal not-found.
  4. Return None / 204 from the endpoint if absence is not exceptional.

Example fix

// before
@app.get("/heroes/{hero_id}", response_model=HeroPublic)
def read_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero

// after
@app.get("/heroes/{hero_id}", response_model=HeroPublic | None)
def read_hero(hero_id: int, session: SessionDep):
    return session.get(Hero, hero_id)
Defensive patterns

Strategy: try-catch

Try / catch

import httpx
try:
    r = httpx.get(f"http://localhost:8000/heroes/{hero_id}")
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 404:
        hero = None
    else:
        raise

Prevention

When it happens

Trigger: GET /heroes/{hero_id} against an ID that has no Hero row (deleted, never created, or wrong DB file).

Common situations: Client requests a hero ID obtained from a stale list or external source; database.db reset; the row was deleted after being listed.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/5f46f771e7b8e454. Report an issue: GitHub.