|
1 | 1 | """Top-level package for exceptiongroup."""
|
2 | 2 |
|
| 3 | +import sys |
| 4 | + |
3 | 5 | from ._version import __version__
|
4 | 6 |
|
5 | 7 | __all__ = ["ExceptionGroup", "split", "catch"]
|
@@ -42,26 +44,152 @@ def __init__(self, message, exceptions, sources):
|
42 | 44 | )
|
43 | 45 | )
|
44 | 46 |
|
| 47 | + def __bool__(self): |
| 48 | + return bool(self.exceptions) |
| 49 | + |
| 50 | + def __contains__(self, exception): |
| 51 | + return exception in self.exceptions |
| 52 | + |
45 | 53 | # copy.copy doesn't work for ExceptionGroup, because BaseException have
|
46 | 54 | # rewrite __reduce_ex__ method. We need to add __copy__ method to
|
47 | 55 | # make it can be copied.
|
48 | 56 | def __copy__(self):
|
49 | 57 | 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) |
57 | 59 | return new_group
|
58 | 60 |
|
| 61 | + def __iter__(self): |
| 62 | + return zip(self.exceptions, self.sources) |
| 63 | + |
| 64 | + def __len__(self): |
| 65 | + return len(self.exceptions) |
| 66 | + |
59 | 67 | def __str__(self):
|
60 | 68 | return ", ".join(repr(exc) for exc in self.exceptions)
|
61 | 69 |
|
62 | 70 | def __repr__(self):
|
63 | 71 | return "<ExceptionGroup: {}>".format(self)
|
64 | 72 |
|
| 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 | + |
65 | 193 |
|
66 | 194 | from . import _monkeypatch
|
67 | 195 | from ._tools import split, catch
|
0 commit comments