1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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]
|