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.
@@ -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('DependencyConfigurator is already closed.')
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
- setattr(instance, name, wrapped_method)
276
- else:
277
- raise NotImplementedError('Synchronous methods with Dependency() defaults are not supported.')
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._wrap_instance(instance)
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.0
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=vsXTHFUkhW5_bR95qFYOzaio-UwupIkS9qjPL2e8s4o,14356
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.0.dist-info/METADATA,sha256=sGBRFKAmbtQS1vc79a5tyotUivlciNlhZAbwYqSC1FQ,1555
7
- dinkleberg-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- dinkleberg-0.3.0.dist-info/top_level.txt,sha256=6TjbaJ_eyRzoxzz2r1Eu0hgYfxBBM2bOV3u_TJ8yjjw,11
9
- dinkleberg-0.3.0.dist-info/RECORD,,
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,,