Skip to content

Commit 7c8ca82

Browse files
committed
Replace MultiError with (Base)ExceptionGroup
Closes #2211.
1 parent dedee6e commit 7c8ca82

22 files changed

+81
-1579
lines changed

docs/source/design.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,9 +465,6 @@ There are two notable sub-modules that are largely independent of
465465
the rest of Trio, and could (possibly should?) be extracted into their
466466
own independent packages:
467467

468-
* ``_multierror.py``: Implements :class:`MultiError` and associated
469-
infrastructure.
470-
471468
* ``_ki.py``: Implements the core infrastructure for safe handling of
472469
:class:`KeyboardInterrupt`.
473470

docs/source/reference-core.rst

Lines changed: 3 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ crucial things to keep in mind:
641641

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

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

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

724719

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

839834

840-
Working with :exc:`MultiError`\s
841-
++++++++++++++++++++++++++++++++
842-
843-
.. autoexception:: MultiError
844-
845-
.. attribute:: exceptions
846-
847-
The list of exception objects that this :exc:`MultiError`
848-
represents.
849-
850-
.. automethod:: filter
851-
852-
.. automethod:: catch
853-
:with:
854-
855-
Examples:
856-
857-
Suppose we have a handler function that discards :exc:`ValueError`\s::
858-
859-
def handle_ValueError(exc):
860-
if isinstance(exc, ValueError):
861-
return None
862-
else:
863-
return exc
864-
865-
Then these both raise :exc:`KeyError`::
866-
867-
with MultiError.catch(handle_ValueError):
868-
raise MultiError([KeyError(), ValueError()])
869-
870-
with MultiError.catch(handle_ValueError):
871-
raise MultiError([
872-
ValueError(),
873-
MultiError([KeyError(), ValueError()]),
874-
])
875-
876-
And both of these raise nothing at all::
877-
878-
with MultiError.catch(handle_ValueError):
879-
raise MultiError([ValueError(), ValueError()])
880-
881-
with MultiError.catch(handle_ValueError):
882-
raise MultiError([
883-
MultiError([ValueError(), ValueError()]),
884-
ValueError(),
885-
])
886-
887-
You can also return a new or modified exception, for example::
888-
889-
def convert_ValueError_to_MyCustomError(exc):
890-
if isinstance(exc, ValueError):
891-
# Similar to 'raise MyCustomError from exc'
892-
new_exc = MyCustomError(...)
893-
new_exc.__cause__ = exc
894-
return new_exc
895-
else:
896-
return exc
897-
898-
In the example above, we set ``__cause__`` as a form of explicit
899-
context chaining. :meth:`MultiError.filter` and
900-
:meth:`MultiError.catch` also perform implicit exception chaining – if
901-
you return a new exception object, then the new object's
902-
``__context__`` attribute will automatically be set to the original
903-
exception.
904-
905-
We also monkey patch :class:`traceback.TracebackException` to be able
906-
to handle formatting :exc:`MultiError`\s. This means that anything that
907-
formats exception messages like :mod:`logging` will work out of the
908-
box::
909-
910-
import logging
911-
912-
logging.basicConfig()
913-
914-
try:
915-
raise MultiError([ValueError("foo"), KeyError("bar")])
916-
except:
917-
logging.exception("Oh no!")
918-
raise
919-
920-
Will properly log the inner exceptions:
921-
922-
.. code-block:: none
923-
924-
ERROR:root:Oh no!
925-
Traceback (most recent call last):
926-
File "<stdin>", line 2, in <module>
927-
trio.MultiError: ValueError('foo',), KeyError('bar',)
928-
929-
Details of embedded exception 1:
930-
931-
ValueError: foo
932-
933-
Details of embedded exception 2:
934-
935-
KeyError: 'bar'
936-
937-
938835
.. _task-local-storage:
939836

940837
Task-local storage

docs/source/tutorial.rst

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ Tutorial
1313
still probably read this, because Trio is different.)
1414
1515
Trio turns Python into a concurrent language. It takes the core
16-
async/await syntax introduced in 3.5, and uses it to add three
16+
async/await syntax introduced in 3.5, and uses it to add two
1717
new pieces of semantics:
1818
1919
- cancel scopes: a generic system for managing timeouts and
2020
cancellation
2121
- nurseries: which let your program do multiple things at the same
2222
time
23-
- MultiErrors: for when multiple things go wrong at once
2423
2524
Of course it also provides a complete suite of APIs for doing
2625
networking, file I/O, using worker threads,
@@ -57,8 +56,6 @@ Tutorial
5756
and demonstrate start()
5857
then point out that you can just use serve_tcp()
5958
60-
exceptions and MultiError
61-
6259
example: catch-all logging in our echo server
6360
6461
review of the three (or four) core language extensions
@@ -1149,9 +1146,6 @@ TODO: explain :exc:`Cancelled`
11491146
TODO: explain how cancellation is also used when one child raises an
11501147
exception
11511148

1152-
TODO: show an example :exc:`MultiError` traceback and walk through its
1153-
structure
1154-
11551149
TODO: maybe a brief discussion of :exc:`KeyboardInterrupt` handling?
11561150

11571151
..

newsfragments/2211.removal.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``trio.MultiError`` has been removed in favor of the built-in ``BaseExceptionGroup``
2+
(and its derivative ``ExceptionGroup``), falling back to the backport_ on Python < 3.11.
3+
4+
.. _backport: https://pypi.org/project/exceptiongroup/

trio/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
Cancelled,
2323
BusyResourceError,
2424
ClosedResourceError,
25-
MultiError,
2625
run,
2726
open_nursery,
2827
CancelScope,

trio/_core/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
EndOfChannel,
1818
)
1919

20-
from ._multierror import MultiError
21-
2220
from ._ki import (
2321
enable_ki_protection,
2422
disable_ki_protection,

0 commit comments

Comments
 (0)