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 CHANGED
@@ -1,5 +1,5 @@
1
- from .dependency import Dependency
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 .dependency import Dependency
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, generator: Callable[..., AsyncGenerator] = None,
62
- callable: Callable = None):
63
- if generator is None and callable is None:
64
- raise ValueError('Invalid dependency registration. Either generator or callable must be provided.')
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
- t = self._infer_type(generator=generator, callable=callable)
67
- self._descriptors[t] = Descriptor(generator=generator, callable=callable, lifetime=lifetime)
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
- if t in self._descriptors:
121
- descriptor = self._descriptors[t]
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
- setattr(instance, name, wrapped_method)
242
- else:
243
- raise NotImplementedError('Synchronous methods with Dependency() defaults are not supported.')
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, generator: Callable[..., AsyncGenerator[I]] = 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, generator: Callable[..., AsyncGenerator[I]] = 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
@@ -4,6 +4,7 @@ Lifetime = Union[Literal['singleton'], Literal['scoped'], Literal['transient']]
4
4
 
5
5
 
6
6
  class Descriptor(TypedDict):
7
+ implementation: Optional[type]
7
8
  generator: Optional[Callable[..., AsyncGenerator]]
8
9
  callable: Optional[Callable]
9
10
  lifetime: Lifetime
@@ -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.2.1
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
@@ -1,2 +0,0 @@
1
- class Dependency:
2
- pass
@@ -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,,