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

1from __future__ import annotations 

2 

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 

7 

8if TYPE_CHECKING: 

9 from typing import Self 

10 

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) 

15 

16 

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 

23 

24 return False 

25 

26 

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) 

41 

42 raise TypeError("expected a function, exception type or tuple of exception types") 

43 

44 

45class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]): 

46 """A combination of multiple unrelated exceptions.""" 

47 

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 ) 

59 

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 ) 

65 

66 if cls is BaseExceptionGroup: 

67 if all(isinstance(exc, Exception) for exc in __exceptions): 

68 cls = ExceptionGroup 

69 

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 ) 

81 

82 instance = super().__new__(cls, __message, __exceptions) 

83 instance._message = __message 

84 instance._exceptions = __exceptions 

85 return instance 

86 

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 ) 

92 

93 if not hasattr(self, "__notes__"): 

94 self.__notes__: list[str] = [] 

95 

96 self.__notes__.append(note) 

97 

98 @property 

99 def message(self) -> str: 

100 return self._message 

101 

102 @property 

103 def exceptions( 

104 self, 

105 ) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]: 

106 return tuple(self._exceptions) 

107 

108 @overload 

109 def subgroup( 

110 self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] 

111 ) -> ExceptionGroup[_ExceptionT] | None: 

112 ... 

113 

114 @overload 

115 def subgroup( 

116 self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...] 

117 ) -> BaseExceptionGroup[_BaseExceptionT] | None: 

118 ... 

119 

120 @overload 

121 def subgroup( 

122 self, __condition: Callable[[_BaseExceptionT_co | Self], bool] 

123 ) -> BaseExceptionGroup[_BaseExceptionT_co] | None: 

124 ... 

125 

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 

136 

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) 

143 

144 if subgroup is not exc: 

145 modified = True 

146 elif condition(exc): 

147 exceptions.append(exc) 

148 else: 

149 modified = True 

150 

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 

161 

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 ... 

170 

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 ... 

179 

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 ... 

188 

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 

211 

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) 

219 

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) 

226 

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__ 

233 

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__ 

240 

241 return matching_group, nonmatching_group 

242 

243 @overload 

244 def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]: 

245 ... 

246 

247 @overload 

248 def derive( 

249 self, __excs: Sequence[_BaseExceptionT] 

250 ) -> BaseExceptionGroup[_BaseExceptionT]: 

251 ... 

252 

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__) 

260 

261 return eg 

262 

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})" 

266 

267 def __repr__(self) -> str: 

268 return f"{self.__class__.__name__}({self.message!r}, {self._exceptions!r})" 

269 

270 

271class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception): 

272 def __new__(cls, __message: str, __exceptions: Sequence[_ExceptionT_co]) -> Self: 

273 return super().__new__(cls, __message, __exceptions) 

274 

275 if TYPE_CHECKING: 

276 

277 @property 

278 def exceptions( 

279 self, 

280 ) -> tuple[_ExceptionT_co | ExceptionGroup[_ExceptionT_co], ...]: 

281 ... 

282 

283 @overload # type: ignore[override] 

284 def subgroup( 

285 self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...] 

286 ) -> ExceptionGroup[_ExceptionT] | None: 

287 ... 

288 

289 @overload 

290 def subgroup( 

291 self, __condition: Callable[[_ExceptionT_co | Self], bool] 

292 ) -> ExceptionGroup[_ExceptionT_co] | None: 

293 ... 

294 

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) 

302 

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 ... 

310 

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 ... 

318 

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)