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
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-06 06:04 +0000
1# cython: language_level = 3 # noqa: ERA001
4"""错误类"""
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
19from .abc import ABCPath
20from .abc import AnyKey
23class DependencyNotFoundError(ImportError):
24 """
25 依赖缺失
27 .. versionadded:: 0.3.0
28 """
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))
48# noinspection PyNewStyleGenericSyntax
49def _unavailable_method[M: Callable[..., Never]](method: M) -> M:
50 """
51 被装饰的方法在调用时会抛出实例 :py:attr:`~UnavailableAttribute._exception` 上的异常
53 :param method: 被装饰的方法
54 :type method: M
55 :return: 被装饰的方法
56 :rtype: M
58 .. versionadded:: 0.3.0
59 """
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")
66 return cast(M, wrapper)
69class UnavailableAttribute:
70 """
71 占位代理对象,在任意访问时抛出异常
73 .. versionadded:: 0.3.0
74 """ # noqa: RUF002
76 __slots__ = ("_name", "_reason")
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)
88 @_unavailable_method
89 @override
90 def __getattribute__(self, name: str) -> Never: ... # type: ignore[empty-body]
92 @_unavailable_method
93 @override
94 def __setattr__(self, name: str, value: Any) -> Never: ... # type: ignore[empty-body]
96 @_unavailable_method
97 @override
98 def __delattr__(self, name: str) -> Never: ... # type: ignore[empty-body]
100 @_unavailable_method
101 def __call__(self, *args: Any, **kwargs: Any) -> Never: ... # type: ignore[empty-body] # noqa: D102
103 @_unavailable_method
104 def __getitem__(self, item: Any) -> Never: ... # type: ignore[empty-body]
106 @_unavailable_method
107 def __setitem__(self, key: Any, value: Any) -> Never: ... # type: ignore[empty-body]
109 @_unavailable_method
110 def __delitem__(self, key: Any) -> Never: ... # type: ignore[empty-body]
112 @_unavailable_method
113 def __iter__(self) -> Never: ... # type: ignore[empty-body]
115 @_unavailable_method
116 def __next__(self) -> Never: ... # type: ignore[empty-body]
118 @override
119 def __repr__(self) -> str:
120 return f"<{type(self).__name__} {object.__getattribute__(self, '_name')}>"
123@dataclass
124class TokenInfo:
125 """一段标记的相关信息 用于快速定位到指定标记"""
127 tokens: tuple[str, ...]
128 """
129 当前完整标记列表
130 """
131 current_token: str
132 """
133 当前标记
134 """
135 index: int
136 """
137 current_token在tokens的下标
138 """
140 @property
141 def raw_string(self) -> str:
142 """标记的原始字符串"""
143 return "".join(self.tokens)
146class ConfigDataPathSyntaxException(Exception): # noqa: N818
147 """配置数据检索路径语法错误"""
149 msg: str
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
158 .. tip::
159 错误信息获取优先级
161 1.msg参数
163 2.类字段msg (供快速创建子类)
165 .. versionchanged:: 0.3.0
166 现在传入的错误消息不再软要求带冒号
167 """ # noqa: D205
168 self.token_info = token_info
170 if msg is not None:
171 self.msg = msg
172 elif not hasattr(self, "msg"):
173 self.msg = "Configuration data path syntax error"
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 )
184class UnknownTokenTypeError(ConfigDataPathSyntaxException):
185 # noinspection GrazieInspection
186 """
187 未知的标志类型
189 .. versionchanged:: 0.1.3
190 重命名 ``UnknownTokenType`` 为 ``UnknownTokenTypeError``
191 """
193 msg = "Unknown token type"
196class ConfigOperate(Enum):
197 """对配置的操作类型"""
199 Delete = "Delete"
200 Read = "Read"
201 Write = "Write"
202 Unknown = None
205@dataclass
206class KeyInfo[K: AnyKey]:
207 """一段路径的相关信息 用于快速定位到指定键"""
209 path: ABCPath[K]
210 """
211 当前完整路径
212 """
213 current_key: K
214 """
215 当前键
216 """
217 index: int
218 """
219 current_key在path的下标
220 """
222 @property
223 def relative_keys(self) -> Iterable[K]:
224 """从根到当前键的相对路径"""
225 return self.path[: self.index]
228class RequiredPathNotFoundError(LookupError):
229 """
230 需求的键未找到错误
232 .. versionchanged:: 0.1.5
233 现在继承自LookupError
234 """
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)
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
261class ConfigDataReadOnlyError(TypeError):
262 """
263 配置数据为只读
265 .. versionadded:: 0.1.3
266 """
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)
278class ConfigDataTypeError(ValueError):
279 """配置数据类型错误"""
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
295 .. versionchanged:: 0.1.4
296 ``required_type`` 支持传入多个需求的数据类型
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]
304 self.key_info = key_info
305 self.requited_type = required_type
306 self.current_type = current_type
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 )
316class CyclicReferenceError(ValueError):
317 """
318 配置数据存在循环引用错误
320 .. versionadded:: 0.2.0
321 """
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
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 )
338class UnknownErrorDuringValidateError(Exception):
339 # noinspection GrazieInspection
340 """
341 在验证配置数据时发生未知错误
343 .. versionchanged:: 0.1.3
344 重命名 ``UnknownErrorDuringValidate`` 为 ``UnknownErrorDuringValidateError``
345 """
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}")
355class UnsupportedConfigFormatError(Exception):
356 """
357 不支持的配置文件格式错误
359 .. note::
360 :py:attr:`format` 可以为 :py:const:`None` 这表示 `未指定配置格式` 。
361 在一些情况下 :py:const:`None` 是有效的配置格式,如表示 `默认` 。
362 此错误以 :py:const:`None` 为参数抛出时表示 `我找到了配置格式None,但是我不支持None作为配置格式`
363 """ # noqa: RUF002
365 def __init__(self, _format: str | None):
366 """
367 :param _format: 不支持的配置的文件格式
368 :type _format: str | None
370 .. versionchanged:: 0.3.0
371 重命名参数 ``format_`` 为 ``_format``
372 更改参数 ``_format`` 类型为 ``str | None``
373 """ # noqa: D205
374 self._format = _format
376 @property
377 def format(self) -> str | None:
378 """不支持的配置的文件格式"""
379 return self._format
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}"
387 @override
388 def __eq__(self, other: Any) -> bool:
389 return isinstance(other, type(self)) and self._format == other._format
391 @override
392 def __hash__(self) -> int:
393 """.. versionadded:: 0.3.0"""
394 return hash(self._format)
397class FailedProcessConfigFileError[E: Exception](ExceptionGroup):
398 """
399 SL处理器无法正确处理当前配置文件
401 .. versionchanged:: 0.1.4
402 现在继承自 :py:class:`BaseExceptionGroup`
404 .. versionchanged:: 0.3.0
405 现在正确的继承自 :py:class:`ExceptionGroup`
406 移除冗余属性 ``reasons``
407 """
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
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)
435class ComponentMetadataException(LookupError): # noqa: N818
436 """
437 组件元数据异常
439 .. versionadded:: 0.3.0
440 """
443class ComponentMemberMismatchError(ComponentMetadataException):
444 """
445 组件成员元数据与成员不匹配错误
447 .. versionadded:: 0.3.0
448 """
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
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
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)