We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Failure by Design step 4 of 18
Exception chaining: raise ... from
RuntimeError: request failed with no __cause__ tells you nothing. The same
error chained to a ConnectionResetError tells you everything: which layer
broke, and whether it is worth retrying. Chaining is the cheapest debuggability
you will ever buy, and Python gives you three distinct behaviours:
-
Implicit — raise anything inside an
exceptblock and the interpreter sets__context__to whatever you were handling. The traceback prints “During handling of the above exception, another exception occurred”. You get this for free; it is often noise. -
Explicit —
raise NewError(...) from originalsets__cause__. The traceback prints “The above exception was the direct cause of the following exception”, and every log aggregator and error tracker follows the link. This is what you want when translating an error across a boundary. -
Suppressed —
raise NewError(...) from Noneclears the display chain. Correct in exactly two situations: the original leaks an implementation detail your caller must not couple to, or it contains a secret. Reaching for it because the traceback is “ugly” is how incidents become unsolvable.
Two more tools worth internalising: a bare raise inside an except block
re-raises the current exception with its original traceback (writing
raise exc instead resets the traceback to this line — a real loss of
information), and exc.add_note("...") (3.11) attaches context to an exception
that is already in flight.
What to build
def translate[T](
fn: Callable[[], T],
mapping: Mapping[type[Exception], type[AppError]],
) -> T:
Call fn(). If it returns, return its value.
If it raises an Exception, walk type(exc).__mro__ in order and take the
first class that is a key in mapping. MRO order is what makes
“most specific wins” fall out for free: for a ZeroDivisionError the MRO is
ZeroDivisionError -> ArithmeticError -> Exception -> BaseException -> object,
so a mapping containing both ZeroDivisionError and ArithmeticError picks
the former. Raise mapping[base](str(exc)) from the original.
If nothing in the MRO is a key, the exception is not yours to translate:
re-raise it untouched with a bare raise, preserving its traceback.
The module already gives you RAISABLE (name to builtin exception class) and
APP_ERRORS (name to AppError subclass), plus the AppError hierarchy:
NetworkError, DataError, AuthError.
The probe
def solve(
raises: str | None,
message: str,
mapping: Mapping[str, str],
) -> dict[str, object]:
Build fn so that it raises RAISABLE[raises](message) when raises is not
None, and returns the integer 7 otherwise. Build the class-keyed mapping
from the string-keyed one. Call translate, and report.
On the success path return exactly:
{"outcome": "returned", "value": <what fn returned>}
On the failure path catch at the boundary and return exactly:
| key | value |
|---|---|
"outcome" |
"raised" |
"type" |
type(exc).__name__ |
"app_error" |
isinstance(exc, AppError) |
"message" |
str(exc) |
"cause_type" |
type(exc.__cause__).__name__, or None |
"cause_message" |
str(exc.__cause__), or None |
"context_type" |
type(exc.__context__).__name__, or None |
"cause_is_context" |
True when __cause__ is not None and is the same object as __context__ |
That last key is not a trick question. Raising from exc inside an except exc block sets both attributes, to the same object — the explicit cause and
the implicit context coincide. Seeing that once is worth a paragraph of prose.
Typing notes
from accepts BaseException | None, so mypy is happy with from exc.
Reading exc.__cause__ gives you BaseException | None, which means you
cannot call type(...).__name__ on it until you have proved it is not None —
the is not None check is load-bearing, not decorative.
translate is generic in T with PEP 695 syntax (def translate[T](...),
Python 3.12+). Getting that right is what makes translate(fn, mapping) return
int rather than Any at the call site.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.