We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Error Handling with Exceptions, Retries and Warnings step 12 of 20
Split an Exception Group Without Losing Its Shape
An import job collects its errors in a tree of exception groups, with one
inner group per file. You want to retry some errors, report the rest, and
still know which file each error came from. split() does this. It cuts a
group in two and keeps the nesting on both sides. Collect Every Failure in a Batch handled a group with except*, and this exercise takes one apart as
data.
Build the tree
build(spec) turns a nested dictionary into exceptions:
- A spec whose
"items"is a list is a group. Build each child that is a mapping, recursively, skip the other entries, and returnBaseExceptionGroup(message, children). A group ignores any"exc"key. - Any other spec is a single error. Its
"exc"names a class inLEAF_TYPES. Return that class called with the message. - The message is
"message"if it is a string, otherwise"". - If
"note"is a string, even an empty one, attach it withadd_note(). Ignore a missing or non-string note. - If a single error's
"exc"is missing or is not a string, raiseValueError("leaf spec needs an 'exc' name"). An unknown name raisesKeyError.
The constructor raises ValueError for a group with no children left to
build. It returns an ExceptionGroup when every child is an Exception, and
a BaseExceptionGroup otherwise.
Cancelled in the starter stands for a cancellation signal, a request to
stop work early. Python makes its own cancellation exception a
BaseException on purpose, so that except Exception does not catch it.
Cancelled works the same way.
Partition the errors
partition_errors(eg, *, retryable) returns a (retryable, terminal) pair:
- Split
egonException. If the other half exists, for example aCancelledor aKeyboardInterrupt, raise that half at once. The caller then sees only that half, and the ordinary errors go unreported. - Otherwise split the
Exceptionhalf on theretryabletuple of classes, and return the pair thatsplit()gives you.
Either half can be None, and an empty retryable tuple matches nothing.
split() keeps the original errors with their nesting, order, messages and
notes, so build no new errors.
Describe the result
describe(exc) turns an exception into plain data, recursively:
group: {"kind": "group", "type": <class name>, "message": <group message>,
"notes": [...], "items": [<described children>]}
leaf: {"kind": "leaf", "type": <class name>, "message": str(exc), "notes": [...]}
str() of a group adds a count, as in batch (2 sub-exceptions), so read
the group's message attribute instead. "notes" lists the exception's own
notes, or []. A group's notes stay on the group and do not repeat on its
children.
solve(tree, retryable) builds the root, looks up each retryable name in
RETRYABLE_TYPES, partitions the root, and describes the result:
{"outcome": "partitioned", "retryable": <description or None>, "terminal": <description or None>}
{"outcome": "escaped", "escaped": <description>}
- If the root is not a group, raise
TypeError("the top-level spec must be a group"). - Let errors from
buildand unknown retryable names escape. - Catch the group that
partition_errorsraises with a plainexcept BaseExceptionGroup, and return the escaped shape.
RETRYABLE_TYPES includes TerminalError on purpose. The caller's list
decides what is retryable, not the class name. A name listed twice still
matches each error once.
In the starter's types, BaseExceptionGroup[Exception] is a group of
Exception instances, and type[Exception] means a class such as
ValueError itself. Cancelled is not an Exception, so mypy rejects it in
RETRYABLE_TYPES.
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.