{"record":{"id":"3811997bcaa18257","repo":"tiangolo/fastapi","slug":"hero-not-found","errorCode":null,"errorMessage":"Hero not found","messagePattern":"Hero not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"docs_src/sql_databases/tutorial001_an_py310.py","lineNumber":62,"sourceCode":"    session.refresh(hero)\n    return hero\n\n\n@app.get(\"/heroes/\")\ndef read_heroes(\n    session: SessionDep,\n    offset: int = 0,\n    limit: Annotated[int, Query(le=100)] = 100,\n) -> list[Hero]:\n    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()\n    return heroes\n\n\n@app.get(\"/heroes/{hero_id}\")\ndef read_hero(hero_id: int, session: SessionDep) -> Hero:\n    hero = session.get(Hero, hero_id)\n    if not hero:\n        raise HTTPException(status_code=404, detail=\"Hero not found\")\n    return hero\n\n\n@app.delete(\"/heroes/{hero_id}\")\ndef delete_hero(hero_id: int, session: SessionDep):\n    hero = session.get(Hero, hero_id)\n    if not hero:\n        raise HTTPException(status_code=404, detail=\"Hero not found\")\n    session.delete(hero)\n    session.commit()\n    return {\"ok\": True}\n","sourceCodeStart":44,"sourceCodeEnd":74,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/sql_databases/tutorial001_an_py310.py#L44-L74","documentation":"SQLModel/SQLAlchemy CRUD example. GET /heroes/{hero_id} does a primary-key lookup via session.get(Hero, hero_id); if it returns None (no row with that id) the route raises HTTP 404 'Hero not found'. session.get returns None — it does not raise — so this is normal, expected control flow for a missing resource, not a crash. The same 404 is reused by the DELETE handler at line 70.","triggerScenarios":"GET /heroes/9999 (an id never inserted), GET /heroes/0, or requesting a hero after DELETE /heroes/{id} succeeded. Also when database.db is fresh/empty (no POST /heroes/ has run since the file was created).","commonSituations":"DB file deleted/regenerated so previously valid ids no longer exist; a race between a delete and a concurrent read; client holding a stale id from a previous DB instance; check_same_thread SQLite quirks resetting state.","solutions":["GET /heroes/ first to list valid ids, then request one that exists.","POST /heroes/ to create one and use the id returned in the response body.","If soft-delete semantics are needed, keep a tombstone flag instead of hard-deleting, so reads still resolve."],"exampleFix":"// before\nhero = session.get(Hero, hero_id)\nif not hero:\n    raise HTTPException(status_code=404, detail=\"Hero not found\")\n\n// after (same lookup, typed response)\nhero = session.get(Hero, hero_id)\nif hero is None:\n    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=\"Hero not found\")\nreturn hero","handlingStrategy":"validation","validationCode":"# Confirm the id exists before the targeted GET (or just use the list endpoint)\nimport httpx\ndef hero_exists(client: httpx.Client, hero_id: int) -> bool:\n    r = client.get(\"/heroes/\", params={\"limit\": 1000})\n    return any(h[\"id\"] == hero_id for h in r.json())","typeGuard":"from typing import TypeGuard\ndef is_positive_id(x: object) -> TypeGuard[int]:\n    return isinstance(x, int) and x > 0","tryCatchPattern":"import httpx\ntry:\n    r = httpx.get(f\"/heroes/{hero_id}\")\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 404:\n        # render 'not found' UI; fall back to the list view\n        show_list_view()","preventionTips":["Use ids returned by POST /heroes/ rather than guessing.","Prefer the list endpoint with pagination for discovery, then targeted reads.","Treat 404 as an expected outcome for stale ids, not as a transport error."],"tags":["fastapi","sqlmodel","sqlalchemy","rest","python"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}