dinkleberg 0.2.1__py3-none-any.whl → 0.3.1__py3-none-any.whl
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.
- dinkleberg/__init__.py +2 -2
- dinkleberg/dependency_configurator.py +138 -51
- dinkleberg/descriptor.py +1 -0
- dinkleberg/fastapi/__init__.py +13 -0
- dinkleberg/typing.py +5 -0
- {dinkleberg-0.2.1.dist-info → dinkleberg-0.3.1.dist-info}/METADATA +4 -1
- dinkleberg-0.3.1.dist-info/RECORD +9 -0
- dinkleberg/dependency.py +0 -2
- dinkleberg/dependency_scope.py +0 -15
- dinkleberg-0.2.1.dist-info/RECORD +0 -10
- {dinkleberg-0.2.1.dist-info → dinkleberg-0.3.1.dist-info}/WHEEL +0 -0
- {dinkleberg-0.2.1.dist-info → dinkleberg-0.3.1.dist-info}/top_level.txt +0 -0
dinkleberg/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from dinkleberg_abc import Dependency, DependencyScope
|
|
2
|
+
|
|
2
3
|
from .dependency_configurator import DependencyConfigurator
|
|
3
|
-
from .dependency_scope import DependencyScope
|
|
4
4
|
|
|
5
5
|
__all__ = ['DependencyScope', 'DependencyConfigurator', 'Dependency']
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import abc
|
|
2
1
|
import asyncio
|
|
3
2
|
import inspect
|
|
4
3
|
import logging
|
|
4
|
+
from functools import wraps
|
|
5
5
|
from inspect import Signature
|
|
6
6
|
from types import MappingProxyType
|
|
7
7
|
from typing import AsyncGenerator, Callable, overload, get_type_hints, Mapping, get_origin
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from .dependency_scope import DependencyScope
|
|
9
|
+
from dinkleberg_abc import DependencyScope, Dependency
|
|
11
10
|
from .descriptor import Descriptor, Lifetime
|
|
12
|
-
from .typing import get_static_params, get_public_methods, is_builtin_type
|
|
11
|
+
from .typing import get_static_params, get_public_methods, is_builtin_type, is_abstract
|
|
13
12
|
|
|
14
13
|
logger = logging.getLogger(__name__)
|
|
15
14
|
|
|
@@ -58,13 +57,45 @@ class DependencyConfigurator(DependencyScope):
|
|
|
58
57
|
if exceptions:
|
|
59
58
|
raise ExceptionGroup('Errors occurred during closing DependencyConfigurator', exceptions)
|
|
60
59
|
|
|
61
|
-
def _add(self, lifetime: Lifetime, *, t: type = None,
|
|
62
|
-
callable: Callable = None):
|
|
63
|
-
if generator is None and callable is None:
|
|
64
|
-
raise ValueError(
|
|
60
|
+
def _add(self, lifetime: Lifetime, *, t: type = None, i: type = None,
|
|
61
|
+
generator: Callable[..., AsyncGenerator] = None, callable: Callable = None):
|
|
62
|
+
if t is None and i is None and generator is None and callable is None:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
'Invalid dependency registration. At least one of t, i, generator, or callable must be provided.')
|
|
65
|
+
|
|
66
|
+
if lifetime == 'singleton' and self._parent is not None:
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
'Singleton dependencies, which are not instances, can only be registered in the root DependencyConfigurator.')
|
|
69
|
+
|
|
70
|
+
if i is not None:
|
|
71
|
+
if is_builtin_type(i):
|
|
72
|
+
raise ValueError(f'Cannot use built-in type {i} as implementation.')
|
|
73
|
+
|
|
74
|
+
if get_origin(i) is not None:
|
|
75
|
+
raise ValueError(f'Cannot use generic type {i} as implementation.')
|
|
76
|
+
|
|
77
|
+
if is_abstract(i):
|
|
78
|
+
raise ValueError(f'Cannot use abstract class {i} as implementation.')
|
|
79
|
+
|
|
65
80
|
if t is None:
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
if i is not None:
|
|
82
|
+
t = i
|
|
83
|
+
else:
|
|
84
|
+
t = self._infer_type(generator=generator, callable=callable)
|
|
85
|
+
elif generator is None and callable is None:
|
|
86
|
+
if is_builtin_type(t):
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f'Cannot register built-in type {t} without explicit implementation, generator or callable.')
|
|
89
|
+
|
|
90
|
+
if get_origin(t) is not None:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f'Cannot register generic type {t} without explicit implementation, generator or callable.')
|
|
93
|
+
|
|
94
|
+
if is_abstract(t):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f'Cannot register abstract class {t} without explicit implementation, generator or callable.')
|
|
97
|
+
|
|
98
|
+
self._descriptors[t] = Descriptor(implementation=i, generator=generator, callable=callable, lifetime=lifetime)
|
|
68
99
|
|
|
69
100
|
@staticmethod
|
|
70
101
|
def _infer_type(*, generator: Callable[..., AsyncGenerator], callable: Callable) -> type:
|
|
@@ -105,9 +136,15 @@ class DependencyConfigurator(DependencyScope):
|
|
|
105
136
|
|
|
106
137
|
# TODO circular dependency detection
|
|
107
138
|
# TODO singleton race condition prevention (async.Lock)
|
|
108
|
-
async def resolve[T](self, t: type[T], **kwargs) -> T:
|
|
139
|
+
async def resolve[T](self, t: type[T] | Callable, **kwargs) -> T:
|
|
109
140
|
self._raise_if_closed()
|
|
110
141
|
|
|
142
|
+
if not inspect.isclass(t):
|
|
143
|
+
if not inspect.isfunction(t):
|
|
144
|
+
raise ValueError(f'Cannot resolve type {t}. Only classes and functions are supported.')
|
|
145
|
+
|
|
146
|
+
return self._wrap_func(t)
|
|
147
|
+
|
|
111
148
|
if t == DependencyScope:
|
|
112
149
|
return self
|
|
113
150
|
|
|
@@ -117,8 +154,26 @@ class DependencyConfigurator(DependencyScope):
|
|
|
117
154
|
if t in self._scoped_instances:
|
|
118
155
|
return self._scoped_instances[t]
|
|
119
156
|
|
|
120
|
-
|
|
121
|
-
|
|
157
|
+
descriptor = self._descriptors.get(t)
|
|
158
|
+
if descriptor is None or descriptor['generator'] is None and descriptor['callable'] is None:
|
|
159
|
+
if descriptor is None:
|
|
160
|
+
if is_builtin_type(t):
|
|
161
|
+
raise ValueError(f'Cannot resolve built-in type {t} without explicit registration.')
|
|
162
|
+
|
|
163
|
+
if get_origin(t) is not None:
|
|
164
|
+
raise ValueError(f'Cannot resolve generic type {t} without explicit registration.')
|
|
165
|
+
|
|
166
|
+
if is_abstract(t):
|
|
167
|
+
raise ValueError(f'Cannot resolve abstract class {t} without explicit registration.')
|
|
168
|
+
|
|
169
|
+
factory = t
|
|
170
|
+
else:
|
|
171
|
+
factory = descriptor['implementation'] or t
|
|
172
|
+
|
|
173
|
+
is_generator = False
|
|
174
|
+
lifetime = descriptor['lifetime'] if descriptor else 'transient'
|
|
175
|
+
deps = await self._resolve_deps(factory.__init__)
|
|
176
|
+
else:
|
|
122
177
|
lifetime = descriptor['lifetime']
|
|
123
178
|
if lifetime == 'singleton' and self._parent:
|
|
124
179
|
# we need to resolve singleton from the root scope
|
|
@@ -127,20 +182,6 @@ class DependencyConfigurator(DependencyScope):
|
|
|
127
182
|
is_generator = descriptor['generator'] is not None
|
|
128
183
|
factory = descriptor['generator'] or descriptor['callable']
|
|
129
184
|
deps = await self._resolve_deps(factory)
|
|
130
|
-
else:
|
|
131
|
-
if is_builtin_type(t):
|
|
132
|
-
raise ValueError(f'Cannot resolve built-in type {t} without explicit registration.')
|
|
133
|
-
|
|
134
|
-
if get_origin(t) is not None:
|
|
135
|
-
raise ValueError(f'Cannot resolve generic type {t} without explicit registration.')
|
|
136
|
-
|
|
137
|
-
if inspect.isabstract(t) or t is abc.ABC:
|
|
138
|
-
raise ValueError(f'Cannot resolve abstract class {t} without explicit registration.')
|
|
139
|
-
|
|
140
|
-
is_generator = False
|
|
141
|
-
lifetime = 'transient'
|
|
142
|
-
factory = t
|
|
143
|
-
deps = await self._resolve_deps(t.__init__)
|
|
144
185
|
|
|
145
186
|
if is_generator:
|
|
146
187
|
generator = factory(**deps, **kwargs)
|
|
@@ -215,6 +256,28 @@ class DependencyConfigurator(DependencyScope):
|
|
|
215
256
|
|
|
216
257
|
return actual_kwargs
|
|
217
258
|
|
|
259
|
+
def _wrap_func(self, func: Callable):
|
|
260
|
+
signature = inspect.signature(func)
|
|
261
|
+
|
|
262
|
+
dep_params = MappingProxyType({
|
|
263
|
+
param_name: param
|
|
264
|
+
for param_name, param in signature.parameters.items()
|
|
265
|
+
if isinstance(param.default, Dependency)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
if not dep_params:
|
|
269
|
+
return func
|
|
270
|
+
|
|
271
|
+
if not asyncio.iscoroutinefunction(func):
|
|
272
|
+
raise NotImplementedError('Synchronous functions with Dependency() defaults are not supported.')
|
|
273
|
+
|
|
274
|
+
@wraps(func)
|
|
275
|
+
async def wrapped_func(*args, **kwargs):
|
|
276
|
+
new_kwargs = await self._resolve_kwargs(signature, func.__name__, args, kwargs, dep_params)
|
|
277
|
+
return await func(*args, **new_kwargs)
|
|
278
|
+
|
|
279
|
+
return wrapped_func
|
|
280
|
+
|
|
218
281
|
# TODO handle __slots__
|
|
219
282
|
def _wrap_instance(self, instance):
|
|
220
283
|
if getattr(instance, '__dinkleberg__', False):
|
|
@@ -222,25 +285,11 @@ class DependencyConfigurator(DependencyScope):
|
|
|
222
285
|
|
|
223
286
|
methods = get_public_methods(instance)
|
|
224
287
|
for name, value in methods:
|
|
225
|
-
signature = inspect.signature(value)
|
|
226
|
-
|
|
227
|
-
dep_params = MappingProxyType({
|
|
228
|
-
param_name: param
|
|
229
|
-
for param_name, param in signature.parameters.items()
|
|
230
|
-
if isinstance(param.default, Dependency)
|
|
231
|
-
})
|
|
232
|
-
if not dep_params:
|
|
233
|
-
continue
|
|
234
|
-
|
|
235
288
|
instance_method = getattr(instance, name)
|
|
236
|
-
if asyncio.iscoroutinefunction(instance_method):
|
|
237
|
-
async def wrapped_method(*args, __m=instance_method, __s=signature, __n=name, __d=dep_params, **kwargs):
|
|
238
|
-
new_kwargs = await self._resolve_kwargs(__s, __n, args, kwargs, __d)
|
|
239
|
-
return await __m(*args, **new_kwargs)
|
|
240
289
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
290
|
+
wrapped_method = self._wrap_func(instance_method)
|
|
291
|
+
|
|
292
|
+
setattr(instance, name, wrapped_method)
|
|
244
293
|
|
|
245
294
|
try:
|
|
246
295
|
setattr(instance, '__dinkleberg__', True)
|
|
@@ -256,6 +305,18 @@ class DependencyConfigurator(DependencyScope):
|
|
|
256
305
|
def add_singleton[T, I](self, *, t: type[T], instance: I):
|
|
257
306
|
...
|
|
258
307
|
|
|
308
|
+
@overload
|
|
309
|
+
def add_singleton[T](self, *, t: type[T]):
|
|
310
|
+
...
|
|
311
|
+
|
|
312
|
+
@overload
|
|
313
|
+
def add_singleton[I](self, *, i: type[I]):
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
@overload
|
|
317
|
+
def add_singleton[T, I](self, *, t: type[T], i: type[I]):
|
|
318
|
+
...
|
|
319
|
+
|
|
259
320
|
@overload
|
|
260
321
|
def add_singleton[I](self, *, callable: Callable[..., I]):
|
|
261
322
|
...
|
|
@@ -272,11 +333,13 @@ class DependencyConfigurator(DependencyScope):
|
|
|
272
333
|
def add_singleton[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]]):
|
|
273
334
|
...
|
|
274
335
|
|
|
275
|
-
def add_singleton[T, I](self, *, t: type[T] = None,
|
|
336
|
+
def add_singleton[T, I](self, *, t: type[T] = None, i: type[I] = None,
|
|
337
|
+
generator: Callable[..., AsyncGenerator[I]] = None,
|
|
276
338
|
callable: Callable[..., I] = None, instance: I = None):
|
|
277
339
|
self._raise_if_closed()
|
|
340
|
+
|
|
278
341
|
if instance is None:
|
|
279
|
-
self._add('singleton', t=t, generator=generator, callable=callable)
|
|
342
|
+
self._add('singleton', t=t, i=i, generator=generator, callable=callable)
|
|
280
343
|
return
|
|
281
344
|
elif t is None:
|
|
282
345
|
t = type(instance)
|
|
@@ -285,6 +348,18 @@ class DependencyConfigurator(DependencyScope):
|
|
|
285
348
|
|
|
286
349
|
self._singleton_instances[t] = instance
|
|
287
350
|
|
|
351
|
+
@overload
|
|
352
|
+
def add_scoped[T](self, *, t: type[T]):
|
|
353
|
+
...
|
|
354
|
+
|
|
355
|
+
@overload
|
|
356
|
+
def add_scoped[I](self, *, i: type[I]):
|
|
357
|
+
...
|
|
358
|
+
|
|
359
|
+
@overload
|
|
360
|
+
def add_scoped[T, I](self, *, t: type[T], i: type[I]):
|
|
361
|
+
...
|
|
362
|
+
|
|
288
363
|
@overload
|
|
289
364
|
def add_scoped[I](self, *, callable: Callable[..., I]):
|
|
290
365
|
...
|
|
@@ -301,10 +376,22 @@ class DependencyConfigurator(DependencyScope):
|
|
|
301
376
|
def add_scoped[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]]):
|
|
302
377
|
...
|
|
303
378
|
|
|
304
|
-
def add_scoped[T, I](self, *, t: type[T] = None,
|
|
305
|
-
callable: Callable[..., I] = None):
|
|
379
|
+
def add_scoped[T, I](self, *, t: type[T] = None, i: type[I] = None,
|
|
380
|
+
generator: Callable[..., AsyncGenerator[I]] = None, callable: Callable[..., I] = None):
|
|
306
381
|
self._raise_if_closed()
|
|
307
|
-
self._add('scoped', t=t, generator=generator, callable=callable)
|
|
382
|
+
self._add('scoped', t=t, i=i, generator=generator, callable=callable)
|
|
383
|
+
|
|
384
|
+
@overload
|
|
385
|
+
def add_transient[T](self, *, t: type[T]):
|
|
386
|
+
...
|
|
387
|
+
|
|
388
|
+
@overload
|
|
389
|
+
def add_transient[I](self, *, i: type[I]):
|
|
390
|
+
...
|
|
391
|
+
|
|
392
|
+
@overload
|
|
393
|
+
def add_transient[T, I](self, *, t: type[T], i: type[I]):
|
|
394
|
+
...
|
|
308
395
|
|
|
309
396
|
@overload
|
|
310
397
|
def add_transient[I](self, *, callable: Callable[..., I]):
|
|
@@ -314,6 +401,6 @@ class DependencyConfigurator(DependencyScope):
|
|
|
314
401
|
def add_transient[T, I](self, *, t: type[T], callable: Callable[..., I]):
|
|
315
402
|
...
|
|
316
403
|
|
|
317
|
-
def add_transient[T, I](self, *, t: type[T] = None, callable: Callable[..., I] = None):
|
|
404
|
+
def add_transient[T, I](self, *, t: type[T] = None, i: type[I] = None, callable: Callable[..., I] = None):
|
|
318
405
|
self._raise_if_closed()
|
|
319
|
-
self._add('transient', t=t, callable=callable)
|
|
406
|
+
self._add('transient', i=i, t=t, callable=callable)
|
dinkleberg/descriptor.py
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from _dinkleberg_fastapi import di
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from _dinkleberg_fastapi import di
|
|
8
|
+
|
|
9
|
+
__all__ = ['di']
|
|
10
|
+
except ImportError:
|
|
11
|
+
raise ImportError(
|
|
12
|
+
"dinkleberg-fastapi is not installed. Please install it with 'pip install dinkleberg[fastapi]' to use FastAPI integration."
|
|
13
|
+
)
|
dinkleberg/typing.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import abc
|
|
1
2
|
import inspect
|
|
2
3
|
from inspect import Parameter, signature
|
|
3
4
|
from typing import Callable, get_origin
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
def is_abstract(t: type) -> bool:
|
|
8
|
+
return inspect.isabstract(t) or t is abc.ABC
|
|
9
|
+
|
|
10
|
+
|
|
6
11
|
def is_builtin_type(t: type) -> bool:
|
|
7
12
|
origin = get_origin(t) or t
|
|
8
13
|
return getattr(origin, '__module__', None) in ('builtins', 'typing', 'types')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dinkleberg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Your friendly neighbour when it comes to dependency management.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DavidVollmers/dinkleberg
|
|
6
6
|
Project-URL: Documentation, https://github.com/DavidVollmers/dinkleberg/blob/main/libs/dinkleberg/README.md
|
|
@@ -17,6 +17,9 @@ Classifier: Operating System :: OS Independent
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Requires-Python: >=3.13
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: dinkleberg-abc>=0.3.0
|
|
21
|
+
Provides-Extra: fastapi
|
|
22
|
+
Requires-Dist: dinkleberg-fastapi>=0.3.0; extra == "fastapi"
|
|
20
23
|
|
|
21
24
|
# dinkleberg
|
|
22
25
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
dinkleberg/__init__.py,sha256=6gowHoL3xTWd2iyU09upgjXKUv_WWp6Tw1iqw2uz7Vc,192
|
|
2
|
+
dinkleberg/dependency_configurator.py,sha256=gSuizzr28qwO9Q5Z1IYytxaIRB5dAAKiPKe3GozKUuk,14660
|
|
3
|
+
dinkleberg/descriptor.py,sha256=ZtVwJihvCLugakHx201iX7Jm4A50Z0SzPu9zeHfD320,349
|
|
4
|
+
dinkleberg/typing.py,sha256=qAdSQomntQfI4bSWc9X32DXGftuyj56WP7nG3Pia7ZE,942
|
|
5
|
+
dinkleberg/fastapi/__init__.py,sha256=boj1ywpWhuuOjHLwdUha2EPBG2f1zbc374N35k1CJxk,352
|
|
6
|
+
dinkleberg-0.3.1.dist-info/METADATA,sha256=zK12ZPnFptOjz9QiF8i13yqHnG2UtWz1qSMzUkzE8rQ,1555
|
|
7
|
+
dinkleberg-0.3.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
dinkleberg-0.3.1.dist-info/top_level.txt,sha256=6TjbaJ_eyRzoxzz2r1Eu0hgYfxBBM2bOV3u_TJ8yjjw,11
|
|
9
|
+
dinkleberg-0.3.1.dist-info/RECORD,,
|
dinkleberg/dependency.py
DELETED
dinkleberg/dependency_scope.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class DependencyScope(ABC):
|
|
5
|
-
@abstractmethod
|
|
6
|
-
async def resolve[T](self, t: type[T], **kwargs) -> T:
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
@abstractmethod
|
|
10
|
-
async def close(self) -> None:
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
|
-
@abstractmethod
|
|
14
|
-
def scope(self) -> 'DependencyScope':
|
|
15
|
-
pass
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
dinkleberg/__init__.py,sha256=HGu8mjYt-Fa0y9_aMtJZFYo3s9Yb0q5dmbMKtHrAV_k,217
|
|
2
|
-
dinkleberg/dependency.py,sha256=YcGvuvhoBRxShgSRaznKyZg49GCgOWy_L8HD0sXAMYo,29
|
|
3
|
-
dinkleberg/dependency_configurator.py,sha256=N0mF78B9HnCKPwhDo4DTt8aDGbLwKEPFqEfo5ZtrrxA,11877
|
|
4
|
-
dinkleberg/dependency_scope.py,sha256=sufXjQ6F62ZfohItUnrV7Fvh8gSSc7a6orO6PXu19Vw,318
|
|
5
|
-
dinkleberg/descriptor.py,sha256=mVI00K1M2m4Rl5ANatGMeB_HpzeFONoULwg0UmNxfQ0,313
|
|
6
|
-
dinkleberg/typing.py,sha256=_T2plzKBU_GTMNazpU8bqKuqTvQotJPuWEHQH2dF76g,841
|
|
7
|
-
dinkleberg-0.2.1.dist-info/METADATA,sha256=Mv6RezK3xmMBpnRY0DKkwXSxPnm6mTezMRcMWFMiNs8,1430
|
|
8
|
-
dinkleberg-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
-
dinkleberg-0.2.1.dist-info/top_level.txt,sha256=6TjbaJ_eyRzoxzz2r1Eu0hgYfxBBM2bOV3u_TJ8yjjw,11
|
|
10
|
-
dinkleberg-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|