File: exceptions.py

package info (click to toggle)
python-schema-salad 8.9.20251102115403-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,060 kB
  • sloc: python: 19,247; cpp: 2,631; cs: 1,869; java: 1,341; makefile: 187; xml: 184; sh: 103; javascript: 46
file content (146 lines) | stat: -rw-r--r-- 4,783 bytes parent folder | download
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
138
139
140
141
142
143
144
145
146
"""Shared Exception classes."""

from collections.abc import Sequence
from typing import Final, Optional, Union

from .sourceline import SourceLine, reflow_all, strip_duplicated_lineno


def _simplify(exc: "SchemaSaladException") -> list["SchemaSaladException"]:
    return [exc] if len(exc.message) else exc.children


def _with_bullet(exc: "SchemaSaladException", bullet: str) -> "SchemaSaladException":
    if exc.bullet == "":
        exc.bullet = bullet
    return exc


class SchemaSaladException(Exception):
    """Base class for all schema-salad exceptions."""

    def __init__(
        self,
        msg: str,
        sl: Optional[SourceLine] = None,
        children: Optional[Sequence["SchemaSaladException"]] = None,
        bullet_for_children: str = "",
        detailed_message: Optional[str] = None,
    ) -> None:
        super().__init__(msg)
        self.message: Final = self.args[0]
        self.detailed_message: Final = detailed_message
        self.file: Optional[str] = None
        self.start: Optional[tuple[int, int]] = None
        self.end: Optional[tuple[int, int]] = None

        self.is_warning: bool = False

        # It will be set by its parent
        self.bullet: str = ""

        if children is None:
            self.children: list["SchemaSaladException"] = []
        elif len(children) <= 1:
            self.children = sum((_simplify(c) for c in children), [])
        else:
            self.children = sum(
                (_simplify(_with_bullet(c, bullet_for_children)) for c in children), []
            )

        self.with_sourceline(sl)
        self.propagate_sourceline()

    def propagate_sourceline(self) -> None:
        if self.file is None:
            return
        for c in self.children:
            if c.file is None:
                c.file = self.file
                c.start = self.start
                c.end = self.end
                c.propagate_sourceline()

    def as_warning(self) -> "SchemaSaladException":
        self.is_warning = True
        for c in self.children:
            c.as_warning()
        return self

    def with_sourceline(self, sl: Optional[SourceLine]) -> "SchemaSaladException":
        """Use the provided SourceLine to set the causal location."""
        if sl and sl.file():
            self.file = sl.file()
            self.start = sl.start()
            self.end = sl.end()
        else:
            self.file = None
            self.start = None
            self.end = None
        return self

    def leaves(self) -> list["SchemaSaladException"]:
        """Return the list of all the exceptions at the tips of the tree."""
        if len(self.children) > 0:
            return sum((c.leaves() for c in self.children), [])
        if len(self.message):
            return [self]
        return []

    def prefix(self) -> str:
        pre: str = ""
        if self.file:
            linecol0: Union[int, str] = ""
            linecol1: Union[int, str] = ""
            if self.start:
                linecol0, linecol1 = self.start
            pre = f"{self.file}:{linecol0}:{linecol1}: "

        return pre + "Warning: " if self.is_warning else pre

    def summary(self, level: int = 0, with_bullet: bool = False) -> str:
        indent_per_level: Final = 2
        spaces: Final = (level * indent_per_level) * " "
        bullet: Final = self.bullet + " " if len(self.bullet) > 0 and with_bullet else ""
        message_string: Final = (
            self.detailed_message
            if (len(self.children) < 1 and self.detailed_message)
            else self.message
        )
        return f"{self.prefix()}{spaces}{bullet}{message_string}"

    def __str__(self) -> str:
        """Convert to a string using :py:meth:`pretty_str`."""
        return str(self.pretty_str())

    def pretty_str(self, level: int = 0) -> str:
        messages: Final = (
            len(self.message)
            if len(self.children) > 0
            else len(self.detailed_message or self.message)
        )
        my_summary: Final = [self.summary(level, True)] if messages else []
        next_level: Final = level + 1 if messages else level

        ret: Final = "\n".join(
            e for e in my_summary + [c.pretty_str(next_level) for c in self.children]
        )
        if level == 0:
            return strip_duplicated_lineno(reflow_all(ret))
        return ret


class SchemaException(SchemaSaladException):
    """Indicates error with the provided schema definition."""


class ValidationException(SchemaSaladException):
    """Indicates error with document against the provided schema."""


class ClassValidationException(ValidationException):
    pass


def to_one_line_messages(exc: SchemaSaladException) -> str:
    return "\n".join(c.summary() for c in exc.leaves())