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 tutorial. session.get(Hero, hero_id) returns None for a missing primary key, so the handler raises HTTPException(status_code=404, detail="Hero not found"). FastAPI renders that as an HTTP 404 response with body {"detail":"Hero not found"}.

Source

Thrown at docs_src/sql_databases/tutorial001_py310.py:58

    session.refresh(hero)
    return hero


@app.get("/heroes/")
def read_heroes(
    session: Session = Depends(get_session),
    offset: int = 0,
    limit: int = Query(default=100, le=100),
) -> list[Hero]:
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes


@app.get("/heroes/{hero_id}")
def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero:
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero


@app.delete("/heroes/{hero_id}")
def delete_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")
    session.delete(hero)
    session.commit()
    return {"ok": True}

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Verify the ID exists (e.g. via the list endpoint) before requesting the detail.
  2. Ensure the SQLite database file is the one containing your data and was seeded.
  3. Treat a 404 from this route as a normal 'not found' result rather than a bug.
  4. If a missing row is common, consider returning null/204 instead of raising 404.

Example fix

// before
@app.get("/heroes/{hero_id}")
def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero:
    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: A GET /heroes/{hero_id} request where no Hero row has the given primary key, for example requesting an ID that was never created, was deleted, or lives in a different database file.

Common situations: Requesting a hero by an ID the client guessed or cached; database.db recreated empty on startup; pointing at the wrong SQLite file; the row was deleted between a list call and a detail call.

Related errors


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