Passed
Pull Request — master (#226)
by Steve
02:47
created

lagom.wrapping.RegularFunc.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 4
dl 0
loc 4
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
"""
2
Code in this module is used to wrap and decorate functions that have been
3
bound to a container
4
"""
5 1
import functools
6 1
import inspect
7 1
from typing import Callable
8
9 1
from .exceptions import UnableToInvokeBoundFunction
10 1
from .injection_context import TemporaryInjectionContext
11 1
from .interfaces import ReadableContainer, ContainerBoundFunction
12 1
from .util.reflection import FunctionSpec
13
14
15 1
class RegularFunc(ContainerBoundFunction):
16
    """
17
    Represents a function that has been bound to a container
18
    """
19
20 1
    _argument_updater: Callable
21 1
    _base_injection_context: TemporaryInjectionContext
22 1
    _inner_func: Callable
23
24 1
    def __init__(self, inner_func, base_injection_context, argument_updater):
25 1
        self._inner_func = inner_func
26 1
        self._base_injection_context = base_injection_context
27 1
        self._argument_updater = argument_updater
28
29 1
    def __call__(self, *args, **kwargs):
30 1
        argument_updater = self._argument_updater
31 1
        inner_func = self._inner_func
32 1
        bound_args, bound_kwargs = argument_updater(
33
            self._base_injection_context, args, kwargs
34
        )
35 1
        return inner_func(*bound_args, **bound_kwargs)
36
37 1
    def rebind(self, container: ReadableContainer):
38 1
        return RegularFunc(
39
            self._inner_func,
40
            self._base_injection_context.rebind(container),
41
            self._argument_updater,
42
        )
43
44
45 1
class AsyncFunc(ContainerBoundFunction):
46
    """
47
    Represents an async function that has been bound to a container
48
    """
49
50 1
    _argument_updater: Callable
51 1
    _base_injection_context: TemporaryInjectionContext
52 1
    _inner_func: Callable
53
54 1
    def __init__(self, inner_func, base_injection_context, argument_updater):
55 1
        self._inner_func = inner_func
56 1
        self._base_injection_context = base_injection_context
57 1
        self._argument_updater = argument_updater
58
59 1
    def __call__(self, *args, **kwargs):
60
        return self.__async_call__(*args, **kwargs)
61
62 1
    async def __async_call__(self, *args, **kwargs):
63 1
        argument_updater = self._argument_updater
64 1
        inner_func = self._inner_func
65 1
        bound_args, bound_kwargs = argument_updater(
66
            self._base_injection_context, args, kwargs
67
        )
68 1
        return await inner_func(*bound_args, **bound_kwargs)
69
70 1
    def as_coroutine(self):
71
        """
72
        returns a coroutine that wraps this class. Some class methods
73
        also get added to the coroutine. This is so it acts like a class with
74
        an __asynccall__ magic method.
75
        """
76
77 1
        async def _coroutine_func(*args, **kwargs):
78 1
            return await self.__async_call__(*args, **kwargs)
79
80 1
        _coroutine_func.rebind = self.rebind  # type: ignore
81
82 1
        return _coroutine_func
83
84 1
    def rebind(self, container: ReadableContainer):
85 1
        return AsyncFunc(
86
            self._inner_func,
87
            self._base_injection_context.rebind(container),
88
            self._argument_updater,
89
        ).as_coroutine()
90
91
92 1
def apply_argument_updater(
93
    func,
94
    base_injection_context,
95
    argument_updater,
96
    spec: FunctionSpec,
97
    catch_errors=False,
98
) -> ContainerBoundFunction:
99
    """
100
    Takes a function and binds it to a container with an update function
101
    """
102 1
    inner_func = func if not catch_errors else _wrap_func_in_error_handling(func, spec)
103 1
    if inspect.iscoroutinefunction(func):
104
105 1
        _bound_func = AsyncFunc(
106
            inner_func, base_injection_context, argument_updater
107
        ).as_coroutine()
108
109
    else:
110
111 1
        _bound_func = RegularFunc(inner_func, base_injection_context, argument_updater)
112
113 1
    return functools.wraps(func)(_bound_func)
114
115
116 1
def _wrap_func_in_error_handling(func, spec: FunctionSpec):
117
    """
118
    Takes a func and its spec and returns a function that's the same
119
    but with more useful TypeError messages
120
    :param func:
121
    :param spec:
122
    :return:
123
    """
124
125 1
    @functools.wraps(func)
126 1
    def _error_handling_func(*args, **kwargs):
127 1
        try:
128 1
            return func(*args, **kwargs)
129 1
        except TypeError as error:
130
            # if it wasn't in kwargs the container couldn't build it
131 1
            unresolvable_deps = [
132
                dep_type
133
                for (name, dep_type) in spec.annotations.items()
134
                if name not in kwargs.keys()
135
            ]
136 1
            raise UnableToInvokeBoundFunction(str(error), unresolvable_deps)
137
138
    return _error_handling_func
139