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
- Validate the ID before requesting it.
- Ensure the SQLite file is seeded and correct.
- Have the client treat 404 as expected.
- 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
- Request only IDs returned by the list endpoint.
- Keep the database persistent and seeded.
- Return Optional/None if absence is expected.
- Treat 404 as a normal not-found in client code.
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.