diff options
Diffstat (limited to 'tools/cru-py/cru/excp.py')
-rw-r--r-- | tools/cru-py/cru/excp.py | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/tools/cru-py/cru/excp.py b/tools/cru-py/cru/excp.py new file mode 100644 index 0000000..5a5871b --- /dev/null +++ b/tools/cru-py/cru/excp.py @@ -0,0 +1,137 @@ +from collections.abc import Callable +from dataclasses import dataclass +from types import NoneType +from typing import Any + +from cru.attr import CruAttrDefRegistry + +CRU_EXCEPTION_ATTR_DEF_REGISTRY = CruAttrDefRegistry() + + +class CruException(Exception): + @staticmethod + def transform_inner(inner: Exception | list[Exception] | None): + if inner is None: + return None + if isinstance(inner, Exception): + return [inner] + if isinstance(inner, list): + return inner + + @staticmethod + def validate_inner(inner: list[Exception]): + for i in inner: + if not isinstance(i, Exception): + raise TypeError(f"Invalid inner exception: {i}") + + MESSAGE_DEF = CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_required("message", "Message describing the exception.", + allow_types=str, default_value="") + INNER_DEF = CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_required("inner", "Inner exception.", + allow_types=list, default_value=[], + transformer=transform_inner, validator=validate_inner) + INTERNAL_DEF = CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_required("internal", + "True if the exception is caused by wrong internal logic. False if it is caused by user's wrong input.", + allow_types=bool, default_value=False) + CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_optional("name", "Name of the object that causes the exception.", + allow_types=str) + CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_optional("value", "Value that causes the exception.", + allow_types=[]) + CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_with("path", "Path that causes the exception.",) + CRU_EXCEPTION_ATTR_DEF_REGISTRY.register_with("type", "Python type related to the exception.") + + def __init__(self, message: str, *args, + inner: Exception | list[Exception] | None = None, + internal: bool = False, + name: str | None = None, + value: Any | None = None, + path: str | None = None, + type_: type | None = None, + init_attrs: dict[str, Any] | None = None, + attrs: dict[str, Any] | None = None, **kwargs) -> None: + super().__init__(message, *args) + + self._attrs = { + CruException.MESSAGE_KEY: message, + CruException.INTERNAL_KEY: internal, + CruException.INNER_KEY: inner, + CruException.NAME_KEY: name, + CruException.VALUE_KEY: value, + CruException.PATH_KEY: path, + CruException.TYPE_KEY: type_, + } + if init_attrs is not None: + self._attrs.update(init_attrs) + if attrs is not None: + self._attrs.update(attrs) + self._attrs.update(kwargs) + + @property + def message(self) -> str: + return self[CruException.MESSAGE_KEY] + + @property + def internal(self) -> bool: + return self[CruException.INTERNAL_KEY] + + @property + def inner(self) -> list[Exception]: + return self[CruException.INNER_KEY] + + @property + def name(self) -> str | None: + return self[CruException.NAME_KEY] + + @property + def value(self) -> Any | None: + return self[CruException.VALUE_KEY] + + @property + def path(self) -> str | None: + return self[CruException.PATH_KEY] + + @property + def type(self) -> type | None: + return self[CruException.TYPE_KEY] + + def _get_attr_list_recursive(self, name: str, depth: int, max_depth: int, l: list[Any]): + if 0 < max_depth < depth + 1: + return + a = self._attrs.get(name, None) + if a is not None: + l.append(a) + for i in self.inner: + if isinstance(i, CruException): + i._get_attr_list_recursive(name, depth + 1, max_depth, l) + + def get_attr_list_recursive(self, name: str, /, max_depth: int = -1) -> list[Any]: + l = [] + self._get_attr_list_recursive(name, 0, max_depth, l) + return l + + def get_optional_attr(self, name: str, max_depth: int = -1) -> Any | None: + l = self.get_attr_list_recursive(name, max_depth) + return l[0] if len(l) > 0 else None + + def __getitem__(self, name: str) -> Any | None: + return self.get_optional_attr(name) + + +class CruInternalLogicError(CruException): + def __init__(self, message: str, *args, **kwargs) -> None: + super().__init__(message, *args, internal=True, **kwargs) + + +class UserFriendlyException(CruException): + USER_MESSAGE_KEY = "user_message" + + CRU_EXCEPTION_ATTR_DEF_REGISTRY.register( + CruExceptionAttrDef(USER_MESSAGE_KEY, "Message describing the exception, but with user-friendly language.")) + + def __init__(self, message: str, user_message: str | None = None, *args, **kwargs) -> None: + if user_message is None: + user_message = message + super().__init__(message, *args, init_attrs={UserFriendlyException.USER_MESSAGE_KEY: user_message}, **kwargs) + + @property + def user_message(self) -> str: + return self[UserFriendlyException.USER_MESSAGE_KEY] |