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