Skip to content
This repository was archived by the owner on Nov 2, 2022. It is now read-only.

Commit af50327

Browse files
author
Robert Schindler
committed
Implement API for handling ExceptionGroup
1 parent b3e0d6c commit af50327

File tree

1 file changed

+135
-7
lines changed

1 file changed

+135
-7
lines changed

exceptiongroup/__init__.py

+135-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Top-level package for exceptiongroup."""
22

3+
import sys
4+
35
from ._version import __version__
46

57
__all__ = ["ExceptionGroup", "split", "catch"]
@@ -42,26 +44,152 @@ def __init__(self, message, exceptions, sources):
4244
)
4345
)
4446

47+
def __bool__(self):
48+
return bool(self.exceptions)
49+
50+
def __contains__(self, exception):
51+
return exception in self.exceptions
52+
4553
# copy.copy doesn't work for ExceptionGroup, because BaseException have
4654
# rewrite __reduce_ex__ method. We need to add __copy__ method to
4755
# make it can be copied.
4856
def __copy__(self):
4957
new_group = self.__class__(self.message, self.exceptions, self.sources)
50-
new_group.__traceback__ = self.__traceback__
51-
new_group.__context__ = self.__context__
52-
new_group.__cause__ = self.__cause__
53-
# Setting __cause__ also implicitly sets the __suppress_context__
54-
# attribute to True. So we should copy __suppress_context__ attribute
55-
# last, after copying __cause__.
56-
new_group.__suppress_context__ = self.__suppress_context__
58+
self._copy_magic_attrs(new_group)
5759
return new_group
5860

61+
def __iter__(self):
62+
return zip(self.exceptions, self.sources)
63+
64+
def __len__(self):
65+
return len(self.exceptions)
66+
5967
def __str__(self):
6068
return ", ".join(repr(exc) for exc in self.exceptions)
6169

6270
def __repr__(self):
6371
return "<ExceptionGroup: {}>".format(self)
6472

73+
def _copy_magic_attrs(self, dst):
74+
"""Copy exception-specific attributes to another :class:`ExceptionGroup`."""
75+
dst.__traceback__ = self.__traceback__
76+
dst.__context__ = self.__context__
77+
dst.__cause__ = self.__cause__
78+
# Setting __cause__ also implicitly sets the __suppress_context__
79+
# attribute to True. So we should copy __suppress_context__ attribute
80+
# last, after copying __cause__.
81+
dst.__suppress_context__ = self.__suppress_context__
82+
83+
def add(self, exception, source=""):
84+
"""Return a new group with exceptions of this group + another exception.
85+
86+
The magic attributes ``__cause__``, ``__context__``, ``__suppress_context__``
87+
and ``__traceback__`` are preserved.
88+
89+
:param exception: exception to add
90+
:type exception: BaseException
91+
:param source: string describing where the exception originated from
92+
:type source: str
93+
:rtype: ExceptionGroup
94+
"""
95+
new = type(self)(
96+
self.message,
97+
self.exceptions + [exception],
98+
self.sources + [source],
99+
)
100+
self._copy_magic_attrs(new)
101+
return new
102+
103+
def find(self, predicate, with_source=False):
104+
"""Return the first exception that fulfills some predicate or ``None``.
105+
106+
:param predicate: see :meth:`findall`
107+
:type predicate: callable, type, (type)
108+
:param with_source: see :meth:`findall`
109+
:type with_source: bool
110+
:rtype: BaseException, None
111+
"""
112+
for item in self.findall(predicate, with_source):
113+
return item
114+
115+
def findall(self, predicate, with_source=False):
116+
"""Yield only exceptions that fulfill some predicate.
117+
118+
:param predicate:
119+
Callable that takes a :class:`BaseException` object and returns whether it
120+
fulfills some criteria (``True``) or not (``False``).
121+
If a type or tuple of types is given instead of a callable, :func:`isinstance`
122+
is automatically used as the predicate function.
123+
:type predicate: callable, type, (type)
124+
:param with_source:
125+
Normally, only the matching :class:`BaseException` objects are
126+
yielded. However, when this is set to ``True``, two-element tuples are
127+
yielded whose first element is the :class:`BaseException` and the second
128+
is the associated source (:class:`str`).
129+
:type with_source: bool
130+
"""
131+
if isinstance(predicate, (type, tuple)):
132+
exc_type = predicate
133+
predicate = lambda _exc: isinstance(_exc, exc_type)
134+
if with_source:
135+
for exception, source in zip(self.exceptions, self.sources):
136+
if predicate(exception):
137+
yield exception, source
138+
else:
139+
yield from filter(predicate, self.exceptions)
140+
141+
def maybe_reraise(self, from_exception=0, unwrap=True):
142+
"""(Re-)raise this exception group if it contains any exception.
143+
144+
If the group is empty, this returns without doing anything.
145+
146+
:param from_exception:
147+
This has the same meaning as the ``from`` keyword of the ``raise``
148+
statement. The default value of ``0`` causes the exception originally
149+
caught by the current ``except`` block to be used. This is retrieved using
150+
``sys.exc_info()[1]``.
151+
:type from_exception: BaseException, None
152+
:param unwrap:
153+
Normally, when there is just one exception left in the group, it is
154+
unwrapped and raised as is. With this option, you can prevent the
155+
unwrapping.
156+
:type unwrap: bool
157+
"""
158+
if not self.exceptions:
159+
return
160+
if unwrap and len(self.exceptions) == 1:
161+
exc = self.exceptions[0]
162+
else:
163+
exc = self
164+
if from_exception == 0:
165+
from_exception = sys.exc_info()[1]
166+
raise exc from from_exception
167+
168+
def remove(self, exception):
169+
"""Return a new group without a particular exception.
170+
171+
The magic attributes ``__cause__``, ``__context__``, ``__suppress_context__``
172+
and ``__traceback__`` are preserved.
173+
174+
:param exception: exception to remove
175+
:type exception: BaseException
176+
:rtype: ExceptionGroup
177+
:raises ValueError: if exception not contained in this group
178+
"""
179+
try:
180+
idx = self.exceptions.index(exception)
181+
except ValueError:
182+
raise ValueError(
183+
f"{exception!r} not contained in {self!r}"
184+
) from None
185+
new = type(self)(
186+
self.message,
187+
self.exceptions[:idx] + self.exceptions[idx + 1 :],
188+
self.sources[:idx] + self.sources[idx + 1 :],
189+
)
190+
self._copy_magic_attrs(new)
191+
return new
192+
65193

66194
from . import _monkeypatch
67195
from ._tools import split, catch

0 commit comments

Comments
 (0)