summaryrefslogtreecommitdiff
path: root/pint/errors.py
blob: b1f8e511fed9d02e8646fb5ab7d23f49993a623f (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
138
139
140
141
"""
    pint.errors
    ~~~~~~~~~~~

    Functions and classes related to unit definitions and conversions.

    :copyright: 2016 by Pint Authors, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
"""

OFFSET_ERROR_DOCS_HTML = "https://pint.readthedocs.io/en/latest/nonmult.html"
LOG_ERROR_DOCS_HTML = "https://pint.readthedocs.io/en/latest/nonmult.html"


def _file_prefix(filename=None, lineno=None):
    if filename and lineno is not None:
        return f"While opening {filename}, in line {lineno}: "
    elif filename:
        return f"While opening {filename}: "
    elif lineno is not None:
        return f"In line {lineno}: "
    else:
        return ""


class PintError(Exception):
    """Base exception for all Pint errors."""


class DefinitionSyntaxError(SyntaxError, PintError):
    """Raised when a textual definition has a syntax error."""

    def __init__(self, msg, *, filename=None, lineno=None):
        super().__init__(msg)
        self.filename = filename
        self.lineno = lineno

    def __str__(self):
        return _file_prefix(self.filename, self.lineno) + str(self.args[0])

    @property
    def __dict__(self):
        # SyntaxError.filename and lineno are special fields that don't appear in
        # the __dict__. This messes up pickling and deepcopy, as well
        # as any other Python library that expects sane behaviour.
        return {"filename": self.filename, "lineno": self.lineno}

    def __reduce__(self):
        return DefinitionSyntaxError, self.args, self.__dict__


class RedefinitionError(ValueError, PintError):
    """Raised when a unit or prefix is redefined."""

    def __init__(self, name, definition_type, *, filename=None, lineno=None):
        super().__init__(name, definition_type)
        self.filename = filename
        self.lineno = lineno

    def __str__(self):
        msg = f"Cannot redefine '{self.args[0]}' ({self.args[1]})"
        return _file_prefix(self.filename, self.lineno) + msg

    def __reduce__(self):
        return RedefinitionError, self.args, self.__dict__


class UndefinedUnitError(AttributeError, PintError):
    """Raised when the units are not defined in the unit registry."""

    def __init__(self, *unit_names):
        if len(unit_names) == 1 and not isinstance(unit_names[0], str):
            unit_names = unit_names[0]
        super().__init__(*unit_names)

    def __str__(self):
        if len(self.args) == 1:
            return f"'{self.args[0]}' is not defined in the unit registry"
        return f"{self.args} are not defined in the unit registry"


class PintTypeError(TypeError, PintError):
    pass


class DimensionalityError(PintTypeError):
    """Raised when trying to convert between incompatible units."""

    def __init__(self, units1, units2, dim1="", dim2="", *, extra_msg=""):
        super().__init__()
        self.units1 = units1
        self.units2 = units2
        self.dim1 = dim1
        self.dim2 = dim2
        self.extra_msg = extra_msg

    def __str__(self):
        if self.dim1 or self.dim2:
            dim1 = f" ({self.dim1})"
            dim2 = f" ({self.dim2})"
        else:
            dim1 = ""
            dim2 = ""

        return (
            f"Cannot convert from '{self.units1}'{dim1} to "
            f"'{self.units2}'{dim2}{self.extra_msg}"
        )

    def __reduce__(self):
        return TypeError.__new__, (DimensionalityError,), self.__dict__


class OffsetUnitCalculusError(PintTypeError):
    """Raised on ambiguous operations with offset units."""

    def __str__(self):
        return (
            "Ambiguous operation with offset unit (%s)."
            % ", ".join(str(u) for u in self.args)
            + " See "
            + OFFSET_ERROR_DOCS_HTML
            + " for guidance."
        )


class LogarithmicUnitCalculusError(PintTypeError):
    """Raised on inappropriate operations with logarithmic units."""

    def __str__(self):
        return (
            "Ambiguous operation with logarithmic unit (%s)."
            % ", ".join(str(u) for u in self.args)
            + " See "
            + LOG_ERROR_DOCS_HTML
            + " for guidance."
        )


class UnitStrippedWarning(UserWarning, PintError):
    pass