dinkleberg 0.2.0__py3-none-any.whl → 0.3.0__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,4 +1,3 @@
1
- import abc
2
1
  import asyncio
3
2
  import inspect
4
3
  import logging
@@ -6,10 +5,9 @@ from inspect import Signature
6
5
  from types import MappingProxyType
7
6
  from typing import AsyncGenerator, Callable, overload, get_type_hints, Mapping, get_origin
8
7
 
9
- from .dependency import Dependency
10
- from .dependency_scope import DependencyScope
8
+ from dinkleberg_abc import DependencyScope, Dependency
11
9
  from .descriptor import Descriptor, Lifetime
12
- from .typing import get_static_params, get_public_methods, is_builtin
10
+ from .typing import get_static_params, get_public_methods, is_builtin_type, is_abstract
13
11
 
14
12
  logger = logging.getLogger(__name__)
15
13
 
@@ -58,13 +56,45 @@ class DependencyConfigurator(DependencyScope):
58
56
  if exceptions:
59
57
  raise ExceptionGroup('Errors occurred during closing DependencyConfigurator', exceptions)
60
58
 
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.')
59
+ def _add(self, lifetime: Lifetime, *, t: type = None, i: type = None,
60
+ generator: Callable[..., AsyncGenerator] = None, callable: Callable = None):
61
+ if t is None and i is None and generator is None and callable is None:
62
+ raise ValueError(
63
+ 'Invalid dependency registration. At least one of t, i, generator, or callable must be provided.')
64
+
65
+ if lifetime == 'singleton' and self._parent is not None:
66
+ raise RuntimeError(
67
+ 'Singleton dependencies, which are not instances, can only be registered in the root DependencyConfigurator.')
68
+
69
+ if i is not None:
70
+ if is_builtin_type(i):
71
+ raise ValueError(f'Cannot use built-in type {i} as implementation.')
72
+
73
+ if get_origin(i) is not None:
74
+ raise ValueError(f'Cannot use generic type {i} as implementation.')
75
+
76
+ if is_abstract(i):
77
+ raise ValueError(f'Cannot use abstract class {i} as implementation.')
78
+
65
79
  if t is None:
66
- t = self._infer_type(generator=generator, callable=callable)
67
- self._descriptors[t] = Descriptor(generator=generator, callable=callable, lifetime=lifetime)
80
+ if i is not None:
81
+ t = i
82
+ else:
83
+ t = self._infer_type(generator=generator, callable=callable)
84
+ elif generator is None and callable is None:
85
+ if is_builtin_type(t):
86
+ raise ValueError(
87
+ f'Cannot register built-in type {t} without explicit implementation, generator or callable.')
88
+
89
+ if get_origin(t) is not None:
90
+ raise ValueError(
91
+ f'Cannot register generic type {t} without explicit implementation, generator or callable.')
92
+
93
+ if is_abstract(t):
94
+ raise ValueError(
95
+ f'Cannot register abstract class {t} without explicit implementation, generator or callable.')
96
+
97
+ self._descriptors[t] = Descriptor(implementation=i, generator=generator, callable=callable, lifetime=lifetime)
68
98
 
69
99
  @staticmethod
70
100
  def _infer_type(*, generator: Callable[..., AsyncGenerator], callable: Callable) -> type:
@@ -117,8 +147,26 @@ class DependencyConfigurator(DependencyScope):
117
147
  if t in self._scoped_instances:
118
148
  return self._scoped_instances[t]
119
149
 
120
- if t in self._descriptors:
121
- descriptor = self._descriptors[t]
150
+ descriptor = self._descriptors.get(t)
151
+ if descriptor is None or descriptor['generator'] is None and descriptor['callable'] is None:
152
+ if descriptor is None:
153
+ if is_builtin_type(t):
154
+ raise ValueError(f'Cannot resolve built-in type {t} without explicit registration.')
155
+
156
+ if get_origin(t) is not None:
157
+ raise ValueError(f'Cannot resolve generic type {t} without explicit registration.')
158
+
159
+ if is_abstract(t):
160
+ raise ValueError(f'Cannot resolve abstract class {t} without explicit registration.')
161
+
162
+ factory = t
163
+ else:
164
+ factory = descriptor['implementation'] or t
165
+
166
+ is_generator = False
167
+ lifetime = descriptor['lifetime'] if descriptor else 'transient'
168
+ deps = await self._resolve_deps(factory.__init__)
169
+ else:
122
170
  lifetime = descriptor['lifetime']
123
171
  if lifetime == 'singleton' and self._parent:
124
172
  # we need to resolve singleton from the root scope
@@ -127,17 +175,6 @@ class DependencyConfigurator(DependencyScope):
127
175
  is_generator = descriptor['generator'] is not None
128
176
  factory = descriptor['generator'] or descriptor['callable']
129
177
  deps = await self._resolve_deps(factory)
130
- else:
131
- if get_origin(t) is not None:
132
- raise ValueError(f'Cannot resolve generic type {t} without explicit registration.')
133
-
134
- if inspect.isabstract(t) or t is abc.ABC:
135
- raise ValueError(f'Cannot resolve abstract class {t} without explicit registration.')
136
-
137
- is_generator = False
138
- lifetime = 'transient'
139
- factory = t
140
- deps = await self._resolve_deps(t.__init__)
141
178
 
142
179
  if is_generator:
143
180
  generator = factory(**deps, **kwargs)
@@ -180,7 +217,7 @@ class DependencyConfigurator(DependencyScope):
180
217
  if not param.annotation or param.annotation is inspect.Parameter.empty:
181
218
  continue
182
219
 
183
- if is_builtin(param.annotation):
220
+ if is_builtin_type(param.annotation):
184
221
  continue
185
222
 
186
223
  # TODO handle more complex cases (e.g., Union, Optional, etc.)
@@ -253,6 +290,18 @@ class DependencyConfigurator(DependencyScope):
253
290
  def add_singleton[T, I](self, *, t: type[T], instance: I):
254
291
  ...
255
292
 
293
+ @overload
294
+ def add_singleton[T](self, *, t: type[T]):
295
+ ...
296
+
297
+ @overload
298
+ def add_singleton[I](self, *, i: type[I]):
299
+ ...
300
+
301
+ @overload
302
+ def add_singleton[T, I](self, *, t: type[T], i: type[I]):
303
+ ...
304
+
256
305
  @overload
257
306
  def add_singleton[I](self, *, callable: Callable[..., I]):
258
307
  ...
@@ -269,11 +318,13 @@ class DependencyConfigurator(DependencyScope):
269
318
  def add_singleton[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]]):
270
319
  ...
271
320
 
272
- def add_singleton[T, I](self, *, t: type[T] = None, generator: Callable[..., AsyncGenerator[I]] = None,
321
+ def add_singleton[T, I](self, *, t: type[T] = None, i: type[I] = None,
322
+ generator: Callable[..., AsyncGenerator[I]] = None,
273
323
  callable: Callable[..., I] = None, instance: I = None):
274
324
  self._raise_if_closed()
325
+
275
326
  if instance is None:
276
- self._add('singleton', t=t, generator=generator, callable=callable)
327
+ self._add('singleton', t=t, i=i, generator=generator, callable=callable)
277
328
  return
278
329
  elif t is None:
279
330
  t = type(instance)
@@ -282,6 +333,18 @@ class DependencyConfigurator(DependencyScope):
282
333
 
283
334
  self._singleton_instances[t] = instance
284
335
 
336
+ @overload
337
+ def add_scoped[T](self, *, t: type[T]):
338
+ ...
339
+
340
+ @overload
341
+ def add_scoped[I](self, *, i: type[I]):
342
+ ...
343
+
344
+ @overload
345
+ def add_scoped[T, I](self, *, t: type[T], i: type[I]):
346
+ ...
347
+
285
348
  @overload
286
349
  def add_scoped[I](self, *, callable: Callable[..., I]):
287
350
  ...
@@ -298,10 +361,22 @@ class DependencyConfigurator(DependencyScope):
298
361
  def add_scoped[T, I](self, *, t: type[T], generator: Callable[..., AsyncGenerator[I]]):
299
362
  ...
300
363
 
301
- def add_scoped[T, I](self, *, t: type[T] = None, generator: Callable[..., AsyncGenerator[I]] = None,
302
- callable: Callable[..., I] = None):
364
+ def add_scoped[T, I](self, *, t: type[T] = None, i: type[I] = None,
365
+ generator: Callable[..., AsyncGenerator[I]] = None, callable: Callable[..., I] = None):
303
366
  self._raise_if_closed()
304
- self._add('scoped', t=t, generator=generator, callable=callable)
367
+ self._add('scoped', t=t, i=i, generator=generator, callable=callable)
368
+
369
+ @overload
370
+ def add_transient[T](self, *, t: type[T]):
371
+ ...
372
+
373
+ @overload
374
+ def add_transient[I](self, *, i: type[I]):
375
+ ...
376
+
377
+ @overload
378
+ def add_transient[T, I](self, *, t: type[T], i: type[I]):
379
+ ...
305
380
 
306
381
  @overload
307
382
  def add_transient[I](self, *, callable: Callable[..., I]):
@@ -311,6 +386,6 @@ class DependencyConfigurator(DependencyScope):
311
386
  def add_transient[T, I](self, *, t: type[T], callable: Callable[..., I]):
312
387
  ...
313
388
 
314
- def add_transient[T, I](self, *, t: type[T] = None, callable: Callable[..., I] = None):
389
+ def add_transient[T, I](self, *, t: type[T] = None, i: type[I] = None, callable: Callable[..., I] = None):
315
390
  self._raise_if_closed()
316
- self._add('transient', t=t, callable=callable)
391
+ 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,11 +1,16 @@
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
 
6
- def is_builtin(t: type) -> bool:
7
+ def is_abstract(t: type) -> bool:
8
+ return inspect.isabstract(t) or t is abc.ABC
9
+
10
+
11
+ def is_builtin_type(t: type) -> bool:
7
12
  origin = get_origin(t) or t
8
- return getattr(origin, '__module__', None) == 'builtins'
13
+ return getattr(origin, '__module__', None) in ('builtins', 'typing', 'types')
9
14
 
10
15
 
11
16
  def get_static_params(func: Callable) -> list[Parameter]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dinkleberg
3
- Version: 0.2.0
3
+ Version: 0.3.0
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=vsXTHFUkhW5_bR95qFYOzaio-UwupIkS9qjPL2e8s4o,14356
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.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,,
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=vc3m23BopF0rSFDRO-Fj-8cGmU4bbGve1Qa1ymYsF0g,11727
4
- dinkleberg/dependency_scope.py,sha256=sufXjQ6F62ZfohItUnrV7Fvh8gSSc7a6orO6PXu19Vw,318
5
- dinkleberg/descriptor.py,sha256=mVI00K1M2m4Rl5ANatGMeB_HpzeFONoULwg0UmNxfQ0,313
6
- dinkleberg/typing.py,sha256=kVawkc46Jblqa4XUdangv3ZL8X3RyP8TpZ08EU7RbP0,815
7
- dinkleberg-0.2.0.dist-info/METADATA,sha256=aVA9a_YmlnDJPMFVirD81BwTPBMBizhmnv02mf7Mkwc,1430
8
- dinkleberg-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
9
- dinkleberg-0.2.0.dist-info/top_level.txt,sha256=6TjbaJ_eyRzoxzz2r1Eu0hgYfxBBM2bOV3u_TJ8yjjw,11
10
- dinkleberg-0.2.0.dist-info/RECORD,,