Passed
Push — master ( 8dc26c...68acdf )
by Steve
02:50
created

lagom.exceptions   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Test Coverage

Coverage 91.25%

Importance

Changes 0
Metric Value
eloc 90
dl 0
loc 214
rs 10
c 0
b 0
f 0
ccs 73
cts 80
cp 0.9125
wmc 22

11 Methods

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