aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/excp.py
blob: 9ea204e0beebb10574eab673066f541f5b7f1ce6 (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
from typing import Any

from .attr import CruAttrDefRegistry, CruAttr, CruAttrTable
from ._util import CRU_NOT_FOUND, CruList, CRU_USE_DEFAULT

CRU_EXCEPTION_ATTR_DEF_REGISTRY = CruAttrDefRegistry()


class CruException(Exception):
    ATTR_REGISTRY = CRU_EXCEPTION_ATTR_DEF_REGISTRY

    MESSAGE_KEY = "message"
    INNER_KEY = "inner"
    INTERNAL_KEY = "internal"
    NAME_KEY = "name"
    VALUE_KEY = "value"
    PATH_KEY = "path"
    TYPE_KEY = "type_"

    ATTR_REGISTRY.make_builder(MESSAGE_KEY, "Message describing the exception.").with_constraint(True, str,
                                                                                                 "").build()
    ATTR_REGISTRY.make_builder(INNER_KEY, "Inner exception.").with_constraint(True, Exception,
                                                                              auto_list=True).build()
    ATTR_REGISTRY.make_builder(INTERNAL_KEY,
                               "True if the exception is caused by wrong internal logic. False if it is caused by user's wrong input.").with_constraint(
        True, bool, False).build()
    ATTR_REGISTRY.make_builder(NAME_KEY, "Name of the object that causes the exception.").with_types(str).build()
    ATTR_REGISTRY.make_builder(VALUE_KEY, "Value that causes the exception.").build()
    ATTR_REGISTRY.make_builder(PATH_KEY, "Path that causes the exception.").with_types(str).build()
    ATTR_REGISTRY.make_builder(TYPE_KEY, "Python type related to the exception.").with_types(type).build()

    def __init__(self, message: str, *args,
                 init_attrs: list[CruAttr] | dict[str, Any] | None = None,
                 attrs: list[CruAttr] | dict[str, Any] | None = None, **kwargs) -> None:
        super().__init__(message, *args)

        self._attrs: CruAttrTable = CruAttrTable(self.ATTR_REGISTRY)

        self._attrs.add_value(CruException.MESSAGE_KEY, message)
        if init_attrs is not None:
            self._attrs.extend_with(init_attrs, True)
        if attrs is not None:
            self._attrs.extend_with(attrs, True)
        self._attrs.extend_with(dict(kwargs), True)

    @property
    def attrs(self) -> CruAttrTable:
        return self._attrs

    def get_attr(self, name: str) -> Any:
        return self._attrs.get_value_or(name, None)

    @property
    def message(self) -> str:
        return self.get_attr(CruException.MESSAGE_KEY)

    @property
    def internal(self) -> bool:
        return self.get_attr(CruException.INTERNAL_KEY)

    @property
    def inner(self) -> list[Exception]:
        return self.get_attr(CruException.INNER_KEY)

    @property
    def name(self) -> str | None:
        return self.get_attr(CruException.NAME_KEY)

    @property
    def value(self) -> Any | None:
        return self.get_attr(CruException.VALUE_KEY)

    @property
    def path(self) -> str | None:
        return self.get_attr(CruException.PATH_KEY)

    @property
    def type_(self) -> type | None:
        return self.get_attr(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_or(name)
        if a is not CRU_NOT_FOUND:
            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, des: type = CruList) -> list[Any]:
        l = []
        self._get_attr_list_recursive(name, 0, max_depth, l)
        return des(l)


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"

    CruException.ATTR_REGISTRY.make_builder(USER_MESSAGE_KEY,
                                            "Message describing the exception, but with user-friendly language.").with_types(
        str).build()

    def __init__(self, message: str, user_message: str | None = CRU_USE_DEFAULT, *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.get_attr(UserFriendlyException.USER_MESSAGE_KEY)