1
|
|
|
""" |
2
|
|
|
This module provides decorators for hooking an |
3
|
|
|
application into the container.s |
4
|
|
|
""" |
5
|
|
|
|
6
|
1 |
|
import inspect |
7
|
1 |
|
from contextlib import contextmanager, asynccontextmanager |
8
|
1 |
|
from functools import wraps |
9
|
1 |
|
from types import FunctionType |
10
|
1 |
|
from typing import ( |
11
|
|
|
List, |
12
|
|
|
Type, |
13
|
|
|
Callable, |
14
|
|
|
Tuple, |
15
|
|
|
TypeVar, |
16
|
|
|
ContextManager, |
17
|
|
|
AsyncContextManager, |
18
|
|
|
Optional, |
19
|
|
|
) |
20
|
|
|
|
21
|
1 |
|
from .container import Container |
22
|
1 |
|
from .definitions import Singleton, construction, yielding_construction |
23
|
1 |
|
from .exceptions import ( |
24
|
|
|
MissingReturnType, |
25
|
|
|
ClassesCannotBeDecorated, |
26
|
|
|
InvalidDependencyDefinition, |
27
|
|
|
) |
28
|
1 |
|
from .interfaces import SpecialDepDefinition |
29
|
1 |
|
from .util.reflection import reflect |
30
|
|
|
|
31
|
1 |
|
T = TypeVar("T") |
32
|
1 |
|
R = TypeVar("R") |
33
|
|
|
|
34
|
|
|
|
35
|
1 |
|
def bind_to_container( |
36
|
|
|
container: Container, shared: Optional[List[Type]] = None |
37
|
|
|
) -> Callable[[Callable[..., R]], Callable[..., R]]: |
38
|
1 |
|
def _decorator(func): |
39
|
1 |
|
if not isinstance(func, FunctionType): |
40
|
1 |
|
raise ClassesCannotBeDecorated() |
41
|
1 |
|
return wraps(func)(container.partial(func, shared=shared)) |
42
|
|
|
|
43
|
1 |
|
return _decorator |
44
|
|
|
|
45
|
|
|
|
46
|
1 |
|
def magic_bind_to_container( |
47
|
|
|
container: Container, shared: Optional[List[Type]] = None |
48
|
|
|
) -> Callable[[Callable[..., R]], Callable[..., R]]: |
49
|
|
|
"""Decorates the function so that it's uses the container to construct things |
50
|
|
|
|
51
|
|
|
>>> from tests.examples import SomeClass |
52
|
|
|
>>> c = Container() |
53
|
|
|
>>> @magic_bind_to_container(c) |
54
|
|
|
... def say_hello_from(sayer: SomeClass): |
55
|
|
|
... return f"hello from {sayer}" |
56
|
|
|
>>> say_hello_from() |
57
|
|
|
'hello from <tests.examples.SomeClass object at ...>' |
58
|
|
|
|
59
|
|
|
""" |
60
|
|
|
|
61
|
1 |
|
def _decorator(func): |
62
|
1 |
|
if not isinstance(func, FunctionType): |
63
|
1 |
|
raise ClassesCannotBeDecorated() |
64
|
1 |
|
return wraps(func)(container.magic_partial(func, shared=shared)) |
65
|
|
|
|
66
|
1 |
|
return _decorator |
67
|
|
|
|
68
|
|
|
|
69
|
1 |
|
def dependency_definition(container: Container, singleton: bool = False): |
70
|
|
|
"""Registers the provided function with the container |
71
|
|
|
The return type of the decorated function will be reflected and whenever |
72
|
|
|
the container is asked for this type the function will be called |
73
|
|
|
|
74
|
|
|
>>> from tests.examples import SomeClass, SomeExtendedClass |
75
|
|
|
>>> c = Container() |
76
|
|
|
>>> @dependency_definition(c) |
77
|
|
|
... def build_some_class_but_extended() -> SomeClass: |
78
|
|
|
... return SomeExtendedClass() |
79
|
|
|
>>> c.resolve(SomeClass) |
80
|
|
|
<tests.examples.SomeExtendedClass object at ...> |
81
|
|
|
|
82
|
|
|
""" |
83
|
|
|
|
84
|
1 |
|
def _decorator(func): |
85
|
1 |
|
definition_func, return_type = _extract_definition_func_and_type(func) # type: ignore |
86
|
|
|
|
87
|
1 |
|
if singleton: |
88
|
1 |
|
container.define(return_type, Singleton(definition_func)) |
89
|
|
|
else: |
90
|
1 |
|
container.define(return_type, definition_func) |
91
|
1 |
|
return func |
92
|
|
|
|
93
|
1 |
|
return _decorator |
94
|
|
|
|
95
|
|
|
|
96
|
1 |
|
def context_dependency_definition(container: Container): |
97
|
|
|
""" |
98
|
|
|
Turns the decorated function into a definition for a context manager |
99
|
|
|
in the given container. |
100
|
|
|
|
101
|
|
|
>>> from tests.examples import SomeClass |
102
|
|
|
>>> from typing import Iterator |
103
|
|
|
>>> |
104
|
|
|
>>> c = Container() |
105
|
|
|
>>> |
106
|
|
|
>>> @context_dependency_definition(c) |
107
|
|
|
... def my_constructor() -> Iterator[SomeClass]: |
108
|
|
|
... try: |
109
|
|
|
... yield SomeClass() |
110
|
|
|
... finally: |
111
|
|
|
... pass # Any tidy up or resource closing could happen here |
112
|
|
|
>>> with c[ContextManager[SomeClass]] as something: |
113
|
|
|
... something |
114
|
|
|
<tests.examples.SomeClass ...> |
115
|
|
|
|
116
|
|
|
with container[ContextManager[MyComplexDep]] as dep: # type: ignore |
117
|
|
|
assert dep.some_number == 3 |
118
|
|
|
""" |
119
|
|
|
|
120
|
1 |
|
def _decorator(func): |
121
|
1 |
|
if not inspect.isgeneratorfunction(func) and not inspect.isasyncgenfunction( |
122
|
|
|
func |
123
|
|
|
): |
124
|
|
|
raise InvalidDependencyDefinition( |
125
|
|
|
"context_dependency_definition must be given a generator" |
126
|
|
|
) |
127
|
1 |
|
dep_type = _generator_type(reflect(func).return_type) |
128
|
1 |
|
if inspect.isgeneratorfunction(func): |
129
|
1 |
|
container.define(ContextManager[dep_type], contextmanager(func)) # type: ignore |
130
|
1 |
|
if inspect.isasyncgenfunction(func): |
131
|
1 |
|
container.define(AsyncContextManager[dep_type], asynccontextmanager(func)) # type: ignore |
132
|
1 |
|
return func |
133
|
|
|
|
134
|
1 |
|
return _decorator |
135
|
|
|
|
136
|
|
|
|
137
|
1 |
|
def _extract_definition_func_and_type( |
138
|
|
|
func, |
139
|
|
|
) -> Tuple[SpecialDepDefinition, Type[T]]: |
140
|
|
|
""" |
141
|
|
|
Takes a function or a generator and returns a function and the return type. |
142
|
|
|
:param func: |
143
|
|
|
:return: |
144
|
|
|
""" |
145
|
|
|
|
146
|
1 |
|
return_type = reflect(func).return_type |
147
|
1 |
|
if not return_type: |
148
|
1 |
|
raise MissingReturnType( |
149
|
|
|
f"Function {func.__name__} used as a definition must have a return type" |
150
|
|
|
) |
151
|
|
|
|
152
|
1 |
|
if not inspect.isgeneratorfunction(func) and not inspect.isasyncgenfunction(func): |
153
|
1 |
|
return construction(func), return_type |
154
|
|
|
|
155
|
1 |
|
return ( |
156
|
|
|
yielding_construction(func), |
157
|
|
|
_generator_type(return_type), |
158
|
|
|
) |
159
|
|
|
|
160
|
|
|
|
161
|
1 |
|
def _generator_type(return_type): |
162
|
|
|
return return_type.__args__[0] # todo: something less hacky |
163
|
|
|
|