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
- Verify the ID exists (e.g. via the list endpoint) before requesting the detail.
- Ensure the SQLite database file is the one containing your data and was seeded.
- Treat a 404 from this route as a normal 'not found' result rather than a bug.
- 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
- Fetch the list of valid IDs first and only request IDs that appear in it.
- Keep the database file stable across restarts (do not let create_all wipe data).
- Return Optional/None or 204 from the endpoint if absence is expected.
- Log 404s separately so clients can distinguish not-found from server errors.
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.