aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/excp.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cru-py/cru/excp.py')
-rw-r--r--tools/cru-py/cru/excp.py137
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]