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 (non-Annotated). When session.get(Hero, hero_id) returns None the handler raises HTTPException(status_code=404, detail="Hero not found"), which FastAPI returns as a 404 with {"detail":"Hero not found"}.

Source

Thrown at docs_src/sql_databases/tutorial002_py310.py:76

    session.refresh(db_hero)
    return db_hero


@app.get("/heroes/", response_model=list[HeroPublic])
def read_heroes(
    session: Session = Depends(get_session),
    offset: int = 0,
    limit: int = Query(default=100, le=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: Session = Depends(get_session)):
    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: Session = Depends(get_session)
):
    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

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Validate the ID before requesting it.
  2. Ensure the SQLite file is seeded and correct.
  3. Have the client treat 404 as expected.
  4. Return None / 204 instead of raising if absence is normal.

Example fix

// before
@app.get("/heroes/{hero_id}", response_model=HeroPublic)
def read_hero(hero_id: int, session: Session = Depends(get_session)):
    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: Session = Depends(get_session)):
    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()
    hero = r.json()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 404:
        hero = None
    else:
        raise

Prevention

When it happens

Trigger: GET /heroes/{hero_id} for a primary key that has no Hero row (deleted, never created, wrong DB).

Common situations: Stale client ID; empty/reset database.db; row deleted between list and detail calls; wrong SQLite file.

Related errors


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