python-injection 0.6.7__tar.gz → 0.6.8__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.6.7
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:_
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Create an injectable
4
4
 
5
+ > **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
6
+
5
7
  If you wish to inject a singleton, use `singleton` decorator.
6
8
 
7
9
  ```python
@@ -22,7 +24,14 @@ class Injectable:
22
24
  """ class implementation """
23
25
  ```
24
26
 
25
- > **Note**: If the class needs dependencies, these will be resolved when the instance is retrieved.
27
+ If you have a constant (such as a global variable) and wish to register it as an injectable, use `set_constant`
28
+ function.
29
+
30
+ ```python
31
+ from injection import set_constant
32
+
33
+ app = set_constant(Application())
34
+ ```
26
35
 
27
36
  ## Inject an instance
28
37
 
@@ -40,6 +49,9 @@ def my_function(instance: Injectable):
40
49
  If `inject` decorates a class, it will be applied to the `__init__` method.
41
50
  _Especially useful for dataclasses:_
42
51
 
52
+ > **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
53
+ > dependencies.
54
+
43
55
  ```python
44
56
  from dataclasses import dataclass
45
57
 
@@ -51,9 +63,6 @@ class DataClass:
51
63
  instance: Injectable = ...
52
64
  ```
53
65
 
54
- > **Note**: Doesn't work with Pydantic `BaseModel` because the signature of the `__init__` method doesn't contain the
55
- > dependencies.
56
-
57
66
  ## Get an instance
58
67
 
59
68
  _Example with `get_instance` function:_
@@ -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
@@ -17,6 +17,8 @@ inject = default_module.inject
17
17
  injectable = default_module.injectable
18
18
  singleton = default_module.singleton
19
19
 
20
+ set_constant = default_module.set_constant
21
+
20
22
  @final
21
23
  class Module:
22
24
  """
@@ -59,12 +61,22 @@ class Module:
59
61
  singleton will be constructed. At injection time, the injected instance will
60
62
  always be the same.
61
63
  """
62
- def get_instance(self, cls: type[_T] | UnionType) -> _T | None:
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
+ """
74
+ def get_instance(self, cls: type[_T]) -> _T | None:
63
75
  """
64
76
  Function used to retrieve an instance associated with the type passed in
65
77
  parameter or return `None`.
66
78
  """
67
- def get_lazy_instance(self, cls: type[_T] | UnionType) -> Lazy[_T | None]:
79
+ def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
68
80
  """
69
81
  Function used to retrieve an instance associated with the type passed in
70
82
  parameter or `None`. Return a `Lazy` object. To access the instance contained
@@ -0,0 +1,36 @@
1
+ from collections.abc import Iterator
2
+ from types import NoneType, UnionType
3
+ from typing import Annotated, Any, Union, get_args, get_origin
4
+
5
+ __all__ = ("format_type", "get_origins")
6
+
7
+
8
+ def format_type(cls: type | Any) -> str:
9
+ try:
10
+ return f"{cls.__module__}.{cls.__qualname__}"
11
+ except AttributeError:
12
+ return str(cls)
13
+
14
+
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
@@ -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
  """
@@ -211,7 +212,7 @@ class Container:
211
212
  def __injectables(self) -> frozenset[Injectable]:
212
213
  return frozenset(self.__data.values())
213
214
 
214
- def update(self, classes: Iterable[type] | UnionType, injectable: Injectable):
215
+ def update(self, classes: Types, injectable: Injectable):
215
216
  classes = frozenset(get_origins(*classes))
216
217
 
217
218
  if classes:
@@ -308,7 +309,16 @@ class Module(EventListener):
308
309
  yield from tuple(self.__modules)
309
310
  yield self.__container
310
311
 
311
- def get_instance(self, cls: type[_T] | UnionType) -> _T | None:
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
+
321
+ def get_instance(self, cls: type[_T]) -> _T | None:
312
322
  try:
313
323
  injectable = self[cls]
314
324
  except KeyError:
@@ -317,10 +327,10 @@ class Module(EventListener):
317
327
  instance = injectable.get_instance()
318
328
  return cast(cls, instance)
319
329
 
320
- def get_lazy_instance(self, cls: type[_T] | UnionType) -> Lazy[_T | None]:
330
+ def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
321
331
  return Lazy(lambda: self.get_instance(cls))
322
332
 
323
- def update(self, classes: Iterable[type] | UnionType, injectable: Injectable):
333
+ def update(self, classes: Types, injectable: Injectable):
324
334
  self.__container.update(classes, injectable)
325
335
  return self
326
336
 
@@ -548,39 +558,29 @@ class InjectableDecorator:
548
558
  wrapped: Callable[..., Any] = None,
549
559
  /,
550
560
  *,
551
- on: type | Iterable[type] | UnionType = None,
561
+ on: type | Types = None,
552
562
  ):
553
563
  def decorator(wp):
554
- @lambda fn: fn()
555
- def classes():
556
- if cls := self.__get_class(wp):
557
- yield cls
558
-
559
- if on is None:
560
- return
561
- elif isinstance(on, type | str):
562
- yield on
563
- else:
564
- yield from on
565
-
566
564
  @self.__module.inject
567
565
  @wraps(wp, updated=())
568
566
  def factory(*args, **kwargs):
569
567
  return wp(*args, **kwargs)
570
568
 
571
569
  injectable = self.__injectable_type(factory)
570
+ classes = self.__get_classes(wp, on)
572
571
  self.__module.update(classes, injectable)
573
-
574
572
  return wp
575
573
 
576
574
  return decorator(wrapped) if wrapped else decorator
577
575
 
578
- @staticmethod
579
- def __get_class(wrapped: Callable[..., Any]) -> type | None:
580
- if isclass(wrapped):
581
- return wrapped
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)
582
581
 
583
- if isfunction(wrapped):
584
- return get_annotations(wrapped, eval_str=True).get("return")
582
+ elif isfunction(obj):
583
+ yield get_annotations(obj, eval_str=True).get("return")
585
584
 
586
- return None
585
+ else:
586
+ yield obj
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-injection"
3
- version = "0.6.7"
3
+ version = "0.6.8"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  authors = ["remimd"]
6
6
  keywords = ["dependencies", "inject", "injection"]
@@ -14,7 +14,7 @@ python = ">=3.10, <4"
14
14
 
15
15
  [tool.poetry.group.dev.dependencies]
16
16
  black = "*"
17
- blacksheep = "^2.0.4"
17
+ blacksheep = "^2.0.5"
18
18
  flake8 = "*"
19
19
  isort = "*"
20
20
  pydantic = "^2.5.3"
@@ -1,35 +0,0 @@
1
- from types import NoneType, UnionType
2
- from typing import Any, Iterator, Union, get_args
3
-
4
- __all__ = ("format_type", "get_origins")
5
-
6
-
7
- def format_type(cls: type | Any) -> str:
8
- try:
9
- return f"{cls.__module__}.{cls.__qualname__}"
10
- except AttributeError:
11
- return str(cls)
12
-
13
-
14
- def get_full_origin(cls: type | Any) -> type | Any:
15
- try:
16
- origin = cls.__origin__
17
- except AttributeError:
18
- return cls
19
-
20
- return get_full_origin(origin)
21
-
22
-
23
- def get_origins(*classes: type | Any) -> Iterator[type | Any]:
24
- for cls in classes:
25
- if cls in (None, NoneType):
26
- continue
27
-
28
- origin = get_full_origin(cls)
29
-
30
- if origin is Union or isinstance(cls, UnionType):
31
- for argument in get_args(cls):
32
- yield from get_origins(argument)
33
-
34
- else:
35
- yield origin