Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/exceptiongroup/_exceptions.py: 25%
172 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:05 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:05 +0000
1from __future__ import annotations
3from collections.abc import Callable, Sequence
4from functools import partial
5from inspect import getmro, isclass
6from typing import TYPE_CHECKING, Generic, Type, TypeVar, cast, overload
8if TYPE_CHECKING:
9 from typing import Self
11_BaseExceptionT_co = TypeVar("_BaseExceptionT_co", bound=BaseException, covariant=True)
12_BaseExceptionT = TypeVar("_BaseExceptionT", bound=BaseException)
13_ExceptionT_co = TypeVar("_ExceptionT_co", bound=Exception, covariant=True)
14_ExceptionT = TypeVar("_ExceptionT", bound=Exception)
17def check_direct_subclass(
18 exc: BaseException, parents: tuple[type[BaseException]]
19) -> bool:
20 for cls in getmro(exc.__class__)[:-1]:
21 if cls in parents:
22 return True
24 return False
27def get_condition_filter(
28 condition: type[_BaseExceptionT]
29 | tuple[type[_BaseExceptionT], ...]
30 | Callable[[_BaseExceptionT_co], bool]
31) -> Callable[[_BaseExceptionT_co], bool]:
32 if isclass(condition) and issubclass(
33 cast(Type[BaseException], condition), BaseException
34 ):
35 return partial(check_direct_subclass, parents=(condition,))
36 elif isinstance(condition, tuple):
37 if all(isclass(x) and issubclass(x, BaseException) for x in condition):
38 return partial(check_direct_subclass, parents=condition)
39 elif callable(condition):
40 return cast("Callable[[BaseException], bool]", condition)
42 raise TypeError("expected a function, exception type or tuple of exception types")
45class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
46 """A combination of multiple unrelated exceptions."""
48 def __new__(
49 cls, __message: str, __exceptions: Sequence[_BaseExceptionT_co]
50 ) -> Self:
51 if not isinstance(__message, str):
52 raise TypeError(f"argument 1 must be str, not {type(__message)}")
53 if not isinstance(__exceptions, Sequence):
54 raise TypeError("second argument (exceptions) must be a sequence")
55 if not __exceptions:
56 raise ValueError(
57 "second argument (exceptions) must be a non-empty sequence"
58 )
60 for i, exc in enumerate(__exceptions):
61 if not isinstance(exc, BaseException):
62 raise ValueError(
63 f"Item {i} of second argument (exceptions) is not an exception"
64 )
66 if cls is BaseExceptionGroup:
67 if all(isinstance(exc, Exception) for exc in __exceptions):
68 cls = ExceptionGroup
70 if issubclass(cls, Exception):
71 for exc in __exceptions:
72 if not isinstance(exc, Exception):
73 if cls is ExceptionGroup:
74 raise TypeError(
75 "Cannot nest BaseExceptions in an ExceptionGroup"
76 )
77 else:
78 raise TypeError(
79 f"Cannot nest BaseExceptions in {cls.__name__!r}"
80 )
82 instance = super().__new__(cls, __message, __exceptions)
83 instance._message = __message
84 instance._exceptions = __exceptions
85 return instance
87 def add_note(self, note: str) -> None:
88 if not isinstance(note, str):
89 raise TypeError(
90 f"Expected a string, got note={note!r} (type {type(note).__name__})"
91 )
93 if not hasattr(self, "__notes__"):
94 self.__notes__: list[str] = []
96 self.__notes__.append(note)
98 @property
99 def message(self) -> str:
100 return self._message
102 @property
103 def exceptions(
104 self,
105 ) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]:
106 return tuple(self._exceptions)
108 @overload
109 def subgroup(
110 self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
111 ) -> ExceptionGroup[_ExceptionT] | None:
112 ...
114 @overload
115 def subgroup(
116 self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
117 ) -> BaseExceptionGroup[_BaseExceptionT] | None:
118 ...
120 @overload
121 def subgroup(
122 self, __condition: Callable[[_BaseExceptionT_co | Self], bool]
123 ) -> BaseExceptionGroup[_BaseExceptionT_co] | None:
124 ...
126 def subgroup(
127 self,
128 __condition: type[_BaseExceptionT]
129 | tuple[type[_BaseExceptionT], ...]
130 | Callable[[_BaseExceptionT_co | Self], bool],
131 ) -> BaseExceptionGroup[_BaseExceptionT] | None:
132 condition = get_condition_filter(__condition)
133 modified = False
134 if condition(self):
135 return self
137 exceptions: list[BaseException] = []
138 for exc in self.exceptions:
139 if isinstance(exc, BaseExceptionGroup):
140 subgroup = exc.subgroup(__condition)
141 if subgroup is not None:
142 exceptions.append(subgroup)
144 if subgroup is not exc:
145 modified = True
146 elif condition(exc):
147 exceptions.append(exc)
148 else:
149 modified = True
151 if not modified:
152 return self
153 elif exceptions:
154 group = self.derive(exceptions)
155 group.__cause__ = self.__cause__
156 group.__context__ = self.__context__
157 group.__traceback__ = self.__traceback__
158 return group
159 else:
160 return None
162 @overload
163 def split(
164 self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
165 ) -> tuple[
166 ExceptionGroup[_ExceptionT] | None,
167 BaseExceptionGroup[_BaseExceptionT_co] | None,
168 ]:
169 ...
171 @overload
172 def split(
173 self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
174 ) -> tuple[
175 BaseExceptionGroup[_BaseExceptionT] | None,
176 BaseExceptionGroup[_BaseExceptionT_co] | None,
177 ]:
178 ...
180 @overload
181 def split(
182 self, __condition: Callable[[_BaseExceptionT_co | Self], bool]
183 ) -> tuple[
184 BaseExceptionGroup[_BaseExceptionT_co] | None,
185 BaseExceptionGroup[_BaseExceptionT_co] | None,
186 ]:
187 ...
189 def split(
190 self,
191 __condition: type[_BaseExceptionT]
192 | tuple[type[_BaseExceptionT], ...]
193 | Callable[[_BaseExceptionT_co], bool],
194 ) -> (
195 tuple[
196 ExceptionGroup[_ExceptionT] | None,
197 BaseExceptionGroup[_BaseExceptionT_co] | None,
198 ]
199 | tuple[
200 BaseExceptionGroup[_BaseExceptionT] | None,
201 BaseExceptionGroup[_BaseExceptionT_co] | None,
202 ]
203 | tuple[
204 BaseExceptionGroup[_BaseExceptionT_co] | None,
205 BaseExceptionGroup[_BaseExceptionT_co] | None,
206 ]
207 ):
208 condition = get_condition_filter(__condition)
209 if condition(self):
210 return self, None
212 matching_exceptions: list[BaseException] = []
213 nonmatching_exceptions: list[BaseException] = []
214 for exc in self.exceptions:
215 if isinstance(exc, BaseExceptionGroup):
216 matching, nonmatching = exc.split(condition)
217 if matching is not None:
218 matching_exceptions.append(matching)
220 if nonmatching is not None:
221 nonmatching_exceptions.append(nonmatching)
222 elif condition(exc):
223 matching_exceptions.append(exc)
224 else:
225 nonmatching_exceptions.append(exc)
227 matching_group: Self | None = None
228 if matching_exceptions:
229 matching_group = self.derive(matching_exceptions)
230 matching_group.__cause__ = self.__cause__
231 matching_group.__context__ = self.__context__
232 matching_group.__traceback__ = self.__traceback__
234 nonmatching_group: Self | None = None
235 if nonmatching_exceptions:
236 nonmatching_group = self.derive(nonmatching_exceptions)
237 nonmatching_group.__cause__ = self.__cause__
238 nonmatching_group.__context__ = self.__context__
239 nonmatching_group.__traceback__ = self.__traceback__
241 return matching_group, nonmatching_group
243 @overload
244 def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]:
245 ...
247 @overload
248 def derive(
249 self, __excs: Sequence[_BaseExceptionT]
250 ) -> BaseExceptionGroup[_BaseExceptionT]:
251 ...
253 def derive(
254 self, __excs: Sequence[_BaseExceptionT]
255 ) -> BaseExceptionGroup[_BaseExceptionT]:
256 eg = BaseExceptionGroup(self.message, __excs)
257 if hasattr(self, "__notes__"):
258 # Create a new list so that add_note() only affects one exceptiongroup
259 eg.__notes__ = list(self.__notes__)
261 return eg
263 def __str__(self) -> str:
264 suffix = "" if len(self._exceptions) == 1 else "s"
265 return f"{self.message} ({len(self._exceptions)} sub-exception{suffix})"
267 def __repr__(self) -> str:
268 return f"{self.__class__.__name__}({self.message!r}, {self._exceptions!r})"
271class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception):
272 def __new__(cls, __message: str, __exceptions: Sequence[_ExceptionT_co]) -> Self:
273 return super().__new__(cls, __message, __exceptions)
275 if TYPE_CHECKING:
277 @property
278 def exceptions(
279 self,
280 ) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]:
281 ...
283 @overload # type: ignore[override]
284 def subgroup(
285 self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
286 ) -> ExceptionGroup[_ExceptionT] | None:
287 ...
289 @overload
290 def subgroup(
291 self, __condition: Callable[[_ExceptionT_co | Self], bool]
292 ) -> ExceptionGroup[_ExceptionT_co] | None:
293 ...
295 def subgroup(
296 self,
297 __condition: type[_ExceptionT]
298 | tuple[type[_ExceptionT], ...]
299 | Callable[[_ExceptionT_co], bool],
300 ) -> ExceptionGroup[_ExceptionT] | None:
301 return super().subgroup(__condition)
303 @overload
304 def split(
305 self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
306 ) -> tuple[
307 ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None
308 ]:
309 ...
311 @overload
312 def split(
313 self, __condition: Callable[[_ExceptionT_co | Self], bool]
314 ) -> tuple[
315 ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None
316 ]:
317 ...
319 def split(
320 self: Self,
321 __condition: type[_ExceptionT]
322 | tuple[type[_ExceptionT], ...]
323 | Callable[[_ExceptionT_co], bool],
324 ) -> tuple[
325 ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None
326 ]:
327 return super().split(__condition)