Coverage for src / c41811 / config / errors.py: 100%

141 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-06 06:04 +0000

1# cython: language_level = 3 # noqa: ERA001 

2 

3 

4"""错误类""" 

5 

6import functools 

7from collections.abc import Callable 

8from collections.abc import Iterable 

9from collections.abc import Mapping 

10from collections.abc import Sequence 

11from dataclasses import dataclass 

12from enum import Enum 

13from typing import Any 

14from typing import Never 

15from typing import Self 

16from typing import cast 

17from typing import override 

18 

19from .abc import ABCPath 

20from .abc import AnyKey 

21 

22 

23class DependencyNotFoundError(ImportError): 

24 """ 

25 依赖缺失 

26 

27 .. versionadded:: 0.3.0 

28 """ 

29 

30 def __init__( 

31 self, 

32 dep_name: str, 

33 description: str | None = None, 

34 ): 

35 """ 

36 :param dep_name: 依赖名称 

37 :type dep_name: str 

38 :param description: 描述信息 

39 :type description: str | None 

40 """ # noqa: D205 

41 self.dep_name = dep_name 

42 if description is None: 

43 super().__init__(f"`{dep_name}` is required.") 

44 else: 

45 super().__init__(description.format(dep_name=dep_name)) 

46 

47 

48# noinspection PyNewStyleGenericSyntax 

49def _unavailable_method[M: Callable[..., Never]](method: M) -> M: 

50 """ 

51 被装饰的方法在调用时会抛出实例 :py:attr:`~UnavailableAttribute._exception` 上的异常 

52 

53 :param method: 被装饰的方法 

54 :type method: M 

55 :return: 被装饰的方法 

56 :rtype: M 

57 

58 .. versionadded:: 0.3.0 

59 """ 

60 

61 # noinspection PyUnusedLocal 

62 @functools.wraps(method) 

63 def wrapper(self: "UnavailableAttribute", *args: Any, **kwargs: Any) -> Never: # noqa: ARG001 

64 raise object.__getattribute__(self, "_reason") 

65 

66 return cast(M, wrapper) 

67 

68 

69class UnavailableAttribute: 

70 """ 

71 占位代理对象,在任意访问时抛出异常 

72 

73 .. versionadded:: 0.3.0 

74 """ # noqa: RUF002 

75 

76 __slots__ = ("_name", "_reason") 

77 

78 def __init__(self, name: str, reason: Exception): 

79 """ 

80 :param name: 属性名 

81 :type name: str 

82 :param reason: 抛出的异常 

83 :type reason: DependencyNotFoundError 

84 """ # noqa: D205 

85 object.__setattr__(self, "_name", name) 

86 object.__setattr__(self, "_reason", reason) 

87 

88 @_unavailable_method 

89 @override 

90 def __getattribute__(self, name: str) -> Never: ... # type: ignore[empty-body] 

91 

92 @_unavailable_method 

93 @override 

94 def __setattr__(self, name: str, value: Any) -> Never: ... # type: ignore[empty-body] 

95 

96 @_unavailable_method 

97 @override 

98 def __delattr__(self, name: str) -> Never: ... # type: ignore[empty-body] 

99 

100 @_unavailable_method 

101 def __call__(self, *args: Any, **kwargs: Any) -> Never: ... # type: ignore[empty-body] # noqa: D102 

102 

103 @_unavailable_method 

104 def __getitem__(self, item: Any) -> Never: ... # type: ignore[empty-body] 

105 

106 @_unavailable_method 

107 def __setitem__(self, key: Any, value: Any) -> Never: ... # type: ignore[empty-body] 

108 

109 @_unavailable_method 

110 def __delitem__(self, key: Any) -> Never: ... # type: ignore[empty-body] 

111 

112 @_unavailable_method 

113 def __iter__(self) -> Never: ... # type: ignore[empty-body] 

114 

115 @_unavailable_method 

116 def __next__(self) -> Never: ... # type: ignore[empty-body] 

117 

118 @override 

119 def __repr__(self) -> str: 

120 return f"<{type(self).__name__} {object.__getattribute__(self, '_name')}>" 

121 

122 

123@dataclass 

124class TokenInfo: 

125 """一段标记的相关信息 用于快速定位到指定标记""" 

126 

127 tokens: tuple[str, ...] 

128 """ 

129 当前完整标记列表 

130 """ 

131 current_token: str 

132 """ 

133 当前标记 

134 """ 

135 index: int 

136 """ 

137 current_token在tokens的下标 

138 """ 

139 

140 @property 

141 def raw_string(self) -> str: 

142 """标记的原始字符串""" 

143 return "".join(self.tokens) 

144 

145 

146class ConfigDataPathSyntaxException(Exception): # noqa: N818 

147 """配置数据检索路径语法错误""" 

148 

149 msg: str 

150 

151 def __init__(self, token_info: TokenInfo, msg: str | None = None): 

152 """ 

153 :param token_info: token相关信息 

154 :type token_info: TokenInfo 

155 :param msg: 错误信息 

156 :type msg: str | None 

157 

158 .. tip:: 

159 错误信息获取优先级 

160 

161 1.msg参数 

162 

163 2.类字段msg (供快速创建子类) 

164 

165 .. versionchanged:: 0.3.0 

166 现在传入的错误消息不再软要求带冒号 

167 """ # noqa: D205 

168 self.token_info = token_info 

169 

170 if msg is not None: 

171 self.msg = msg 

172 elif not hasattr(self, "msg"): 

173 self.msg = "Configuration data path syntax error" 

174 

175 @override 

176 def __str__(self) -> str: 

177 return ( 

178 f"{self.msg}: " 

179 f"{self.token_info.raw_string} -> {self.token_info.current_token}" 

180 f" ({self.token_info.index + 1} / {len(self.token_info.tokens)})" 

181 ) 

182 

183 

184class UnknownTokenTypeError(ConfigDataPathSyntaxException): 

185 # noinspection GrazieInspection 

186 """ 

187 未知的标志类型 

188 

189 .. versionchanged:: 0.1.3 

190 重命名 ``UnknownTokenType`` 为 ``UnknownTokenTypeError`` 

191 """ 

192 

193 msg = "Unknown token type" 

194 

195 

196class ConfigOperate(Enum): 

197 """对配置的操作类型""" 

198 

199 Delete = "Delete" 

200 Read = "Read" 

201 Write = "Write" 

202 Unknown = None 

203 

204 

205@dataclass 

206class KeyInfo[K: AnyKey]: 

207 """一段路径的相关信息 用于快速定位到指定键""" 

208 

209 path: ABCPath[K] 

210 """ 

211 当前完整路径 

212 """ 

213 current_key: K 

214 """ 

215 当前键 

216 """ 

217 index: int 

218 """ 

219 current_key在path的下标 

220 """ 

221 

222 @property 

223 def relative_keys(self) -> Iterable[K]: 

224 """从根到当前键的相对路径""" 

225 return self.path[: self.index] 

226 

227 

228class RequiredPathNotFoundError(LookupError): 

229 """ 

230 需求的键未找到错误 

231 

232 .. versionchanged:: 0.1.5 

233 现在继承自LookupError 

234 """ 

235 

236 def __init__( 

237 self, 

238 key_info: KeyInfo[Any], 

239 operate: ConfigOperate = ConfigOperate.Unknown, 

240 ): 

241 """ 

242 :param key_info: 键相关信息 

243 :type key_info: KeyInfo 

244 :param operate: 何种操作过程中发生的该错误 

245 :type operate: ConfigOperate 

246 """ # noqa: D205 

247 self.key_info = key_info 

248 self.operate = ConfigOperate(operate) 

249 

250 @override 

251 def __str__(self) -> str: 

252 string = ( 

253 f"{self.key_info.path.unparse()} -> {self.key_info.current_key.unparse()}" 

254 f" ({self.key_info.index + 1} / {len(self.key_info.path)})" 

255 ) 

256 if self.operate.value is not ConfigOperate.Unknown: 

257 string += f" Operate: {self.operate.value}" 

258 return string 

259 

260 

261class ConfigDataReadOnlyError(TypeError): 

262 """ 

263 配置数据为只读 

264 

265 .. versionadded:: 0.1.3 

266 """ 

267 

268 def __init__(self, msg: str | None = None): 

269 """ 

270 :param msg: 错误信息 

271 :type msg: str | None 

272 """ # noqa: D205 

273 if msg is None: 

274 msg = "ConfigData is read-only" 

275 super().__init__(msg) 

276 

277 

278class ConfigDataTypeError(ValueError): 

279 """配置数据类型错误""" 

280 

281 def __init__( 

282 self, 

283 key_info: KeyInfo[Any], 

284 required_type: tuple[type, ...] | type, 

285 current_type: type, 

286 ): 

287 """ 

288 :param key_info: 键相关信息 

289 :type key_info: KeyInfo 

290 :param required_type: 该键需求的数据类型 

291 :type required_type: tuple[type, ...] | type 

292 :param current_type: 当前键的数据类型 

293 :type current_type: type 

294 

295 .. versionchanged:: 0.1.4 

296 ``required_type`` 支持传入多个需求的数据类型 

297 

298 .. versionchanged:: 0.2.0 

299 重命名参数 ``now_type`` 为 ``current_type`` 

300 """ # noqa: D205 

301 if isinstance(required_type, Sequence) and (len(required_type) == 1): 

302 required_type = required_type[0] 

303 

304 self.key_info = key_info 

305 self.requited_type = required_type 

306 self.current_type = current_type 

307 

308 super().__init__( 

309 f"{self.key_info.path.unparse()} -> {self.key_info.current_key.unparse()}" 

310 f" ({self.key_info.index + 1} / {len(self.key_info.path)})" 

311 f" Must be '{self.requited_type}'" 

312 f", Not '{self.current_type}'" 

313 ) 

314 

315 

316class CyclicReferenceError(ValueError): 

317 """ 

318 配置数据存在循环引用错误 

319 

320 .. versionadded:: 0.2.0 

321 """ 

322 

323 def __init__(self, key_info: KeyInfo[Any]): 

324 """ 

325 :param key_info: 检测到循环引用的键信息 

326 :type key_info: KeyInfo[Any] 

327 """ # noqa: D205 

328 self.key_info = key_info 

329 

330 @override 

331 def __str__(self) -> str: 

332 return ( 

333 f"Cyclic reference detected at {self.key_info.path.unparse()} -> {self.key_info.current_key.unparse()}" 

334 f" ({self.key_info.index + 1}/{len(self.key_info.path)})" 

335 ) 

336 

337 

338class UnknownErrorDuringValidateError(Exception): 

339 # noinspection GrazieInspection 

340 """ 

341 在验证配置数据时发生未知错误 

342 

343 .. versionchanged:: 0.1.3 

344 重命名 ``UnknownErrorDuringValidate`` 为 ``UnknownErrorDuringValidateError`` 

345 """ 

346 

347 def __init__(self, *args: Any, **kwargs: Any): 

348 """ 

349 :param args: 未知错误信息 

350 :param kwargs: 未知错误信息 

351 """ # noqa: D205 

352 super().__init__(f"Args: {args}, Kwargs: {kwargs}") 

353 

354 

355class UnsupportedConfigFormatError(Exception): 

356 """ 

357 不支持的配置文件格式错误 

358 

359 .. note:: 

360 :py:attr:`format` 可以为 :py:const:`None` 这表示 `未指定配置格式` 。 

361 在一些情况下 :py:const:`None` 是有效的配置格式,如表示 `默认` 。 

362 此错误以 :py:const:`None` 为参数抛出时表示 `我找到了配置格式None,但是我不支持None作为配置格式` 

363 """ # noqa: RUF002 

364 

365 def __init__(self, _format: str | None): 

366 """ 

367 :param _format: 不支持的配置的文件格式 

368 :type _format: str | None 

369 

370 .. versionchanged:: 0.3.0 

371 重命名参数 ``format_`` 为 ``_format`` 

372 更改参数 ``_format`` 类型为 ``str | None`` 

373 """ # noqa: D205 

374 self._format = _format 

375 

376 @property 

377 def format(self) -> str | None: 

378 """不支持的配置的文件格式""" 

379 return self._format 

380 

381 @override 

382 def __str__(self) -> str: 

383 if self.format is None: 

384 return "Unspecified config format" 

385 return f"Unsupported config format: {self._format}" 

386 

387 @override 

388 def __eq__(self, other: Any) -> bool: 

389 return isinstance(other, type(self)) and self._format == other._format 

390 

391 @override 

392 def __hash__(self) -> int: 

393 """.. versionadded:: 0.3.0""" 

394 return hash(self._format) 

395 

396 

397class FailedProcessConfigFileError[E: Exception](ExceptionGroup): 

398 """ 

399 SL处理器无法正确处理当前配置文件 

400 

401 .. versionchanged:: 0.1.4 

402 现在继承自 :py:class:`BaseExceptionGroup` 

403 

404 .. versionchanged:: 0.3.0 

405 现在正确的继承自 :py:class:`ExceptionGroup` 

406 移除冗余属性 ``reasons`` 

407 """ 

408 

409 @staticmethod 

410 def __new__(cls, reason: E | Iterable[E] | Mapping[str, E], msg: str = "Failed to process config file") -> Self: 

411 """ 

412 :param reason: 处理配置文件失败的原因 

413 :type reason: E | Iterable[E] | Mapping[str, E] 

414 :param msg: 提示信息 

415 :type msg: str 

416 

417 .. versionchanged:: 0.3.0 

418 更改参数 ``reason`` 类型从 :py:class:`BaseException` 改为 :py:class`Exception` 

419 """ # noqa: D205 

420 message: str 

421 exceptions: Sequence[E] 

422 if isinstance(reason, Exception): 

423 reason: E # type: ignore[no-redef] 

424 message = f"{msg}: {reason}" 

425 exceptions = (reason,) 

426 elif isinstance(reason, Mapping): 

427 message = "\n".join((msg, *(f"{k}: {v}" for k, v in reason.items()))) 

428 exceptions = tuple(reason.values()) 

429 else: 

430 message = "\n".join((msg, *map(str, reason))) 

431 exceptions = tuple(reason) 

432 return super().__new__(cls, message, exceptions) 

433 

434 

435class ComponentMetadataException(LookupError): # noqa: N818 

436 """ 

437 组件元数据异常 

438 

439 .. versionadded:: 0.3.0 

440 """ 

441 

442 

443class ComponentMemberMismatchError(ComponentMetadataException): 

444 """ 

445 组件成员元数据与成员不匹配错误 

446 

447 .. versionadded:: 0.3.0 

448 """ 

449 

450 def __init__(self, missing: set[str], redundant: set[str]): 

451 """ 

452 :param missing: 缺少的成员 

453 :type missing: set[str] 

454 :param redundant: 冗余的成员 

455 :type redundant: set[str] 

456 """ # noqa: D205 

457 self.missing = missing 

458 self.redundant = redundant 

459 

460 @override 

461 def __str__(self) -> str: 

462 msg = "Component member metadata does not match members" 

463 if self.missing: 

464 msg += f", Missing members: {self.missing}" 

465 if self.redundant: 

466 msg += f", Redundant members: {self.redundant}" 

467 return msg 

468 

469 

470__all__ = ( 

471 "ComponentMemberMismatchError", 

472 "ComponentMetadataException", 

473 "ConfigDataPathSyntaxException", 

474 "ConfigDataReadOnlyError", 

475 "ConfigDataTypeError", 

476 "ConfigOperate", 

477 "CyclicReferenceError", 

478 "DependencyNotFoundError", 

479 "FailedProcessConfigFileError", 

480 "KeyInfo", 

481 "RequiredPathNotFoundError", 

482 "TokenInfo", 

483 "UnavailableAttribute", 

484 "UnknownErrorDuringValidateError", 

485 "UnknownTokenTypeError", 

486 "UnsupportedConfigFormatError", 

487)