Raising & Chaining
Translate low-level failures into domain errors without losing context. Use raise ... from, bare raise, and from None intentionally so tracebacks tell the full story.
Search across all documentation pages
Translate low-level failures into domain errors without losing context. Use raise ... from, bare raise, and from None intentionally so tracebacks tell the full story.
def load_user(user_id: str) -> dict:
try:
return fetch_row(user_id)
except KeyError as exc:
raise UserNotFoundError(user_id) from excWhen to reach for this:
from None)raiseclass UserNotFoundError(LookupError):
def __init__(self, user_id: str) -> None:
self.user_id = user_id
super().__init__(f"user not found: {user_id}")
_DB: dict[str, dict] = {"1": {"name": "Ada"}}
def fetch_row(user_id: str) -> dict:
return _DB[user_id]
def get_user(user_id: str) -> dict:
try:
return fetch_row(user_id)
except KeyError as exc:
raise UserNotFoundError(user_id) from exc
def parse_id(raw: str) -> str:
try:
value = int(raw)
except ValueError:
raise ValueError(f"invalid id: {raw!r}") from None
if value < 1:
raise ValueError("id must be positive")
return str(value)
try:
get_user("999")
except UserNotFoundError as exc:
print(exc)
print(exc.__cause__)What this demonstrates:
from exc sets __cause__ for debugging chainsfrom None hides parser internals from API consumersraise NewError() starts a new exception at current traceback pointraise NewError() from exc links exc as __cause__raise inside except re-raises the caught exception in-flightraise exc from stored variable can reset traceback context - prefer bare raiseException.__context__ may differ from __cause__ when implicit chaining occurs| Syntax | Effect |
|---|---|
raise A from b | Explicit cause shown in traceback |
raise (bare) | Same exception continues |
raise A from None | Suppress displayed context |
raise New() without from - implicit context may still appear but is ambiguous. Fix: always use from exc when wrapping.from None everywhere - hides root causes in ops. Fix: reserve from None for user-input parsing boundaries.raise exc after except block ends - traceback points at reraise site. Fix: bare raise only inside active except.exc_info then raising different type without from - logs and traceback disagree. Fix: chain or log inside handler that bare re-raises.| Alternative | Use When | Don't Use When |
|---|---|---|
| Result/Either types | Functional core, no exceptions | Python service boundaries expecting raises |
| Error codes | C extensions, protocols | Python application layers |
| Aggregate in ExceptionGroup | Parallel failures | Single failure path |
When the underlying exception is an implementation detail for callers - typically input parsing at public API edges.
__cause__ is explicit from. __context__ is implicit link when raising during handling another exception.
Only at module boundaries. Internal code can let KeyError propagate within a package.
log.exception("msg") or exc_info=True captures the active exception traceback at log time.
Yes. Subclass Exception, raise with from, and add fields on the class for structured handling.
finally runs before exception propagates; chaining set in except is preserved.
FastAPI/Flask handlers map exception types to HTTP responses - chain to root cause for 500 logs.
Yes - raise last_exc from last_exc or simply raise the final failure with context on attempt count.
assert becomes AssertionError - not for runtime validation in production; use explicit raises.
pytest.raises and assert exc.value.__cause__ is the expected type.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026