dinkleberg 0.3.0__py3-none-any.whl → 0.3.2__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/dependency_configurator.py +93 -52
- {dinkleberg-0.3.0.dist-info → dinkleberg-0.3.2.dist-info}/METADATA +1 -1
- {dinkleberg-0.3.0.dist-info → dinkleberg-0.3.2.dist-info}/RECORD +5 -5
- {dinkleberg-0.3.0.dist-info → dinkleberg-0.3.2.dist-info}/WHEEL +0 -0
- {dinkleberg-0.3.0.dist-info → dinkleberg-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
|
+
from functools import wraps
|
|
4
5
|
from inspect import Signature
|
|
5
6
|
from types import MappingProxyType
|
|
6
7
|
from typing import AsyncGenerator, Callable, overload, get_type_hints, Mapping, get_origin
|
|
@@ -20,10 +21,17 @@ class DependencyConfigurator(DependencyScope):
|
|
|
20
21
|
self._descriptors: dict[type, Descriptor] = {}
|
|
21
22
|
self._singleton_instances = {}
|
|
22
23
|
self._scoped_instances = {}
|
|
24
|
+
self._configurators: dict[type, list[Callable[[object], object | None]]] = {}
|
|
23
25
|
self._active_generators = []
|
|
24
26
|
self._scopes = []
|
|
25
27
|
self._closed = False
|
|
26
28
|
|
|
29
|
+
def configure[T](self, t: type[T], configurator: Callable[[T], T | None]) -> None:
|
|
30
|
+
self._raise_if_closed()
|
|
31
|
+
if t not in self._configurators:
|
|
32
|
+
self._configurators[t] = []
|
|
33
|
+
self._configurators[t].append(configurator)
|
|
34
|
+
|
|
27
35
|
async def close(self) -> None:
|
|
28
36
|
if self._closed:
|
|
29
37
|
return
|
|
@@ -50,6 +58,7 @@ class DependencyConfigurator(DependencyScope):
|
|
|
50
58
|
self._singleton_instances.clear()
|
|
51
59
|
self._active_generators.clear()
|
|
52
60
|
self._scoped_instances.clear()
|
|
61
|
+
self._configurators.clear()
|
|
53
62
|
self._descriptors.clear()
|
|
54
63
|
self._scopes.clear()
|
|
55
64
|
|
|
@@ -57,7 +66,7 @@ class DependencyConfigurator(DependencyScope):
|
|
|
57
66
|
raise ExceptionGroup('Errors occurred during closing DependencyConfigurator', exceptions)
|
|
58
67
|
|
|
59
68
|
def _add(self, lifetime: Lifetime, *, t: type = None, i: type = None,
|
|
60
|
-
generator: Callable[..., AsyncGenerator] = None, callable: Callable = None):
|
|
69
|
+
generator: Callable[..., AsyncGenerator] = None, callable: Callable = None, override: bool = False):
|
|
61
70
|
if t is None and i is None and generator is None and callable is None:
|
|
62
71
|
raise ValueError(
|
|
63
72
|
'Invalid dependency registration. At least one of t, i, generator, or callable must be provided.')
|
|
@@ -81,7 +90,7 @@ class DependencyConfigurator(DependencyScope):
|
|
|
81
90
|
t = i
|
|
82
91
|
else:
|
|
83
92
|
t = self._infer_type(generator=generator, callable=callable)
|
|
84
|
-
elif generator is None and callable is None:
|
|
93
|
+
elif generator is None and callable is None and i is None:
|
|
85
94
|
if is_builtin_type(t):
|
|
86
95
|
raise ValueError(
|
|
87
96
|
f'Cannot register built-in type {t} without explicit implementation, generator or callable.')
|
|
@@ -94,6 +103,9 @@ class DependencyConfigurator(DependencyScope):
|
|
|
94
103
|
raise ValueError(
|
|
95
104
|
f'Cannot register abstract class {t} without explicit implementation, generator or callable.')
|
|
96
105
|
|
|
106
|
+
if t in self._descriptors and not override:
|
|
107
|
+
raise ValueError(f'Type {t} is already registered.')
|
|
108
|
+
|
|
97
109
|
self._descriptors[t] = Descriptor(implementation=i, generator=generator, callable=callable, lifetime=lifetime)
|
|
98
110
|
|
|
99
111
|
@staticmethod
|
|
@@ -117,12 +129,13 @@ class DependencyConfigurator(DependencyScope):
|
|
|
117
129
|
|
|
118
130
|
def _raise_if_closed(self):
|
|
119
131
|
if self._closed:
|
|
120
|
-
raise RuntimeError('
|
|
132
|
+
raise RuntimeError('DependencyScope is already closed.')
|
|
121
133
|
|
|
122
134
|
def scope(self) -> 'DependencyConfigurator':
|
|
123
135
|
self._raise_if_closed()
|
|
124
136
|
scope = DependencyConfigurator(self)
|
|
125
137
|
scope._descriptors = self._descriptors.copy()
|
|
138
|
+
scope._configurators = self._configurators.copy()
|
|
126
139
|
self._scopes.append(scope)
|
|
127
140
|
return scope
|
|
128
141
|
|
|
@@ -135,9 +148,15 @@ class DependencyConfigurator(DependencyScope):
|
|
|
135
148
|
|
|
136
149
|
# TODO circular dependency detection
|
|
137
150
|
# TODO singleton race condition prevention (async.Lock)
|
|
138
|
-
async def resolve[T](self, t: type[T], **kwargs) -> T:
|
|
151
|
+
async def resolve[T](self, t: type[T] | Callable, **kwargs) -> T:
|
|
139
152
|
self._raise_if_closed()
|
|
140
153
|
|
|
154
|
+
if not inspect.isclass(t):
|
|
155
|
+
if not inspect.isfunction(t):
|
|
156
|
+
raise ValueError(f'Cannot resolve type {t}. Only classes and functions are supported.')
|
|
157
|
+
|
|
158
|
+
return self._wrap_func(t)
|
|
159
|
+
|
|
141
160
|
if t == DependencyScope:
|
|
142
161
|
return self
|
|
143
162
|
|
|
@@ -199,7 +218,7 @@ class DependencyConfigurator(DependencyScope):
|
|
|
199
218
|
finally:
|
|
200
219
|
await instance.aclose()
|
|
201
220
|
|
|
202
|
-
self._wrap_instance(instance)
|
|
221
|
+
self._wrap_instance(t, instance)
|
|
203
222
|
|
|
204
223
|
if lifetime == 'singleton':
|
|
205
224
|
self._singleton_instances[t] = instance
|
|
@@ -249,32 +268,46 @@ class DependencyConfigurator(DependencyScope):
|
|
|
249
268
|
|
|
250
269
|
return actual_kwargs
|
|
251
270
|
|
|
271
|
+
def _wrap_func(self, func: Callable):
|
|
272
|
+
signature = inspect.signature(func)
|
|
273
|
+
|
|
274
|
+
dep_params = MappingProxyType({
|
|
275
|
+
param_name: param
|
|
276
|
+
for param_name, param in signature.parameters.items()
|
|
277
|
+
if isinstance(param.default, Dependency)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
if not dep_params:
|
|
281
|
+
return func
|
|
282
|
+
|
|
283
|
+
if not asyncio.iscoroutinefunction(func):
|
|
284
|
+
raise NotImplementedError('Synchronous functions with Dependency() defaults are not supported.')
|
|
285
|
+
|
|
286
|
+
@wraps(func)
|
|
287
|
+
async def wrapped_func(*args, **kwargs):
|
|
288
|
+
new_kwargs = await self._resolve_kwargs(signature, func.__name__, args, kwargs, dep_params)
|
|
289
|
+
return await func(*args, **new_kwargs)
|
|
290
|
+
|
|
291
|
+
return wrapped_func
|
|
292
|
+
|
|
252
293
|
# TODO handle __slots__
|
|
253
|
-
def _wrap_instance(self, instance):
|
|
254
|
-
if getattr(instance, '__dinkleberg__', False):
|
|
294
|
+
def _wrap_instance(self, t: type, instance: object):
|
|
295
|
+
if getattr(instance, '__dinkleberg__', False) or is_builtin_type(t):
|
|
255
296
|
return
|
|
256
297
|
|
|
298
|
+
configurators = self._configurators.get(t, [])
|
|
299
|
+
for configurator in configurators:
|
|
300
|
+
result = configurator(instance)
|
|
301
|
+
if result is not None:
|
|
302
|
+
instance = result
|
|
303
|
+
|
|
257
304
|
methods = get_public_methods(instance)
|
|
258
305
|
for name, value in methods:
|
|
259
|
-
signature = inspect.signature(value)
|
|
260
|
-
|
|
261
|
-
dep_params = MappingProxyType({
|
|
262
|
-
param_name: param
|
|
263
|
-
for param_name, param in signature.parameters.items()
|
|
264
|
-
if isinstance(param.default, Dependency)
|
|
265
|
-
})
|
|
266
|
-
if not dep_params:
|
|
267
|
-
continue
|
|
268
|
-
|
|
269
306
|
instance_method = getattr(instance, name)
|
|
270
|
-
if asyncio.iscoroutinefunction(instance_method):
|
|
271
|
-
async def wrapped_method(*args, __m=instance_method, __s=signature, __n=name, __d=dep_params, **kwargs):
|
|
272
|
-
new_kwargs = await self._resolve_kwargs(__s, __n, args, kwargs, __d)
|
|
273
|
-
return await __m(*args, **new_kwargs)
|
|
274
307
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
308
|
+
wrapped_method = self._wrap_func(instance_method)
|
|
309
|
+
|
|
310
|
+
setattr(instance, name, wrapped_method)
|
|
278
311
|
|
|
279
312
|
try:
|
|
280
313
|
setattr(instance, '__dinkleberg__', True)
|
|
@@ -283,109 +316,117 @@ class DependencyConfigurator(DependencyScope):
|
|
|
283
316
|
pass
|
|
284
317
|
|
|
285
318
|
@overload
|
|
286
|
-
def add_singleton[I](self, *, instance: I):
|
|
319
|
+
def add_singleton[I](self, *, instance: I, override: bool = False):
|
|
287
320
|
...
|
|
288
321
|
|
|
289
322
|
@overload
|
|
290
|
-
def add_singleton[T, I](self, *, t: type[T], instance: I):
|
|
323
|
+
def add_singleton[T, I](self, *, t: type[T], instance: I, override: bool = False):
|
|
291
324
|
...
|
|
292
325
|
|
|
293
326
|
@overload
|
|
294
|
-
def add_singleton[T](self, *, t: type[T]):
|
|
327
|
+
def add_singleton[T](self, *, t: type[T], override: bool = False):
|
|
295
328
|
...
|
|
296
329
|
|
|
297
330
|
@overload
|
|
298
|
-
def add_singleton[I](self, *, i: type[I]):
|
|
331
|
+
def add_singleton[I](self, *, i: type[I], override: bool = False):
|
|
299
332
|
...
|
|
300
333
|
|
|
301
334
|
@overload
|
|
302
|
-
def add_singleton[T, I](self, *, t: type[T], i: type[I]):
|
|
335
|
+
def add_singleton[T, I](self, *, t: type[T], i: type[I], override: bool = False):
|
|
303
336
|
...
|
|
304
337
|
|
|
305
338
|
@overload
|
|
306
|
-
def add_singleton[I](self, *, callable: Callable[..., I]):
|
|
339
|
+
def add_singleton[I](self, *, callable: Callable[..., I], override: bool = False):
|
|
307
340
|
...
|
|
308
341
|
|
|
309
342
|
@overload
|
|
310
|
-
def add_singleton[T, I](self, *, t: type[T], callable: Callable[..., I]):
|
|
343
|
+
def add_singleton[T, I](self, *, t: type[T], callable: Callable[..., I], override: bool = False):
|
|
311
344
|
...
|
|
312
345
|
|
|
313
346
|
@overload
|
|
314
|
-
def add_singleton[I](self, *, generator: Callable[..., AsyncGenerator[I]]):
|
|
347
|
+
def add_singleton[I](self, *, generator: Callable[..., AsyncGenerator[I]], override: bool = False):
|
|
315
348
|
...
|
|
316
349
|
|
|
317
350
|
@overload
|
|
318
|
-
def add_singleton[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]]):
|
|
351
|
+
def add_singleton[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]], override: bool = False):
|
|
319
352
|
...
|
|
320
353
|
|
|
321
354
|
def add_singleton[T, I](self, *, t: type[T] = None, i: type[I] = None,
|
|
322
355
|
generator: Callable[..., AsyncGenerator[I]] = None,
|
|
323
|
-
callable: Callable[..., I] = None, instance: I = None):
|
|
356
|
+
callable: Callable[..., I] = None, instance: I = None, override: bool = False):
|
|
324
357
|
self._raise_if_closed()
|
|
325
358
|
|
|
326
359
|
if instance is None:
|
|
327
|
-
self._add('singleton', t=t, i=i, generator=generator, callable=callable)
|
|
360
|
+
self._add('singleton', t=t, i=i, generator=generator, callable=callable, override=override)
|
|
328
361
|
return
|
|
329
362
|
elif t is None:
|
|
330
363
|
t = type(instance)
|
|
331
364
|
|
|
332
|
-
self.
|
|
365
|
+
if t in self._descriptors:
|
|
366
|
+
raise ValueError(f'Type {t} is already registered with a descriptor. Cannot register singleton instance.')
|
|
367
|
+
|
|
368
|
+
if t in self._singleton_instances and not override:
|
|
369
|
+
raise ValueError(f'Type {t} already has a singleton instance registered.')
|
|
370
|
+
|
|
371
|
+
self._wrap_instance(t, instance)
|
|
333
372
|
|
|
334
373
|
self._singleton_instances[t] = instance
|
|
335
374
|
|
|
336
375
|
@overload
|
|
337
|
-
def add_scoped[T](self, *, t: type[T]):
|
|
376
|
+
def add_scoped[T](self, *, t: type[T], override: bool = False):
|
|
338
377
|
...
|
|
339
378
|
|
|
340
379
|
@overload
|
|
341
|
-
def add_scoped[I](self, *, i: type[I]):
|
|
380
|
+
def add_scoped[I](self, *, i: type[I], override: bool = False):
|
|
342
381
|
...
|
|
343
382
|
|
|
344
383
|
@overload
|
|
345
|
-
def add_scoped[T, I](self, *, t: type[T], i: type[I]):
|
|
384
|
+
def add_scoped[T, I](self, *, t: type[T], i: type[I], override: bool = False):
|
|
346
385
|
...
|
|
347
386
|
|
|
348
387
|
@overload
|
|
349
|
-
def add_scoped[I](self, *, callable: Callable[..., I]):
|
|
388
|
+
def add_scoped[I](self, *, callable: Callable[..., I], override: bool = False):
|
|
350
389
|
...
|
|
351
390
|
|
|
352
391
|
@overload
|
|
353
|
-
def add_scoped[T, I](self, *, t: type[T], callable: Callable[..., I]):
|
|
392
|
+
def add_scoped[T, I](self, *, t: type[T], callable: Callable[..., I], override: bool = False):
|
|
354
393
|
...
|
|
355
394
|
|
|
356
395
|
@overload
|
|
357
|
-
def add_scoped[I](self, *, generator: Callable[..., AsyncGenerator[I]]):
|
|
396
|
+
def add_scoped[I](self, *, generator: Callable[..., AsyncGenerator[I]], override: bool = False):
|
|
358
397
|
...
|
|
359
398
|
|
|
360
399
|
@overload
|
|
361
|
-
def add_scoped[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]]):
|
|
400
|
+
def add_scoped[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]], override: bool = False):
|
|
362
401
|
...
|
|
363
402
|
|
|
364
403
|
def add_scoped[T, I](self, *, t: type[T] = None, i: type[I] = None,
|
|
365
|
-
generator: Callable[..., AsyncGenerator[I]] = None, callable: Callable[..., I] = None
|
|
404
|
+
generator: Callable[..., AsyncGenerator[I]] = None, callable: Callable[..., I] = None,
|
|
405
|
+
override: bool = False):
|
|
366
406
|
self._raise_if_closed()
|
|
367
|
-
self._add('scoped', t=t, i=i, generator=generator, callable=callable)
|
|
407
|
+
self._add('scoped', t=t, i=i, generator=generator, callable=callable, override=override)
|
|
368
408
|
|
|
369
409
|
@overload
|
|
370
|
-
def add_transient[T](self, *, t: type[T]):
|
|
410
|
+
def add_transient[T](self, *, t: type[T], override: bool = False):
|
|
371
411
|
...
|
|
372
412
|
|
|
373
413
|
@overload
|
|
374
|
-
def add_transient[I](self, *, i: type[I]):
|
|
414
|
+
def add_transient[I](self, *, i: type[I], override: bool = False):
|
|
375
415
|
...
|
|
376
416
|
|
|
377
417
|
@overload
|
|
378
|
-
def add_transient[T, I](self, *, t: type[T], i: type[I]):
|
|
418
|
+
def add_transient[T, I](self, *, t: type[T], i: type[I], override: bool = False):
|
|
379
419
|
...
|
|
380
420
|
|
|
381
421
|
@overload
|
|
382
|
-
def add_transient[I](self, *, callable: Callable[..., I]):
|
|
422
|
+
def add_transient[I](self, *, callable: Callable[..., I], override: bool = False):
|
|
383
423
|
...
|
|
384
424
|
|
|
385
425
|
@overload
|
|
386
|
-
def add_transient[T, I](self, *, t: type[T], callable: Callable[..., I]):
|
|
426
|
+
def add_transient[T, I](self, *, t: type[T], callable: Callable[..., I], override: bool = False):
|
|
387
427
|
...
|
|
388
428
|
|
|
389
|
-
def add_transient[T, I](self, *, t: type[T] = None, i: type[I] = None, callable: Callable[..., I] = None
|
|
429
|
+
def add_transient[T, I](self, *, t: type[T] = None, i: type[I] = None, callable: Callable[..., I] = None,
|
|
430
|
+
override: bool = False):
|
|
390
431
|
self._raise_if_closed()
|
|
391
|
-
self._add('transient', i=i, t=t, callable=callable)
|
|
432
|
+
self._add('transient', i=i, t=t, callable=callable, override=override)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dinkleberg
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
dinkleberg/__init__.py,sha256=6gowHoL3xTWd2iyU09upgjXKUv_WWp6Tw1iqw2uz7Vc,192
|
|
2
|
-
dinkleberg/dependency_configurator.py,sha256=
|
|
2
|
+
dinkleberg/dependency_configurator.py,sha256=lS_IPK2nLdVPQZpYOxlk6pv1fzQ9MK2PdJ9ucOrptww,16514
|
|
3
3
|
dinkleberg/descriptor.py,sha256=ZtVwJihvCLugakHx201iX7Jm4A50Z0SzPu9zeHfD320,349
|
|
4
4
|
dinkleberg/typing.py,sha256=qAdSQomntQfI4bSWc9X32DXGftuyj56WP7nG3Pia7ZE,942
|
|
5
5
|
dinkleberg/fastapi/__init__.py,sha256=boj1ywpWhuuOjHLwdUha2EPBG2f1zbc374N35k1CJxk,352
|
|
6
|
-
dinkleberg-0.3.
|
|
7
|
-
dinkleberg-0.3.
|
|
8
|
-
dinkleberg-0.3.
|
|
9
|
-
dinkleberg-0.3.
|
|
6
|
+
dinkleberg-0.3.2.dist-info/METADATA,sha256=sKUDY2mt75smvk6Em79QVJXf8kgL16M1KYDxCVUvCes,1555
|
|
7
|
+
dinkleberg-0.3.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
dinkleberg-0.3.2.dist-info/top_level.txt,sha256=6TjbaJ_eyRzoxzz2r1Eu0hgYfxBBM2bOV3u_TJ8yjjw,11
|
|
9
|
+
dinkleberg-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|