python-injection 0.6.6__py3-none-any.whl → 0.6.8__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.

Potentially problematic release.


This version of python-injection might be problematic. Click here for more details.

injection/_pkg.py CHANGED
@@ -8,6 +8,7 @@ __all__ = (
8
8
  "get_lazy_instance",
9
9
  "inject",
10
10
  "injectable",
11
+ "set_constant",
11
12
  "singleton",
12
13
  )
13
14
 
@@ -19,3 +20,5 @@ get_lazy_instance = default_module.get_lazy_instance
19
20
  inject = default_module.inject
20
21
  injectable = default_module.injectable
21
22
  singleton = default_module.singleton
23
+
24
+ set_constant = default_module.set_constant
injection/_pkg.pyi CHANGED
@@ -1,6 +1,7 @@
1
1
  from collections.abc import Callable, Iterable
2
2
  from contextlib import ContextDecorator
3
3
  from enum import Enum
4
+ from types import UnionType
4
5
  from typing import Any, ContextManager, Final, TypeVar, final
5
6
 
6
7
  from injection.common.lazy import Lazy
@@ -16,6 +17,8 @@ inject = default_module.inject
16
17
  injectable = default_module.injectable
17
18
  singleton = default_module.singleton
18
19
 
20
+ set_constant = default_module.set_constant
21
+
19
22
  @final
20
23
  class Module:
21
24
  """
@@ -27,7 +30,7 @@ class Module:
27
30
  """
28
31
 
29
32
  def __init__(self, name: str = ...): ...
30
- def __contains__(self, cls: type, /) -> bool: ...
33
+ def __contains__(self, cls: type | UnionType, /) -> bool: ...
31
34
  def inject(self, wrapped: Callable[..., Any] = ..., /):
32
35
  """
33
36
  Decorator applicable to a class or function. Inject function dependencies using
@@ -39,7 +42,7 @@ class Module:
39
42
  wrapped: Callable[..., Any] = ...,
40
43
  /,
41
44
  *,
42
- on: type | Iterable[type] = ...,
45
+ on: type | Iterable[type] | UnionType = ...,
43
46
  ):
44
47
  """
45
48
  Decorator applicable to a class or function. It is used to indicate how the
@@ -51,13 +54,23 @@ class Module:
51
54
  wrapped: Callable[..., Any] = ...,
52
55
  /,
53
56
  *,
54
- on: type | Iterable[type] = ...,
57
+ on: type | Iterable[type] | UnionType = ...,
55
58
  ):
56
59
  """
57
60
  Decorator applicable to a class or function. It is used to indicate how the
58
61
  singleton will be constructed. At injection time, the injected instance will
59
62
  always be the same.
60
63
  """
64
+ def set_constant(
65
+ self,
66
+ instance: _T,
67
+ on: type | Iterable[type] | UnionType = ...,
68
+ ) -> _T:
69
+ """
70
+ Function for registering a specific instance to be injected. This is useful for
71
+ registering global variables. The difference with the singleton decorator is
72
+ that no dependencies are resolved, so the module doesn't need to be locked.
73
+ """
61
74
  def get_instance(self, cls: type[_T]) -> _T | None:
62
75
  """
63
76
  Function used to retrieve an instance associated with the type passed in
@@ -1,6 +1,8 @@
1
- from typing import Any
1
+ from collections.abc import Iterator
2
+ from types import NoneType, UnionType
3
+ from typing import Annotated, Any, Union, get_args, get_origin
2
4
 
3
- __all__ = ("format_type", "get_origin")
5
+ __all__ = ("format_type", "get_origins")
4
6
 
5
7
 
6
8
  def format_type(cls: type | Any) -> str:
@@ -10,5 +12,25 @@ def format_type(cls: type | Any) -> str:
10
12
  return str(cls)
11
13
 
12
14
 
13
- def get_origin(cls: type | Any) -> type | Any:
14
- return getattr(cls, "__origin__", cls)
15
+ def get_origins(*classes: type | Any) -> Iterator[type | Any]:
16
+ for cls in classes:
17
+ origin = get_origin(cls) or cls
18
+
19
+ if origin in (None, NoneType):
20
+ continue
21
+
22
+ arguments = get_args(cls)
23
+
24
+ if origin in (Union, UnionType):
25
+ yield from get_origins(*arguments)
26
+
27
+ elif origin is Annotated:
28
+ try:
29
+ annotated = arguments[0]
30
+ except IndexError:
31
+ continue
32
+
33
+ yield from get_origins(annotated)
34
+
35
+ else:
36
+ yield origin
injection/core/module.py CHANGED
@@ -16,9 +16,9 @@ from contextlib import ContextDecorator, contextmanager, suppress
16
16
  from dataclasses import dataclass, field
17
17
  from enum import Enum, auto
18
18
  from functools import singledispatchmethod, wraps
19
- from inspect import Signature, get_annotations
19
+ from inspect import Signature, get_annotations, isclass, isfunction
20
20
  from threading import RLock
21
- from types import MappingProxyType
21
+ from types import MappingProxyType, UnionType
22
22
  from typing import (
23
23
  Any,
24
24
  ContextManager,
@@ -32,7 +32,7 @@ from typing import (
32
32
 
33
33
  from injection.common.event import Event, EventChannel, EventListener
34
34
  from injection.common.lazy import Lazy, LazyMapping
35
- from injection.common.tools import format_type, get_origin
35
+ from injection.common.tools import format_type, get_origins
36
36
  from injection.exceptions import (
37
37
  ModuleError,
38
38
  ModuleLockError,
@@ -46,6 +46,7 @@ _logger = logging.getLogger(__name__)
46
46
  _thread_lock = RLock()
47
47
 
48
48
  _T = TypeVar("_T")
49
+ Types = Iterable[type] | UnionType
49
50
 
50
51
 
51
52
  """
@@ -193,16 +194,15 @@ class Container:
193
194
  __data: dict[type, Injectable] = field(default_factory=dict, init=False)
194
195
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
195
196
 
196
- def __getitem__(self, cls: type[_T], /) -> Injectable[_T]:
197
- origin = get_origin(cls)
197
+ def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
198
+ for origin in get_origins(cls):
199
+ with suppress(KeyError):
200
+ return self.__data[origin]
198
201
 
199
- try:
200
- return self.__data[origin]
201
- except KeyError as exc:
202
- raise NoInjectable(cls) from exc
202
+ raise NoInjectable(cls)
203
203
 
204
- def __contains__(self, cls: type, /) -> bool:
205
- return get_origin(cls) in self.__data
204
+ def __contains__(self, cls: type | UnionType, /) -> bool:
205
+ return any(origin in self.__data for origin in get_origins(cls))
206
206
 
207
207
  @property
208
208
  def is_locked(self) -> bool:
@@ -212,8 +212,8 @@ class Container:
212
212
  def __injectables(self) -> frozenset[Injectable]:
213
213
  return frozenset(self.__data.values())
214
214
 
215
- def update(self, classes: Iterable[type], injectable: Injectable):
216
- classes = frozenset(get_origin(cls) for cls in classes)
215
+ def update(self, classes: Types, injectable: Injectable):
216
+ classes = frozenset(get_origins(*classes))
217
217
 
218
218
  if classes:
219
219
  event = ContainerDependenciesUpdated(self, classes)
@@ -272,17 +272,17 @@ class Module(EventListener):
272
272
  def __post_init__(self):
273
273
  self.__container.add_listener(self)
274
274
 
275
- def __getitem__(self, cls: type[_T], /) -> Injectable[_T]:
275
+ def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
276
276
  for broker in self.__brokers:
277
277
  with suppress(KeyError):
278
278
  return broker[cls]
279
279
 
280
280
  raise NoInjectable(cls)
281
281
 
282
- def __setitem__(self, cls: type, injectable: Injectable, /):
282
+ def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
283
283
  self.update((cls,), injectable)
284
284
 
285
- def __contains__(self, cls: type, /) -> bool:
285
+ def __contains__(self, cls: type | UnionType, /) -> bool:
286
286
  return any(cls in broker for broker in self.__brokers)
287
287
 
288
288
  def __str__(self) -> str:
@@ -309,6 +309,15 @@ class Module(EventListener):
309
309
  yield from tuple(self.__modules)
310
310
  yield self.__container
311
311
 
312
+ def set_constant(self, instance: _T, on: type | Types = None) -> _T:
313
+ cls = type(instance)
314
+
315
+ @self.injectable(on=(cls, on))
316
+ def get_constant():
317
+ return instance
318
+
319
+ return instance
320
+
312
321
  def get_instance(self, cls: type[_T]) -> _T | None:
313
322
  try:
314
323
  injectable = self[cls]
@@ -321,7 +330,7 @@ class Module(EventListener):
321
330
  def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
322
331
  return Lazy(lambda: self.get_instance(cls))
323
332
 
324
- def update(self, classes: Iterable[type], injectable: Injectable):
333
+ def update(self, classes: Types, injectable: Injectable):
325
334
  self.__container.update(classes, injectable)
326
335
  return self
327
336
 
@@ -395,7 +404,7 @@ class Module(EventListener):
395
404
 
396
405
  with self.__channel.dispatch(event):
397
406
  yield
398
- _logger.debug(f"{event}")
407
+ _logger.debug(event)
399
408
 
400
409
  def __check_locking(self):
401
410
  if self.is_locked:
@@ -507,7 +516,7 @@ class InjectDecorator:
507
516
 
508
517
  def __call__(self, wrapped: Callable[..., Any] = None, /):
509
518
  def decorator(wp):
510
- if isinstance(wp, type):
519
+ if isclass(wp):
511
520
  return self.__class_decorator(wp)
512
521
 
513
522
  return self.__decorator(wp)
@@ -549,42 +558,29 @@ class InjectableDecorator:
549
558
  wrapped: Callable[..., Any] = None,
550
559
  /,
551
560
  *,
552
- on: type | Iterable[type] = None,
561
+ on: type | Types = None,
553
562
  ):
554
563
  def decorator(wp):
555
- @lambda fn: fn()
556
- def classes():
557
- if cls := self.__get_target_class(wp):
558
- yield cls
559
-
560
- if on is None:
561
- return
562
- elif isinstance(on, type | str):
563
- yield on
564
- else:
565
- yield from on
566
-
567
564
  @self.__module.inject
568
565
  @wraps(wp, updated=())
569
566
  def factory(*args, **kwargs):
570
567
  return wp(*args, **kwargs)
571
568
 
572
569
  injectable = self.__injectable_type(factory)
570
+ classes = self.__get_classes(wp, on)
573
571
  self.__module.update(classes, injectable)
574
-
575
572
  return wp
576
573
 
577
574
  return decorator(wrapped) if wrapped else decorator
578
575
 
579
- @staticmethod
580
- def __get_target_class(wrapped: Callable[..., Any]) -> type | None:
581
- if isinstance(wrapped, type):
582
- return wrapped
583
-
584
- if callable(wrapped):
585
- return_type = get_annotations(wrapped, eval_str=True).get("return")
576
+ @classmethod
577
+ def __get_classes(cls, *objects: Any) -> Iterator[type | UnionType]:
578
+ for obj in objects:
579
+ if isinstance(obj, Iterable) and not isinstance(obj, type | str):
580
+ yield from cls.__get_classes(*obj)
586
581
 
587
- if isinstance(return_type, type):
588
- return return_type
582
+ elif isfunction(obj):
583
+ yield get_annotations(obj, eval_str=True).get("return")
589
584
 
590
- return None
585
+ else:
586
+ yield obj
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.6.6
3
+ Version: 0.6.8
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -19,6 +19,8 @@ Description-Content-Type: text/markdown
19
19
 
20
20
  ## Create an injectable
21
21
 
22
+ > **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
23
+
22
24
  If you wish to inject a singleton, use `singleton` decorator.
23
25
 
24
26
  ```python
@@ -39,7 +41,14 @@ class Injectable:
39
41
  """ class implementation """
40
42
  ```
41
43
 
42
- > **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
44
+ If you have a constant (such as a global variable) and wish to register it as an injectable, use `set_constant`
45
+ function.
46
+
47
+ ```python
48
+ from injection import set_constant
49
+
50
+ app = set_constant(Application())
51
+ ```
43
52
 
44
53
  ## Inject an instance
45
54
 
@@ -57,6 +66,9 @@ def my_function(instance: Injectable):
57
66
  If `inject` decorates a class, it will be applied to the `__init__` method.
58
67
  _Especially useful for dataclasses:_
59
68
 
69
+ > **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
70
+ > dependencies.
71
+
60
72
  ```python
61
73
  from dataclasses import dataclass
62
74
 
@@ -68,9 +80,6 @@ class DataClass:
68
80
  instance: Injectable = ...
69
81
  ```
70
82
 
71
- > **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
72
- > dependencies.
73
-
74
83
  ## Get an instance
75
84
 
76
85
  _Example with `get_instance` function:_
@@ -1,17 +1,17 @@
1
1
  injection/__init__.py,sha256=9_AVJILxKIBiL_6KJSh2RRydgSHXH38uahAGD0S1-dI,20
2
- injection/_pkg.py,sha256=OnmTG62FZMZteNRfxQKBejiOiedY5I0_C3UWzikex-4,472
3
- injection/_pkg.pyi,sha256=4I7mMRot03oKjsdZQPlJmR_YOD3huFY0O2CFsawB1RQ,3696
2
+ injection/_pkg.py,sha256=7Ns-9VxyGeIf2fOVfOAa5rD_kxAcY1WSHGSkKYAW4T0,536
3
+ injection/_pkg.pyi,sha256=YfSr-pwahHfVx8wak4JxDcXsgrIohd2ZJ73GgysFLNA,4209
4
4
  injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  injection/common/event.py,sha256=uFoGRnxxkohH53JEStn4tN2Pn79HlgGExh7VkXdBwVQ,1316
6
6
  injection/common/lazy.py,sha256=HIefQ1z7ivgU791MDSwBmUcdST3bOv0sivSMyR2DfHc,1493
7
7
  injection/common/tools/__init__.py,sha256=S2y9DaQ4EaTRty9fKpBtPXA7hSjkzgM5N2jFf5jcmJU,21
8
- injection/common/tools/_type.py,sha256=0nPMAwcobITVbd7_MOZXmiZFF8bQnb5FeTPNveH9Flo,313
8
+ injection/common/tools/_type.py,sha256=3hElLUPtiG6_kmxeWiNabX5zNKN_m1vUc3gBVdOf9WY,887
9
9
  injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
10
- injection/core/module.py,sha256=HlM9-x5evGIgVImP9Zdnx_DdlLvLMHvCRQ0Fnz2j18s,15537
10
+ injection/core/module.py,sha256=Lm0uc0uUvVQLfnXwfH6xaXoHYuPvdbR8HgFXWrZmi5Y,15595
11
11
  injection/exceptions.py,sha256=wd4OxmpneGEmlZ0yeNBfnCYfPYqUksfbmA2mopLNm_s,688
12
12
  injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  injection/integrations/blacksheep.py,sha256=F4h85VS9dLGHn-ARDARYMB02Htsd3nKMT9Y-EF4cOqE,818
14
14
  injection/utils.py,sha256=a4w2SXyXP1A8AC_4hyEypByVUZOwSFIA3lh8StLxuLY,590
15
- python_injection-0.6.6.dist-info/METADATA,sha256=fJTPLAW6_b1sfFXSsjuY40zRGGiokvRMH0l8uCiZcJ8,3019
16
- python_injection-0.6.6.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
17
- python_injection-0.6.6.dist-info/RECORD,,
15
+ python_injection-0.6.8.dist-info/METADATA,sha256=4E21PIf33RBie3Wje2_h58VBDSkBwhKGn7gZAuai6q0,3227
16
+ python_injection-0.6.8.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
17
+ python_injection-0.6.8.dist-info/RECORD,,