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

lagom.definitions   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Test Coverage

Coverage 98.13%

Importance

Changes 0
Metric Value
eloc 115
dl 0
loc 205
rs 9.68
c 0
b 0
f 0
ccs 105
cts 107
cp 0.9813
wmc 34

20 Methods

Rating   Name   Duplication   Size   Complexity  
A ConstructionWithContainer.__init__() 0 2 1
A ConstructionWithoutContainer.__init__() 0 2 1
A ConstructionWithoutContainer.get_instance() 0 3 1
A ConstructionWithContainer.get_instance() 0 3 1
A YieldWithoutContainer.__init__() 0 2 1
A YieldWithoutContainer.get_instance() 0 3 1
A YieldWithContainer.__init__() 0 2 1
A Alias.__init__() 0 3 1
A YieldWithContainer.get_instance() 0 3 1
A Alias.get_instance() 0 3 1
A SingletonWrapper.get_instance() 0 4 2
A Singleton.__init__() 0 2 1
A PlainInstance.__init__() 0 2 1
A PlainInstance.get_instance() 0 2 1
A Alias.__copy__() 0 2 1
A SingletonWrapper.__init__() 0 4 1
A SingletonWrapper._has_instance() 0 3 1
A SingletonWrapper._load_instance() 0 9 2
A UnresolvableTypeDefinition.get_instance() 0 5 2
A UnresolvableTypeDefinition.__init__() 0 3 1

3 Functions

Rating   Name   Duplication   Size   Complexity  
A yielding_construction() 0 16 3
A construction() 0 15 3
A normalise() 0 18 5
1
"""
2
Classes representing specific ways of representing dependencies
3
"""
4 1
import inspect
5 1
from threading import Lock
6 1
from typing import Union, Type, Optional, Callable, TypeVar, NoReturn, Iterator
7
8 1
from .exceptions import (
9
    InvalidDependencyDefinition,
10
    TypeResolutionBlocked,
11
)
12 1
from .interfaces import SpecialDepDefinition, ReadableContainer, TypeResolver
13 1
from .util.functional import arity
14
15 1
X = TypeVar("X")
16
17
18 1
class ConstructionWithoutContainer(SpecialDepDefinition[X]):
19
    """Wraps a callable for constructing a type"""
20
21 1
    def __init__(self, constructor: Callable[[], X]):
22 1
        self.constructor = constructor
23
24 1
    def get_instance(self, container: ReadableContainer) -> X:
25 1
        resolver = self.constructor
26 1
        return resolver()
27
28
29 1
class ConstructionWithContainer(SpecialDepDefinition[X]):
30
    """Wraps a callable for constructing a type"""
31
32 1
    def __init__(self, constructor: Callable[[ReadableContainer], X]):
33 1
        self.constructor = constructor
34
35 1
    def get_instance(self, container: ReadableContainer) -> X:
36 1
        resolver = self.constructor
37 1
        return resolver(container)
38
39
40 1
class YieldWithoutContainer(SpecialDepDefinition[X]):
41
    """Wraps a callable for constructing a type"""
42
43 1
    def __init__(self, constructor: Callable[[], Iterator[X]]):
44 1
        self.constructor = constructor
45
46 1
    def get_instance(self, container: ReadableContainer) -> X:
47 1
        resolver = self.constructor
48 1
        return next(resolver())
49
50
51 1
class YieldWithContainer(SpecialDepDefinition[X]):
52
    """Wraps a generator for constructing a type"""
53
54 1
    def __init__(self, constructor: Callable[[ReadableContainer], Iterator[X]]):
55 1
        self.constructor = constructor
56
57 1
    def get_instance(self, container: ReadableContainer) -> X:
58 1
        resolver = self.constructor
59 1
        return next(resolver(container))
60
61
62 1
def construction(
63
    resolver: Callable,
64
) -> Union[ConstructionWithContainer, ConstructionWithoutContainer]:
65
    """
66
    Takes a generator and returns a type definition
67
    :param reflector:
68
    :param resolver:
69
    :return:
70
    """
71 1
    func_arity = arity(resolver)
72 1
    if func_arity == 0:
73 1
        return ConstructionWithoutContainer(resolver)
74 1
    if func_arity == 1:
75 1
        return ConstructionWithContainer(resolver)
76 1
    raise InvalidDependencyDefinition(f"Arity {func_arity} functions are not supported")
77
78
79 1
def yielding_construction(
80
    resolver: Callable,
81
) -> Union[YieldWithContainer, YieldWithoutContainer]:
82
    """
83
    Takes a generator and returns a type definition
84
    :param reflector:
85
    :param resolver:
86
    :return:
87
    """
88 1
    func_arity = arity(resolver)
89 1
    if func_arity == 0:
90 1
        return YieldWithoutContainer(resolver)
91 1
    if func_arity == 1:
92 1
        return YieldWithContainer(resolver)
93
    raise InvalidDependencyDefinition(
94
        f"Arity {func_arity} generators are not supported"
95
    )
96
97
98 1
class Alias(SpecialDepDefinition[X]):
99
    """When one class is asked for the other should be returned"""
100
101 1
    alias_type: Type[X]
102 1
    skip_definitions: bool
103
104 1
    def __init__(self, alias_type, skip_definitions=False):
105 1
        self.alias_type = alias_type
106 1
        self.skip_definitions = skip_definitions
107
108 1
    def get_instance(self, container: ReadableContainer) -> X:
109 1
        return container.resolve(
110
            self.alias_type, skip_definitions=self.skip_definitions
111
        )
112
113 1
    def __copy__(self):
114 1
        return Alias(self.alias_type, self.skip_definitions)
115
116
117 1
class SingletonWrapper(SpecialDepDefinition[X]):
118
    """Builds only once then saves the built instance"""
119
120 1
    singleton_type: SpecialDepDefinition
121 1
    _instance: Optional[X]
122 1
    _thread_lock: Lock
123
124 1
    def __init__(self, def_to_wrap: SpecialDepDefinition):
125 1
        self.singleton_type = def_to_wrap
126 1
        self._instance = None
127 1
        self._thread_lock = Lock()
128
129 1
    def get_instance(self, container: ReadableContainer) -> X:
130 1
        if self._has_instance:
131 1
            return self._instance  # type: ignore
132 1
        return self._load_instance(container)
133
134 1
    @property
135 1
    def _has_instance(self) -> bool:
136 1
        return self._instance is not None
137
138 1
    def _load_instance(self, container):
139 1
        try:
140 1
            self._thread_lock.acquire()
141 1
            if self._has_instance:
142
                return self._instance
143 1
            self._instance = self.singleton_type.get_instance(container)
144 1
            return self._instance
145
        finally:
146 1
            self._thread_lock.release()
147
148
149 1
class Singleton(SingletonWrapper[X]):
150
    """Builds only once then saves the built instance"""
151
152 1
    def __init__(self, singleton_type: TypeResolver):
153 1
        super().__init__(normalise(singleton_type, skip_alias_definitions=True))
154
155
156 1
class PlainInstance(SpecialDepDefinition[X]):
157
    """Wraps an actual object that should just be returned"""
158
159 1
    value: X
160
161 1
    def __init__(self, value):
162 1
        self.value = value
163
164 1
    def get_instance(self, _) -> X:
165 1
        return self.value
166
167
168 1
class UnresolvableTypeDefinition(SpecialDepDefinition[NoReturn]):
169
    """
170
    Used to represent a type that should not be built by the container
171
    """
172
173 1
    dep_type: Type
174 1
    _msg_or_exception: Union[str, Exception]
175
176 1
    def __init__(self, dep_type: Type, msg_or_exception: Union[str, Exception]):
177 1
        self.dep_type = dep_type
178 1
        self._msg_or_exception = msg_or_exception
179
180 1
    def get_instance(self, container: ReadableContainer) -> NoReturn:
181 1
        if isinstance(self._msg_or_exception, Exception):
182 1
            raise self._msg_or_exception
183
        else:
184 1
            raise TypeResolutionBlocked(self.dep_type, self._msg_or_exception)
185
186
187 1
def normalise(
188
    resolver: TypeResolver, skip_alias_definitions=False
189
) -> SpecialDepDefinition:
190
    """
191
    :param resolver:
192
    :param skip_alias_definitions if an alias is loaded should futher definitions be skipped
193
    :return:
194
    """
195 1
    if isinstance(resolver, SpecialDepDefinition):
196 1
        return resolver
197 1
    elif inspect.isfunction(resolver):
198 1
        return construction(resolver)  # type: ignore
199 1
    elif inspect.iscoroutinefunction(resolver):
200 1
        return construction(resolver)  # type: ignore
201 1
    elif inspect.isclass(resolver):
202 1
        return Alias(resolver, skip_alias_definitions)
203
    else:
204
        return PlainInstance(resolver)
205