Skip to content

Commit

Permalink
Replace MultiError with (Base)ExceptionGroup
Browse files Browse the repository at this point in the history
Closes #2211.
  • Loading branch information
agronholm committed Jan 25, 2022
1 parent 31706e8 commit 743354b
Show file tree
Hide file tree
Showing 22 changed files with 81 additions and 1,570 deletions.
3 changes: 0 additions & 3 deletions docs/source/design.rst
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,6 @@ There are two notable sub-modules that are largely independent of
the rest of Trio, and could (possibly should?) be extracted into their
own independent packages:

* ``_multierror.py``: Implements :class:`MultiError` and associated
infrastructure.

* ``_ki.py``: Implements the core infrastructure for safe handling of
:class:`KeyboardInterrupt`.

Expand Down
109 changes: 3 additions & 106 deletions docs/source/reference-core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ crucial things to keep in mind:

* Any unhandled exceptions are re-raised inside the parent task. If
there are multiple exceptions, then they're collected up into a
single :exc:`MultiError` exception.
single :exc:`BaseExceptionGroup` or :exc:`ExceptionGroup` exception.

Since all tasks are descendents of the initial task, one consequence
of this is that :func:`run` can't finish until all tasks have
Expand Down Expand Up @@ -712,14 +712,9 @@ limitation. Consider code like::
what? In some sense, the answer should be "both of these at once", but
in Python there can only be one exception at a time.

Trio's answer is that it raises a :exc:`MultiError` object. This is a
Trio's answer is that it raises a :exc:`BaseExceptionGroup` object. This is a
special exception which encapsulates multiple exception objects –
either regular exceptions or nested :exc:`MultiError`\s. To make these
easier to work with, Trio installs a custom `sys.excepthook` that
knows how to print nice tracebacks for unhandled :exc:`MultiError`\s,
and it also provides some helpful utilities like
:meth:`MultiError.catch`, which allows you to catch "part of" a
:exc:`MultiError`.
either regular exceptions or nested :exc:`BaseExceptionGroup`\s.


Spawning tasks without becoming a parent
Expand Down Expand Up @@ -837,104 +832,6 @@ The nursery API
See :meth:`~Nursery.start`.


Working with :exc:`MultiError`\s
++++++++++++++++++++++++++++++++

.. autoexception:: MultiError

.. attribute:: exceptions

The list of exception objects that this :exc:`MultiError`
represents.

.. automethod:: filter

.. automethod:: catch
:with:

Examples:

Suppose we have a handler function that discards :exc:`ValueError`\s::

def handle_ValueError(exc):
if isinstance(exc, ValueError):
return None
else:
return exc

Then these both raise :exc:`KeyError`::

with MultiError.catch(handle_ValueError):
raise MultiError([KeyError(), ValueError()])

with MultiError.catch(handle_ValueError):
raise MultiError([
ValueError(),
MultiError([KeyError(), ValueError()]),
])

And both of these raise nothing at all::

with MultiError.catch(handle_ValueError):
raise MultiError([ValueError(), ValueError()])

with MultiError.catch(handle_ValueError):
raise MultiError([
MultiError([ValueError(), ValueError()]),
ValueError(),
])

You can also return a new or modified exception, for example::

def convert_ValueError_to_MyCustomError(exc):
if isinstance(exc, ValueError):
# Similar to 'raise MyCustomError from exc'
new_exc = MyCustomError(...)
new_exc.__cause__ = exc
return new_exc
else:
return exc

In the example above, we set ``__cause__`` as a form of explicit
context chaining. :meth:`MultiError.filter` and
:meth:`MultiError.catch` also perform implicit exception chaining – if
you return a new exception object, then the new object's
``__context__`` attribute will automatically be set to the original
exception.

We also monkey patch :class:`traceback.TracebackException` to be able
to handle formatting :exc:`MultiError`\s. This means that anything that
formats exception messages like :mod:`logging` will work out of the
box::

import logging

logging.basicConfig()

try:
raise MultiError([ValueError("foo"), KeyError("bar")])
except:
logging.exception("Oh no!")
raise

Will properly log the inner exceptions:

.. code-block:: none
ERROR:root:Oh no!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
trio.MultiError: ValueError('foo',), KeyError('bar',)
Details of embedded exception 1:
ValueError: foo
Details of embedded exception 2:
KeyError: 'bar'
.. _task-local-storage:

Task-local storage
Expand Down
8 changes: 1 addition & 7 deletions docs/source/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ Tutorial
still probably read this, because Trio is different.)
Trio turns Python into a concurrent language. It takes the core
async/await syntax introduced in 3.5, and uses it to add three
async/await syntax introduced in 3.5, and uses it to add two
new pieces of semantics:
- cancel scopes: a generic system for managing timeouts and
cancellation
- nurseries: which let your program do multiple things at the same
time
- MultiErrors: for when multiple things go wrong at once
Of course it also provides a complete suite of APIs for doing
networking, file I/O, using worker threads,
Expand Down Expand Up @@ -57,8 +56,6 @@ Tutorial
and demonstrate start()
then point out that you can just use serve_tcp()
exceptions and MultiError
example: catch-all logging in our echo server
review of the three (or four) core language extensions
Expand Down Expand Up @@ -1149,9 +1146,6 @@ TODO: explain :exc:`Cancelled`
TODO: explain how cancellation is also used when one child raises an
exception

TODO: show an example :exc:`MultiError` traceback and walk through its
structure

TODO: maybe a brief discussion of :exc:`KeyboardInterrupt` handling?

..
Expand Down
4 changes: 4 additions & 0 deletions newsfragments/2211.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
``trio.MultiError`` has been removed in favor of the built-in ``BaseExceptionGroup``
(and its derivative ``ExceptionGroup``), falling back to the backport_ on Python < 3.11.

.. _backport: https://pypi.org/project/exceptiongroup/
1 change: 0 additions & 1 deletion trio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
Cancelled,
BusyResourceError,
ClosedResourceError,
MultiError,
run,
open_nursery,
CancelScope,
Expand Down
2 changes: 0 additions & 2 deletions trio/_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
EndOfChannel,
)

from ._multierror import MultiError

from ._ki import (
enable_ki_protection,
disable_ki_protection,
Expand Down
Loading

0 comments on commit 743354b

Please sign in to comment.