We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Error Handling with Exceptions, Retries and Warnings step 7 of 20
Translate Library Errors into Your Own
Your app calls a library that raises errors such as ConnectionResetError
or ValueError. Code that calls your app should see only your app's
errors, whichever library you use. Write a helper that turns selected
library errors into app errors and keeps the original attached with
raise ... from.
def translate[T](
fn: Callable[[], T],
mapping: Mapping[type[Exception], type[AppError]],
) -> T: ...
translate(lambda: 7, {ValueError: DataError}) # returns 7
translate(lambda: int("x"), {ValueError: DataError}) # raises DataError
def translate[T](...) -> T says the function returns whatever type fn
returns, and Callable[[], T] is a function that takes zero arguments. Use
this syntax as shown, and pass mypy strict.
What translate does
- Call
fnonce. If it returns, return its result unchanged. - If it raises an
Exception, look throughtype(exc).__mro__in order, and use the first class that is a key inmapping. Construct the mapped class withstr(exc), and raise it from the original exception. - If nothing matches, re-raise the original with a bare
raise. That keeps the same exception object, its traceback and any chain it already has. - Catch only
Exception.KeyboardInterrupt,SystemExitand the other exceptions outside theExceptionfamily pass through untouched.
type(exc).__mro__, the method resolution order, lists the class and every
class it inherits from, starting with the class itself and ending at
object. For ZeroDivisionError it is
(ZeroDivisionError, ArithmeticError, Exception, BaseException, object).
So an entry for the exception's own class comes before an entry for a
parent. If mapping has both
ZeroDivisionError and ArithmeticError, a ZeroDivisionError uses its own
entry. The MRO alone decides, whatever order the keys in mapping have.
If a class has several parents, still follow the MRO as Python gives it.
What solve reports
Keep AppError, its subclasses NetworkError, DataError and AuthError,
and the tables RAISABLE and APP_ERRORS, which map names to classes.
solve(raises, message, mapping) gets names, and every name is a key in one
of the tables.
- Turn
mappinginto a mapping from class to class with the two tables. - Build a function that raises
RAISABLE[raises](message), or returns7whenraisesisNone. Call it throughtranslate. - If it returns, report
{"outcome": "returned", "value": 7}. - If it raises, report these keys:
| Key | Value |
|---|---|
outcome |
"raised" |
type |
the final exception's class name |
app_error |
whether the final exception is an AppError |
message |
str(exc) for the final exception |
cause_type |
the class name of __cause__, or None |
cause_message |
str() of __cause__, or None |
context_type |
the class name of __context__, or None |
cause_is_context |
True only when __cause__ is an exception and is the same object as __context__ |
A mapped error has the original as both __cause__ and __context__. An
unmapped error in this report has neither, because fn raises it outside
any except block.
Edge cases
str(KeyError("missing"))is'missing', with quotes. Reportstr(exc)instead of the message you passed in.- Check for
Nonebefore you take a class name.type(None).__name__is"NoneType", and the report wantsNone.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.