aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/excp.py
blob: 5a5871b260e8bc8394669fee4a983bac6a70306e (plain)
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]