Passed
Push — master ( 817ef7...81ab82 )
by Steve
01:43
created

lagom.container.Container._load_singleton()   A

Complexity

Conditions 2

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 2
dl 0
loc 4
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1 1
import functools
2 1
import inspect
3 1
from typing import Dict, Type, Union, Any, TypeVar, Callable
4
5 1
from .interfaces import SpecialDepDefinition
6 1
from .exceptions import UnresolvableType
7 1
from .definitions import normalise
8
9 1
UNRESOLVABLE_TYPES = [str, int, float, bool]
10
11 1
X = TypeVar("X")
12
13 1
DepDefinition = Any
14
15
16 1
class Container:
17 1
    _registered_types: Dict[Type, SpecialDepDefinition] = {}
18
19 1
    def define(self, dep: Union[Type[X], Type], resolver: DepDefinition) -> None:
20 1
        self._registered_types[dep] = normalise(resolver, self)
21
22 1
    def resolve(self, dep_type: Type[X], suppress_error=False) -> X:
23 1
        try:
24 1
            if dep_type in UNRESOLVABLE_TYPES:
25 1
                raise UnresolvableType(f"Cannot construct type {dep_type}")
26 1
            registered_type = self._registered_types.get(dep_type, dep_type)
27 1
            return self._build(registered_type)
28 1
        except UnresolvableType as inner_error:
29 1
            if not suppress_error:
30 1
                raise UnresolvableType(
31
                    f"Cannot construct type {dep_type.__name__}"
32
                ) from inner_error
33 1
            return None  # type: ignore
34
35 1
    def partial(self, func: Callable[..., X], keys_to_skip=None) -> Callable[..., X]:
36 1
        spec = inspect.getfullargspec(func)
37 1
        bindable_deps = self._infer_dependencies(
38
            spec, suppress_error=True, keys_to_skip=keys_to_skip or []
39
        )
40 1
        return functools.partial(func, **bindable_deps)
41
42 1
    def __getitem__(self, dep: Type[X]) -> X:
43 1
        return self.resolve(dep)
44
45 1
    def __setitem__(self, dep: Type, resolver: DepDefinition):
46 1
        self.define(dep, resolver)
47
48 1
    def _build(self, dep_type: Any) -> Any:
49 1
        if isinstance(dep_type, SpecialDepDefinition):
50 1
            return dep_type.get_instance(self._build)
51 1
        return self._reflection_build(dep_type)
52
53 1
    def _reflection_build(self, dep_type: Type[X]) -> X:
54 1
        spec = inspect.getfullargspec(dep_type.__init__)
55 1
        sub_deps = self._infer_dependencies(spec)
56 1
        return dep_type(**sub_deps)  # type: ignore
57
58 1
    def _infer_dependencies(
59
        self, spec: inspect.FullArgSpec, suppress_error=False, keys_to_skip=[]
60
    ):
61 1
        sub_deps = {
62
            key: self.resolve(sub_dep_type, suppress_error=suppress_error)
63
            for (key, sub_dep_type) in spec.annotations.items()
64
            if key != "return" and sub_dep_type != Any and key not in keys_to_skip
65
        }
66 1
        filtered_deps = {key: dep for (key, dep) in sub_deps.items() if dep is not None}
67
        return filtered_deps
68