cdi-di 0.0.1b1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cdi_di-0.0.1b1/.gitignore +13 -0
- cdi_di-0.0.1b1/.python-version +1 -0
- cdi_di-0.0.1b1/PKG-INFO +271 -0
- cdi_di-0.0.1b1/README.md +263 -0
- cdi_di-0.0.1b1/pyproject.toml +22 -0
- cdi_di-0.0.1b1/src/cdi/__init__.py +22 -0
- cdi_di-0.0.1b1/src/cdi/_container.py +125 -0
- cdi_di-0.0.1b1/src/cdi/_decorators.py +118 -0
- cdi_di-0.0.1b1/src/cdi/_exceptions.py +58 -0
- cdi_di-0.0.1b1/src/cdi/_factory.py +276 -0
- cdi_di-0.0.1b1/src/cdi/_fr_resolver.py +57 -0
- cdi_di-0.0.1b1/src/cdi/_registry.py +23 -0
- cdi_di-0.0.1b1/src/cdi/_scope.py +121 -0
- cdi_di-0.0.1b1/src/cdi/_typing.py +75 -0
- cdi_di-0.0.1b1/src/cdi/py.typed +0 -0
- cdi_di-0.0.1b1/tt/__init__.py +0 -0
- cdi_di-0.0.1b1/tt/c.py +4 -0
- cdi_di-0.0.1b1/tt/s.py +18 -0
- cdi_di-0.0.1b1/tt/test.py +81 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
cdi_di-0.0.1b1/PKG-INFO
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cdi-di
|
|
3
|
+
Version: 0.0.1b1
|
|
4
|
+
Summary: Python dependency injection made easy
|
|
5
|
+
Author-email: daniel sonbolian <dsal3389@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# cdi
|
|
10
|
+
stands for "cute dependency injector" I guess?
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Dependency injection made easy
|
|
14
|
+
while some python dependency injectors require some setup and make some things
|
|
15
|
+
harder to understand for a simple dependency injection, `cdi` aims to simplify
|
|
16
|
+
dependency injection and be fast (relativly to python)
|
|
17
|
+
|
|
18
|
+
```py
|
|
19
|
+
import cdi
|
|
20
|
+
|
|
21
|
+
# this container will contain its own registered types
|
|
22
|
+
ctr = cdi.Container()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# register this function as a factory
|
|
26
|
+
# for the `int` type
|
|
27
|
+
@cdi.Injectable(ctr=ctr)
|
|
28
|
+
def get_int() -> int:
|
|
29
|
+
return 100
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# register `Foo` as injectable
|
|
33
|
+
# so we can create instances
|
|
34
|
+
@cdi.Injectable(ctr=ctr)
|
|
35
|
+
class Foo:
|
|
36
|
+
def __init__(self, number: int) -> None:
|
|
37
|
+
self.number = number
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# create a scope that will have access to registered
|
|
41
|
+
# types in `ctr` then get an instance of `Foo`
|
|
42
|
+
scope = cdi.Scope(cdi)
|
|
43
|
+
instance = scope.get_instance(Foo)
|
|
44
|
+
assert instance.number == 100
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Support Generics
|
|
48
|
+
|
|
49
|
+
```py
|
|
50
|
+
import cdi
|
|
51
|
+
from typing import Generic, TypeVar
|
|
52
|
+
from collection.abc import Sequence
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
T = TypeVar('T')
|
|
56
|
+
|
|
57
|
+
ctr = cdi.Container()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MyBase(Generic[T]):
|
|
61
|
+
def __init__(self, field: T) -> None:
|
|
62
|
+
self.field = field
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@cdi.Injectable(ctr)
|
|
66
|
+
class MyType(MyBase[str]):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@cdi.Injectable(ctr)
|
|
71
|
+
def name_generator() -> str:
|
|
72
|
+
return "foo"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
scope = cdi.Scope(ctr)
|
|
76
|
+
instance = scope.get_instance(MyType)
|
|
77
|
+
assert instance.field == "foo"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### what is not supported with generics (at least yet)
|
|
81
|
+
* TypeAliases as parameters are not supported (i.e `list[int]`)
|
|
82
|
+
* TypeAliases as injectable return type
|
|
83
|
+
* TypeVars as parameters
|
|
84
|
+
* Typevars as injectable return type
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### Explicit is better then implicit
|
|
88
|
+
the library tries to make you explicit with your typing without compromising
|
|
89
|
+
readability or ease of use
|
|
90
|
+
|
|
91
|
+
# Documentation
|
|
92
|
+
|
|
93
|
+
## Container
|
|
94
|
+
container defines the scope of available types for injections, if you have
|
|
95
|
+
a `Scope` that want `int`, it will try to get the `int` factory from his container
|
|
96
|
+
|
|
97
|
+
thus you can have multiple `Containers` containing different types and provide separation
|
|
98
|
+
but most of the time you will be using a single global container
|
|
99
|
+
|
|
100
|
+
```py
|
|
101
|
+
ctr = cdi.Container()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Inject factories
|
|
105
|
+
Containers contain is almost like a register of `Factories`, a factory can
|
|
106
|
+
be injected only through the `Injectable` class
|
|
107
|
+
|
|
108
|
+
## Forward references
|
|
109
|
+
some factories may have unresolved forward references in their return type or parameters, when evaluated
|
|
110
|
+
it is impossible to know what type sits behind those forward ref strings
|
|
111
|
+
|
|
112
|
+
```py
|
|
113
|
+
class Foo:
|
|
114
|
+
# what is the `Boo` type? we just see a string
|
|
115
|
+
def __init__(self, boo: 'Boo') -> None: ...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
such factories will not be usable for injection, to resolve forward refs
|
|
119
|
+
the container class provide `update_forward_ref` which takes the module you want to update the forward refs for, this takes insperation
|
|
120
|
+
from `Pydantic/v1`
|
|
121
|
+
|
|
122
|
+
the `update_forward_ref` has to be called after there is a class that can evaluate the forward ref
|
|
123
|
+
|
|
124
|
+
```py
|
|
125
|
+
import sys
|
|
126
|
+
|
|
127
|
+
@cdi.Injectable(ctr=ctr)
|
|
128
|
+
class Foo:
|
|
129
|
+
# references `Boo` which is not defined yet
|
|
130
|
+
def __init__(self, boo: 'Boo') -> None: ...
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@cdi.Injectable(ctr=ctr)
|
|
134
|
+
class Boo: ...
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# now that `Boo` is defined, we can update the factories
|
|
138
|
+
# in our current module
|
|
139
|
+
ctr.update_forward_ref(sys.modules[__name__])
|
|
140
|
+
|
|
141
|
+
# works fine
|
|
142
|
+
instance = Scope(__name__, ctr=ctr).get_instance(Foo)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Injectable
|
|
146
|
+
injectable is a type that creates factories based on the given type and registers
|
|
147
|
+
them with the given container
|
|
148
|
+
|
|
149
|
+
```py
|
|
150
|
+
ctr = cdi.Container()
|
|
151
|
+
injector = cdi.Injectable(ctr=ctr)
|
|
152
|
+
|
|
153
|
+
injector.register(Foo)
|
|
154
|
+
injector.register(my_func)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
it can also be used as a decorator
|
|
158
|
+
|
|
159
|
+
```py
|
|
160
|
+
ctr = cdi.Container()
|
|
161
|
+
|
|
162
|
+
@cdi.Injectable(ctr=ctr)
|
|
163
|
+
class Foo: ...
|
|
164
|
+
|
|
165
|
+
@cdi.Injectable(ctr=ctr)
|
|
166
|
+
def my_func() -> int: ...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
when creating the factory, the injector relys on the provided type hints
|
|
170
|
+
|
|
171
|
+
### Classes
|
|
172
|
+
when registering a class, the dependencies are taken from the class `__init__` signature, and the factory
|
|
173
|
+
implementation (what is called to return the type) uses the class `__call__`
|
|
174
|
+
|
|
175
|
+
### Functions
|
|
176
|
+
on functions, the function signature will be used to determin the parameters and return types, calling the factory
|
|
177
|
+
will call the provided function at the end
|
|
178
|
+
|
|
179
|
+
### Constant
|
|
180
|
+
a constant can be injected into the container, the constant type will be the factory return type, and all scopes
|
|
181
|
+
that require this type will evaluate to the constant, acting as a "global variable" in a container
|
|
182
|
+
for example
|
|
183
|
+
```py
|
|
184
|
+
ctr = cdi.Container()
|
|
185
|
+
cdi.Injectable().register("hello world")
|
|
186
|
+
|
|
187
|
+
scope = cdi.Scope(__name__, ctr=ctr)
|
|
188
|
+
assert scope.get_instance(str) == "hello world"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Scopes
|
|
192
|
+
scopes provide sepration of live instances, they use the containers to get the factory, and they call
|
|
193
|
+
the factory to create a live instance that will be injected
|
|
194
|
+
|
|
195
|
+
```py
|
|
196
|
+
ctr = cdi.Container()
|
|
197
|
+
|
|
198
|
+
# we inject the `Foo` class into the `ctr` container
|
|
199
|
+
@cdi.Injectable(ctr=ctr)
|
|
200
|
+
class Foo:
|
|
201
|
+
def __init__(self, number: int) -> None:
|
|
202
|
+
self.number = number
|
|
203
|
+
|
|
204
|
+
cdi.Injectable(ctr=ctr).register(100)
|
|
205
|
+
|
|
206
|
+
# we define an instance scope that has access to the injectable
|
|
207
|
+
# registered in `ctr`
|
|
208
|
+
scope = cdi.Scope(__name__, ctr)
|
|
209
|
+
instance = scope.get_instance(Foo)
|
|
210
|
+
instance2 = scope.get_instance(Foo)
|
|
211
|
+
|
|
212
|
+
# the `Foo` will be evaluated only once and be reused
|
|
213
|
+
# for future calls
|
|
214
|
+
assert instance is instance2
|
|
215
|
+
assert instance.number == 100
|
|
216
|
+
|
|
217
|
+
scope2 = Scope(__name__ + '2', ctr)
|
|
218
|
+
scope2_instance = scope.get_instance(Foo)
|
|
219
|
+
|
|
220
|
+
# a different scopes don't have access to each other instances
|
|
221
|
+
# although they are using the same container
|
|
222
|
+
assert scope2_instance is not instance
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### inheritance
|
|
226
|
+
scopes can inherit parent and child like inheritance, the parent has no access to the child
|
|
227
|
+
but the child does have access to the parent
|
|
228
|
+
|
|
229
|
+
there is no unique behavior for the child/parent scope when they aquire the relevant roles, this is mostly
|
|
230
|
+
for ease of use, the real inheritance comes into play via `InjectableMetadata`
|
|
231
|
+
|
|
232
|
+
## annotation Metadata
|
|
233
|
+
you can change some default behaviors of the injectable type but in a way that make
|
|
234
|
+
sense, meaning, if you annotate `str` you cannot return `int`
|
|
235
|
+
|
|
236
|
+
types annotated with a metdata class `InjectableMetadata` is able to control some default behavior
|
|
237
|
+
of the scope
|
|
238
|
+
|
|
239
|
+
### provider_scope
|
|
240
|
+
accepts a `Callable[[Scope], Scope]`, this effect which scope will instantiate the annotated type
|
|
241
|
+
the returned scope will be used for the type instanciation
|
|
242
|
+
|
|
243
|
+
```py
|
|
244
|
+
ctr = cdi.Container()
|
|
245
|
+
ctr2 = cdi.Container()
|
|
246
|
+
|
|
247
|
+
cdi.Injctable(ctr=ctr).register("hello world")
|
|
248
|
+
cdi.Injctable(ctr=ctr2).register("what?")
|
|
249
|
+
|
|
250
|
+
scope = Scope(__name__, ctr=ctr)
|
|
251
|
+
scope2 = scope.fork()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@cdi.Injectable(ctr=ctr2)
|
|
255
|
+
class Foo:
|
|
256
|
+
def __init__(
|
|
257
|
+
self,
|
|
258
|
+
value1: str,
|
|
259
|
+
value2: Annotated[
|
|
260
|
+
str,
|
|
261
|
+
cdi.InjectableMetadata(provider_scope=lambda scope: scope.parent) # get the str from the parent scope
|
|
262
|
+
]
|
|
263
|
+
) -> None:
|
|
264
|
+
self.value1 = value1
|
|
265
|
+
self.value2 = value2
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
instance = scope2.get_instance(Foo)
|
|
269
|
+
assert instance.value1 == "what?"
|
|
270
|
+
assert instance.value2 == "hello world"
|
|
271
|
+
```
|
cdi_di-0.0.1b1/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# cdi
|
|
2
|
+
stands for "cute dependency injector" I guess?
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## Dependency injection made easy
|
|
6
|
+
while some python dependency injectors require some setup and make some things
|
|
7
|
+
harder to understand for a simple dependency injection, `cdi` aims to simplify
|
|
8
|
+
dependency injection and be fast (relativly to python)
|
|
9
|
+
|
|
10
|
+
```py
|
|
11
|
+
import cdi
|
|
12
|
+
|
|
13
|
+
# this container will contain its own registered types
|
|
14
|
+
ctr = cdi.Container()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# register this function as a factory
|
|
18
|
+
# for the `int` type
|
|
19
|
+
@cdi.Injectable(ctr=ctr)
|
|
20
|
+
def get_int() -> int:
|
|
21
|
+
return 100
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# register `Foo` as injectable
|
|
25
|
+
# so we can create instances
|
|
26
|
+
@cdi.Injectable(ctr=ctr)
|
|
27
|
+
class Foo:
|
|
28
|
+
def __init__(self, number: int) -> None:
|
|
29
|
+
self.number = number
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# create a scope that will have access to registered
|
|
33
|
+
# types in `ctr` then get an instance of `Foo`
|
|
34
|
+
scope = cdi.Scope(cdi)
|
|
35
|
+
instance = scope.get_instance(Foo)
|
|
36
|
+
assert instance.number == 100
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Support Generics
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
import cdi
|
|
43
|
+
from typing import Generic, TypeVar
|
|
44
|
+
from collection.abc import Sequence
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
T = TypeVar('T')
|
|
48
|
+
|
|
49
|
+
ctr = cdi.Container()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MyBase(Generic[T]):
|
|
53
|
+
def __init__(self, field: T) -> None:
|
|
54
|
+
self.field = field
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@cdi.Injectable(ctr)
|
|
58
|
+
class MyType(MyBase[str]):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@cdi.Injectable(ctr)
|
|
63
|
+
def name_generator() -> str:
|
|
64
|
+
return "foo"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
scope = cdi.Scope(ctr)
|
|
68
|
+
instance = scope.get_instance(MyType)
|
|
69
|
+
assert instance.field == "foo"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### what is not supported with generics (at least yet)
|
|
73
|
+
* TypeAliases as parameters are not supported (i.e `list[int]`)
|
|
74
|
+
* TypeAliases as injectable return type
|
|
75
|
+
* TypeVars as parameters
|
|
76
|
+
* Typevars as injectable return type
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Explicit is better then implicit
|
|
80
|
+
the library tries to make you explicit with your typing without compromising
|
|
81
|
+
readability or ease of use
|
|
82
|
+
|
|
83
|
+
# Documentation
|
|
84
|
+
|
|
85
|
+
## Container
|
|
86
|
+
container defines the scope of available types for injections, if you have
|
|
87
|
+
a `Scope` that want `int`, it will try to get the `int` factory from his container
|
|
88
|
+
|
|
89
|
+
thus you can have multiple `Containers` containing different types and provide separation
|
|
90
|
+
but most of the time you will be using a single global container
|
|
91
|
+
|
|
92
|
+
```py
|
|
93
|
+
ctr = cdi.Container()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Inject factories
|
|
97
|
+
Containers contain is almost like a register of `Factories`, a factory can
|
|
98
|
+
be injected only through the `Injectable` class
|
|
99
|
+
|
|
100
|
+
## Forward references
|
|
101
|
+
some factories may have unresolved forward references in their return type or parameters, when evaluated
|
|
102
|
+
it is impossible to know what type sits behind those forward ref strings
|
|
103
|
+
|
|
104
|
+
```py
|
|
105
|
+
class Foo:
|
|
106
|
+
# what is the `Boo` type? we just see a string
|
|
107
|
+
def __init__(self, boo: 'Boo') -> None: ...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
such factories will not be usable for injection, to resolve forward refs
|
|
111
|
+
the container class provide `update_forward_ref` which takes the module you want to update the forward refs for, this takes insperation
|
|
112
|
+
from `Pydantic/v1`
|
|
113
|
+
|
|
114
|
+
the `update_forward_ref` has to be called after there is a class that can evaluate the forward ref
|
|
115
|
+
|
|
116
|
+
```py
|
|
117
|
+
import sys
|
|
118
|
+
|
|
119
|
+
@cdi.Injectable(ctr=ctr)
|
|
120
|
+
class Foo:
|
|
121
|
+
# references `Boo` which is not defined yet
|
|
122
|
+
def __init__(self, boo: 'Boo') -> None: ...
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@cdi.Injectable(ctr=ctr)
|
|
126
|
+
class Boo: ...
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# now that `Boo` is defined, we can update the factories
|
|
130
|
+
# in our current module
|
|
131
|
+
ctr.update_forward_ref(sys.modules[__name__])
|
|
132
|
+
|
|
133
|
+
# works fine
|
|
134
|
+
instance = Scope(__name__, ctr=ctr).get_instance(Foo)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Injectable
|
|
138
|
+
injectable is a type that creates factories based on the given type and registers
|
|
139
|
+
them with the given container
|
|
140
|
+
|
|
141
|
+
```py
|
|
142
|
+
ctr = cdi.Container()
|
|
143
|
+
injector = cdi.Injectable(ctr=ctr)
|
|
144
|
+
|
|
145
|
+
injector.register(Foo)
|
|
146
|
+
injector.register(my_func)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
it can also be used as a decorator
|
|
150
|
+
|
|
151
|
+
```py
|
|
152
|
+
ctr = cdi.Container()
|
|
153
|
+
|
|
154
|
+
@cdi.Injectable(ctr=ctr)
|
|
155
|
+
class Foo: ...
|
|
156
|
+
|
|
157
|
+
@cdi.Injectable(ctr=ctr)
|
|
158
|
+
def my_func() -> int: ...
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
when creating the factory, the injector relys on the provided type hints
|
|
162
|
+
|
|
163
|
+
### Classes
|
|
164
|
+
when registering a class, the dependencies are taken from the class `__init__` signature, and the factory
|
|
165
|
+
implementation (what is called to return the type) uses the class `__call__`
|
|
166
|
+
|
|
167
|
+
### Functions
|
|
168
|
+
on functions, the function signature will be used to determin the parameters and return types, calling the factory
|
|
169
|
+
will call the provided function at the end
|
|
170
|
+
|
|
171
|
+
### Constant
|
|
172
|
+
a constant can be injected into the container, the constant type will be the factory return type, and all scopes
|
|
173
|
+
that require this type will evaluate to the constant, acting as a "global variable" in a container
|
|
174
|
+
for example
|
|
175
|
+
```py
|
|
176
|
+
ctr = cdi.Container()
|
|
177
|
+
cdi.Injectable().register("hello world")
|
|
178
|
+
|
|
179
|
+
scope = cdi.Scope(__name__, ctr=ctr)
|
|
180
|
+
assert scope.get_instance(str) == "hello world"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Scopes
|
|
184
|
+
scopes provide sepration of live instances, they use the containers to get the factory, and they call
|
|
185
|
+
the factory to create a live instance that will be injected
|
|
186
|
+
|
|
187
|
+
```py
|
|
188
|
+
ctr = cdi.Container()
|
|
189
|
+
|
|
190
|
+
# we inject the `Foo` class into the `ctr` container
|
|
191
|
+
@cdi.Injectable(ctr=ctr)
|
|
192
|
+
class Foo:
|
|
193
|
+
def __init__(self, number: int) -> None:
|
|
194
|
+
self.number = number
|
|
195
|
+
|
|
196
|
+
cdi.Injectable(ctr=ctr).register(100)
|
|
197
|
+
|
|
198
|
+
# we define an instance scope that has access to the injectable
|
|
199
|
+
# registered in `ctr`
|
|
200
|
+
scope = cdi.Scope(__name__, ctr)
|
|
201
|
+
instance = scope.get_instance(Foo)
|
|
202
|
+
instance2 = scope.get_instance(Foo)
|
|
203
|
+
|
|
204
|
+
# the `Foo` will be evaluated only once and be reused
|
|
205
|
+
# for future calls
|
|
206
|
+
assert instance is instance2
|
|
207
|
+
assert instance.number == 100
|
|
208
|
+
|
|
209
|
+
scope2 = Scope(__name__ + '2', ctr)
|
|
210
|
+
scope2_instance = scope.get_instance(Foo)
|
|
211
|
+
|
|
212
|
+
# a different scopes don't have access to each other instances
|
|
213
|
+
# although they are using the same container
|
|
214
|
+
assert scope2_instance is not instance
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### inheritance
|
|
218
|
+
scopes can inherit parent and child like inheritance, the parent has no access to the child
|
|
219
|
+
but the child does have access to the parent
|
|
220
|
+
|
|
221
|
+
there is no unique behavior for the child/parent scope when they aquire the relevant roles, this is mostly
|
|
222
|
+
for ease of use, the real inheritance comes into play via `InjectableMetadata`
|
|
223
|
+
|
|
224
|
+
## annotation Metadata
|
|
225
|
+
you can change some default behaviors of the injectable type but in a way that make
|
|
226
|
+
sense, meaning, if you annotate `str` you cannot return `int`
|
|
227
|
+
|
|
228
|
+
types annotated with a metdata class `InjectableMetadata` is able to control some default behavior
|
|
229
|
+
of the scope
|
|
230
|
+
|
|
231
|
+
### provider_scope
|
|
232
|
+
accepts a `Callable[[Scope], Scope]`, this effect which scope will instantiate the annotated type
|
|
233
|
+
the returned scope will be used for the type instanciation
|
|
234
|
+
|
|
235
|
+
```py
|
|
236
|
+
ctr = cdi.Container()
|
|
237
|
+
ctr2 = cdi.Container()
|
|
238
|
+
|
|
239
|
+
cdi.Injctable(ctr=ctr).register("hello world")
|
|
240
|
+
cdi.Injctable(ctr=ctr2).register("what?")
|
|
241
|
+
|
|
242
|
+
scope = Scope(__name__, ctr=ctr)
|
|
243
|
+
scope2 = scope.fork()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@cdi.Injectable(ctr=ctr2)
|
|
247
|
+
class Foo:
|
|
248
|
+
def __init__(
|
|
249
|
+
self,
|
|
250
|
+
value1: str,
|
|
251
|
+
value2: Annotated[
|
|
252
|
+
str,
|
|
253
|
+
cdi.InjectableMetadata(provider_scope=lambda scope: scope.parent) # get the str from the parent scope
|
|
254
|
+
]
|
|
255
|
+
) -> None:
|
|
256
|
+
self.value1 = value1
|
|
257
|
+
self.value2 = value2
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
instance = scope2.get_instance(Foo)
|
|
261
|
+
assert instance.value1 == "what?"
|
|
262
|
+
assert instance.value2 == "hello world"
|
|
263
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cdi-di"
|
|
3
|
+
version = "0.0.1-b1"
|
|
4
|
+
description = "Python dependency injection made easy"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "daniel sonbolian", email = "dsal3389@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[tool.hatch.build.targets.wheel]
|
|
13
|
+
packages = ["src/cdi"]
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
|
|
19
|
+
[dependency-groups]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=8.4.2",
|
|
22
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ._container import Container
|
|
2
|
+
from ._exceptions import (
|
|
3
|
+
CdiError,
|
|
4
|
+
IncorrectStackPopping,
|
|
5
|
+
CircularDependencyError,
|
|
6
|
+
TypeEvaluationError,
|
|
7
|
+
)
|
|
8
|
+
from ._scope import Scope
|
|
9
|
+
from ._decorators import Injectable
|
|
10
|
+
from ._typing import InjectableMetadata
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"Scope",
|
|
15
|
+
"Container",
|
|
16
|
+
"Injectable",
|
|
17
|
+
"InjectableMetadata",
|
|
18
|
+
"CdiError",
|
|
19
|
+
"TypeEvaluationError",
|
|
20
|
+
"IncorrectStackPopping",
|
|
21
|
+
"CircularDependencyError",
|
|
22
|
+
)
|