Passed
Push — master ( ff2d91...c0bfaa )
by Steve
02:58
created

lagom.exceptions   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Test Coverage

Coverage 92.31%

Importance

Changes 0
Metric Value
eloc 88
dl 0
loc 212
rs 10
c 0
b 0
f 0
wmc 21
ccs 72
cts 78
cp 0.9231

11 Methods

Rating   Name   Duplication   Size   Complexity  
A TypeResolutionBlocked.__init__() 0 3 2
A UnresolvableType.__init__() 0 14 2
A ClassesCannotBeDecorated.__init__() 0 3 1
A DependencyNotDefined.__init__() 0 7 1
A UnableToInvokeBoundFunction.__init__() 0 5 1
A InvalidEnvironmentVariables.__init__() 0 3 1
A TypeOnlyAvailableAsAwaitable.__init__() 0 9 2
A UnresolvableType.get_unresolvable_deps_sequence() 0 13 4
A MissingEnvVariable.__init__() 0 3 1
A UnresolvableType.__str__() 0 2 1
A RecursiveDefinitionError.__init__() 0 7 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A _dep_type_as_string() 0 8 4
1
"""
2
Exceptions raised by the library
3
"""
4
5 1
import inspect
6 1
import typing
7 1
from abc import ABC
8 1
from typing import Type
9
10
11 1
class LagomException(Exception, ABC):
12
    """All exceptions in this library are instances of a LagomException"""
13
14 1
    pass
15
16
17 1
class InjectableNotResolved(RuntimeError, LagomException):
18
    """
19
    An instance of injectable was consumed in some user code.
20
    This should not occur as lagom should have replaced the injectable
21
    with an object. This likely means the function wasn't bound to
22
    an injection container.
23
    """
24
25 1
    pass
26
27
28 1
class InvalidDependencyDefinition(ValueError, LagomException):
29
    """The provided construction logic is not valid"""
30
31 1
    pass
32
33
34 1
class ClassesCannotBeDecorated(SyntaxError, LagomException):
35
    """Decorating classes is not supported by lagom"""
36
37 1
    dep_type: str
38
39 1
    def __init__(self):
40 1
        super().__init__(
41
            "Decorating classes is not supported by lagom. \n"
42
            "Alternative is to create a factory method and use that: \n"
43
            "factory_func = container.partial(ClassName)"
44
        )
45
46
47 1
class MissingReturnType(SyntaxError, LagomException):
48
    """The function provided doesnt type hint a return"""
49
50 1
    pass
51
52
53 1
class DuplicateDefinition(ValueError, LagomException):
54
    """The type has already been defined somewhere else"""
55
56 1
    pass
57
58
59 1
class TypeOnlyAvailableAsAwaitable(SyntaxError, LagomException):
60
    """The type is only available as Awaitable[T]"""
61
62 1
    dep_type: str
63
64 1
    def __init__(self, dep_type: Type):
65
        """
66
67
        :param dep_type: The type that could not be constructed without Awaitable
68
        """
69 1
        self.dep_type = _dep_type_as_string(dep_type)
70 1
        if inspect.isabstract(dep_type):
71
            super().__init__(
72
                f"Unable to construct type {self.dep_type} as it is only available as an async."
73
                "Try requesting Awaitable[{self.dep_type}] instead"
74
            )
75
76
77 1
class UnableToInvokeBoundFunction(TypeError, LagomException):
78
    """A function bound to the container could not be invoked"""
79
80 1
    unresolvable_deps: typing.List[Type]
81
82 1
    def __init__(self, msg, unresolvable_deps):
83 1
        self.unresolvable_deps = unresolvable_deps
84 1
        unresolvable_string_list = ",".join(d.__name__ for d in unresolvable_deps)
85 1
        super().__init__(
86
            f"{msg}. The container could not construct the following types: {unresolvable_string_list}"
87
        )
88
89
90 1
class UnresolvableType(ValueError, LagomException):
91
    """The type cannot be constructed"""
92
93 1
    dep_type: str
94
95 1
    def __init__(self, dep_type: Type):
96
        """
97
98
        :param dep_type: The type that could not be constructed
99
        """
100 1
        self.dep_type = _dep_type_as_string(dep_type)
101 1
        if inspect.isabstract(dep_type):
102 1
            super().__init__(
103
                f"Unable to construct Abstract type {self.dep_type}."
104
                "Try defining an alias or a concrete class to construct"
105
            )
106
        else:
107 1
            super().__init__(
108
                f"Unable to construct dependency of type {self.dep_type} "
109
                "The constructor probably has some unresolvable dependencies"
110
            )
111
112 1
    def __str__(self):
113 1
        return f"{super().__str__()}: {str.join(' => ', self.get_unresolvable_deps_sequence())}"
114
115 1
    def get_unresolvable_deps_sequence(self) -> typing.List[str]:
116
        """Returns the dependency stack with the last element being the dependency source of the exception"""
117 1
        error: typing.Optional[BaseException] = self
118 1
        unresolvable_deps: typing.List[str] = []
119
120 1
        for _loop_guard in range(100):
121 1
            if not (error and isinstance(error, UnresolvableType)):
122 1
                return unresolvable_deps
123 1
            unresolvable_deps.append(error.dep_type)
124 1
            error = error.__cause__
125
        # This should never happen
126
        unresolvable_deps.append("...")
127
        return unresolvable_deps
128
129
130 1
class TypeResolutionBlocked(UnresolvableType):
131
    """The type was explicitly blocked by configuration"""
132
133 1
    dep_type: str
134
135 1
    def __init__(self, dep_type: typing.Optional[typing.Type], msg: str):
136 1
        self.dep_type = _dep_type_as_string(dep_type) if dep_type else ""
137 1
        super(ValueError, self).__init__(msg)
138
139
140 1
class RecursiveDefinitionError(SyntaxError, LagomException):
141
    """Whilst trying to resolve the type python exceeded the recursion depth"""
142
143 1
    dep_type: Type
144
145 1
    def __init__(self, dep_type: Type):
146
        """
147
        :param dep_type: The type that could not be constructed
148
        """
149
        self.dep_type = dep_type
150
        super().__init__(
151
            f"When trying to build dependency of type '{_dep_type_as_string(dep_type)}' python hit a recursion limit. "
152
            "This could indicate a circular definition somewhere."
153
        )
154
155
156 1
class DependencyNotDefined(ValueError, LagomException):
157
    """The type must be explicitly defined in the container"""
158
159 1
    dep_type: Type
160
161 1
    def __init__(self, dep_type: Type):
162
        """
163
        :param dep_type: The type that was not defined
164
        """
165 1
        self.dep_type = dep_type
166 1
        super().__init__(
167
            f"{_dep_type_as_string(dep_type)} has not been defined. "
168
            f"In an explict container all dependencies must be defined"
169
        )
170
171
172 1
class MissingEnvVariable(LagomException):
173
    """
174
    Whilst trying to load settings from environment variables one or more required variables had not been set.
175
    More documentation for this can be found in the lagom.environment module.
176
    """
177
178 1
    def __init__(self, variable_names: typing.List[str]):
179 1
        super().__init__(
180
            f"Expected environment variables not found: {', '.join(variable_names)}"
181
        )
182
183
184 1
class InvalidEnvironmentVariables(LagomException):
185
    """
186
    Whilst trying to load settings from environment variables one or more variables failed the validation rules.
187
    Internally the pydantic library is used to coerce and validate the environment variable from a string into
188
    a python data type.
189
    More documentation for this can be found in the lagom.environment module.
190
    """
191
192 1
    def __init__(self, variable_names: typing.List[str], details: str):
193 1
        super().__init__(
194
            f"Unable to load environment variables: {', '.join(variable_names)} \n {details}"
195
        )
196
197
198 1
class MissingFeature(LagomException):
199
    """This is raised by code in the experimental module. It represents a feature that is planned but not implemented"""
200
201 1
    pass
202
203
204 1
def _dep_type_as_string(dep_type: Type):
205 1
    if hasattr(typing, "get_origin") and typing.get_origin(dep_type) is not None:  # type: ignore
206
        # repr() gives a more sensible output in version 3.10 for List[X] and others like this
207 1
        return repr(dep_type)
208 1
    elif hasattr(dep_type, "__name__"):
209 1
        return dep_type.__name__
210
211
    return str(dep_type)
212