python-injection 0.8.5__tar.gz → 0.9.0__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.

Files changed (20) hide show
  1. {python_injection-0.8.5 → python_injection-0.9.0}/PKG-INFO +2 -4
  2. {python_injection-0.8.5 → python_injection-0.9.0}/injection/__init__.pyi +44 -38
  3. {python_injection-0.8.5 → python_injection-0.9.0}/injection/common/event.py +3 -3
  4. {python_injection-0.8.5 → python_injection-0.9.0}/injection/common/invertible.py +6 -8
  5. {python_injection-0.8.5 → python_injection-0.9.0}/injection/common/lazy.py +9 -14
  6. {python_injection-0.8.5 → python_injection-0.9.0}/injection/common/tools/threading.py +1 -3
  7. python_injection-0.9.0/injection/common/tools/type.py +73 -0
  8. {python_injection-0.8.5 → python_injection-0.9.0}/injection/core/module.py +113 -94
  9. {python_injection-0.8.5 → python_injection-0.9.0}/injection/exceptions.py +4 -6
  10. {python_injection-0.8.5 → python_injection-0.9.0}/injection/integrations/blacksheep.py +2 -4
  11. {python_injection-0.8.5 → python_injection-0.9.0}/pyproject.toml +3 -2
  12. python_injection-0.8.5/injection/common/tools/type.py +0 -48
  13. {python_injection-0.8.5 → python_injection-0.9.0}/documentation/basic-usage.md +0 -0
  14. {python_injection-0.8.5 → python_injection-0.9.0}/injection/__init__.py +0 -0
  15. {python_injection-0.8.5 → python_injection-0.9.0}/injection/common/__init__.py +0 -0
  16. {python_injection-0.8.5 → python_injection-0.9.0}/injection/common/tools/__init__.py +0 -0
  17. {python_injection-0.8.5 → python_injection-0.9.0}/injection/core/__init__.py +0 -0
  18. {python_injection-0.8.5 → python_injection-0.9.0}/injection/integrations/__init__.py +0 -0
  19. {python_injection-0.8.5 → python_injection-0.9.0}/injection/py.typed +0 -0
  20. {python_injection-0.8.5 → python_injection-0.9.0}/injection/utils.py +0 -0
@@ -1,16 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.8.5
3
+ Version: 0.9.0
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
7
7
  Keywords: dependencies,inject,injection
8
8
  Author: remimd
9
- Requires-Python: >=3.10,<4
9
+ Requires-Python: >=3.12,<4
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
12
  Classifier: Programming Language :: Python :: 3.12
15
13
  Project-URL: Repository, https://github.com/100nm/python-injection
16
14
  Description-Content-Type: text/markdown
@@ -1,23 +1,23 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import Callable, Iterable
2
+ from collections.abc import Callable
3
3
  from contextlib import ContextDecorator
4
- from enum import Enum
4
+ from enum import StrEnum
5
5
  from types import UnionType
6
6
  from typing import (
7
7
  Any,
8
8
  ContextManager,
9
9
  Final,
10
- Literal,
11
10
  Protocol,
12
- TypeVar,
11
+ Self,
13
12
  final,
14
13
  runtime_checkable,
15
14
  )
16
15
 
17
16
  from .common.invertible import Invertible
18
-
19
- _T = TypeVar("_T")
20
- _T_co = TypeVar("_T_co", covariant=True)
17
+ from .common.tools.type import TypeInfo
18
+ from .core import InjectableFactory
19
+ from .core import ModeStr as InjectableModeStr
20
+ from .core import PriorityStr as ModulePriorityStr
21
21
 
22
22
  default_module: Final[Module] = ...
23
23
 
@@ -41,6 +41,8 @@ class Module:
41
41
 
42
42
  def __init__(self, name: str = ...): ...
43
43
  def __contains__(self, cls: type | UnionType, /) -> bool: ...
44
+ @property
45
+ def is_locked(self) -> bool: ...
44
46
  def inject(self, wrapped: Callable[..., Any] = ..., /):
45
47
  """
46
48
  Decorator applicable to a class or function. Inject function dependencies using
@@ -48,15 +50,15 @@ class Module:
48
50
  will be those of the `__init__` method.
49
51
  """
50
52
 
51
- def injectable(
53
+ def injectable[T](
52
54
  self,
53
- wrapped: Callable[..., Any] = ...,
55
+ wrapped: Callable[..., T] = ...,
54
56
  /,
55
57
  *,
56
- cls: type[Injectable] = ...,
58
+ cls: InjectableFactory[T] = ...,
57
59
  inject: bool = ...,
58
- on: type | Iterable[type] | UnionType = ...,
59
- mode: InjectableMode | Literal["fallback", "normal", "override"] = ...,
60
+ on: TypeInfo[T] = ...,
61
+ mode: InjectableMode | InjectableModeStr = ...,
60
62
  ):
61
63
  """
62
64
  Decorator applicable to a class or function. It is used to indicate how the
@@ -64,14 +66,14 @@ class Module:
64
66
  injected each time.
65
67
  """
66
68
 
67
- def singleton(
69
+ def singleton[T](
68
70
  self,
69
- wrapped: Callable[..., Any] = ...,
71
+ wrapped: Callable[..., T] = ...,
70
72
  /,
71
73
  *,
72
74
  inject: bool = ...,
73
- on: type | Iterable[type] | UnionType = ...,
74
- mode: InjectableMode | Literal["fallback", "normal", "override"] = ...,
75
+ on: TypeInfo[T] = ...,
76
+ mode: InjectableMode | InjectableModeStr = ...,
75
77
  ):
76
78
  """
77
79
  Decorator applicable to a class or function. It is used to indicate how the
@@ -86,37 +88,37 @@ class Module:
86
88
  registered.
87
89
  """
88
90
 
89
- def set_constant(
91
+ def set_constant[T](
90
92
  self,
91
- instance: _T,
92
- on: type | Iterable[type] | UnionType = ...,
93
+ instance: T,
94
+ on: TypeInfo[T] = ...,
93
95
  *,
94
- mode: InjectableMode | Literal["fallback", "normal", "override"] = ...,
95
- ) -> _T:
96
+ mode: InjectableMode | InjectableModeStr = ...,
97
+ ) -> T:
96
98
  """
97
99
  Function for registering a specific instance to be injected. This is useful for
98
100
  registering global variables. The difference with the singleton decorator is
99
101
  that no dependencies are resolved, so the module doesn't need to be locked.
100
102
  """
101
103
 
102
- def resolve(self, cls: type[_T]) -> _T:
104
+ def resolve[T](self, cls: type[T]) -> T:
103
105
  """
104
106
  Function used to retrieve an instance associated with the type passed in
105
107
  parameter or an exception will be raised.
106
108
  """
107
109
 
108
- def get_instance(self, cls: type[_T]) -> _T | None:
110
+ def get_instance[T](self, cls: type[T]) -> T | None:
109
111
  """
110
112
  Function used to retrieve an instance associated with the type passed in
111
113
  parameter or return `None`.
112
114
  """
113
115
 
114
- def get_lazy_instance(
116
+ def get_lazy_instance[T](
115
117
  self,
116
- cls: type[_T],
118
+ cls: type[T],
117
119
  *,
118
120
  cache: bool = ...,
119
- ) -> Invertible[_T | None]:
121
+ ) -> Invertible[T | None]:
120
122
  """
121
123
  Function used to retrieve an instance associated with the type passed in
122
124
  parameter or `None`. Return a `Invertible` object. To access the instance
@@ -126,19 +128,24 @@ class Module:
126
128
  Example: instance = ~lazy_instance
127
129
  """
128
130
 
131
+ def init_modules(self, *modules: Module) -> Self:
132
+ """
133
+ Function to clean modules in use and to use those passed as parameters.
134
+ """
135
+
129
136
  def use(
130
137
  self,
131
138
  module: Module,
132
139
  *,
133
- priority: ModulePriority | Literal["low", "high"] = ...,
134
- ):
140
+ priority: ModulePriority | ModulePriorityStr = ...,
141
+ ) -> Self:
135
142
  """
136
143
  Function for using another module. Using another module replaces the module's
137
144
  dependencies with those of the module used. If the dependency is not found, it
138
145
  will be searched for in the module's dependency container.
139
146
  """
140
147
 
141
- def stop_using(self, module: Module):
148
+ def stop_using(self, module: Module) -> Self:
142
149
  """
143
150
  Function to remove a module in use.
144
151
  """
@@ -147,7 +154,7 @@ class Module:
147
154
  self,
148
155
  module: Module,
149
156
  *,
150
- priority: ModulePriority | Literal["low", "high"] = ...,
157
+ priority: ModulePriority | ModulePriorityStr = ...,
151
158
  ) -> ContextManager | ContextDecorator:
152
159
  """
153
160
  Context manager or decorator for temporary use of a module.
@@ -156,8 +163,8 @@ class Module:
156
163
  def change_priority(
157
164
  self,
158
165
  module: Module,
159
- priority: ModulePriority | Literal["low", "high"],
160
- ):
166
+ priority: ModulePriority | ModulePriorityStr,
167
+ ) -> Self:
161
168
  """
162
169
  Function for changing the priority of a module in use.
163
170
  There are two priority values:
@@ -166,27 +173,26 @@ class Module:
166
173
  * **HIGH**: The module concerned becomes the most important of the modules used.
167
174
  """
168
175
 
169
- def unlock(self):
176
+ def unlock(self) -> Self:
170
177
  """
171
178
  Function to unlock the module by deleting cached instances of singletons.
172
179
  """
173
180
 
174
181
  @final
175
- class ModulePriority(str, Enum):
182
+ class ModulePriority(StrEnum):
176
183
  LOW = ...
177
184
  HIGH = ...
178
185
 
179
186
  @runtime_checkable
180
- class Injectable(Protocol[_T_co]):
181
- def __init__(self, factory: Callable[[], _T_co] = ..., /): ...
187
+ class Injectable[T](Protocol):
182
188
  @property
183
189
  def is_locked(self) -> bool: ...
184
190
  def unlock(self): ...
185
191
  @abstractmethod
186
- def get_instance(self) -> _T_co: ...
192
+ def get_instance(self) -> T: ...
187
193
 
188
194
  @final
189
- class InjectableMode(str, Enum):
195
+ class InjectableMode(StrEnum):
190
196
  FALLBACK = ...
191
197
  NORMAL = ...
192
198
  OVERRIDE = ...
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from contextlib import ExitStack, contextmanager, suppress
3
3
  from dataclasses import dataclass, field
4
- from typing import ContextManager
4
+ from typing import ContextManager, Self
5
5
  from weakref import WeakSet
6
6
 
7
7
  __all__ = ("Event", "EventChannel", "EventListener")
@@ -36,11 +36,11 @@ class EventChannel:
36
36
 
37
37
  yield
38
38
 
39
- def add_listener(self, listener: EventListener):
39
+ def add_listener(self, listener: EventListener) -> Self:
40
40
  self.__listeners.add(listener)
41
41
  return self
42
42
 
43
- def remove_listener(self, listener: EventListener):
43
+ def remove_listener(self, listener: EventListener) -> Self:
44
44
  with suppress(KeyError):
45
45
  self.__listeners.remove(listener)
46
46
 
@@ -1,23 +1,21 @@
1
1
  from abc import abstractmethod
2
2
  from collections.abc import Callable
3
3
  from dataclasses import dataclass
4
- from typing import Protocol, TypeVar, runtime_checkable
4
+ from typing import Protocol, runtime_checkable
5
5
 
6
6
  __all__ = ("Invertible", "SimpleInvertible")
7
7
 
8
- _T_co = TypeVar("_T_co", covariant=True)
9
-
10
8
 
11
9
  @runtime_checkable
12
- class Invertible(Protocol[_T_co]):
10
+ class Invertible[T](Protocol):
13
11
  @abstractmethod
14
- def __invert__(self) -> _T_co:
12
+ def __invert__(self) -> T:
15
13
  raise NotImplementedError
16
14
 
17
15
 
18
16
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
19
- class SimpleInvertible(Invertible[_T_co]):
20
- callable: Callable[[], _T_co]
17
+ class SimpleInvertible[T](Invertible[T]):
18
+ callable: Callable[[], T]
21
19
 
22
- def __invert__(self) -> _T_co:
20
+ def __invert__(self) -> T:
23
21
  return self.callable()
@@ -1,31 +1,26 @@
1
1
  from collections.abc import Callable, Iterator, Mapping
2
2
  from types import MappingProxyType
3
- from typing import TypeVar
4
3
 
5
4
  from injection.common.invertible import Invertible
6
5
 
7
6
  __all__ = ("Lazy", "LazyMapping")
8
7
 
9
- _T = TypeVar("_T")
10
- _K = TypeVar("_K")
11
- _V = TypeVar("_V")
12
8
 
13
-
14
- class Lazy(Invertible[_T]):
9
+ class Lazy[T](Invertible[T]):
15
10
  __slots__ = ("__cache", "__is_set")
16
11
 
17
- def __init__(self, factory: Callable[[], _T]):
12
+ def __init__(self, factory: Callable[[], T]):
18
13
  self.__setup_cache(factory)
19
14
 
20
- def __invert__(self) -> _T:
15
+ def __invert__(self) -> T:
21
16
  return next(self.__cache)
22
17
 
23
18
  @property
24
19
  def is_set(self) -> bool:
25
20
  return self.__is_set
26
21
 
27
- def __setup_cache(self, factory: Callable[[], _T]):
28
- def cache_generator() -> Iterator[_T]:
22
+ def __setup_cache(self, factory: Callable[[], T]):
23
+ def cache_generator() -> Iterator[T]:
29
24
  nonlocal factory
30
25
  cached = factory()
31
26
  self.__is_set = True
@@ -38,16 +33,16 @@ class Lazy(Invertible[_T]):
38
33
  self.__is_set = False
39
34
 
40
35
 
41
- class LazyMapping(Mapping[_K, _V]):
36
+ class LazyMapping[K, V](Mapping[K, V]):
42
37
  __slots__ = ("__lazy",)
43
38
 
44
- def __init__(self, iterator: Iterator[tuple[_K, _V]]):
39
+ def __init__(self, iterator: Iterator[tuple[K, V]]):
45
40
  self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
46
41
 
47
- def __getitem__(self, key: _K, /) -> _V:
42
+ def __getitem__(self, key: K, /) -> V:
48
43
  return (~self.__lazy)[key]
49
44
 
50
- def __iter__(self) -> Iterator[_K]:
45
+ def __iter__(self) -> Iterator[K]:
51
46
  yield from ~self.__lazy
52
47
 
53
48
  def __len__(self) -> int:
@@ -3,10 +3,8 @@ from threading import RLock
3
3
 
4
4
  __all__ = ("synchronized",)
5
5
 
6
- __lock = RLock()
7
-
8
6
 
9
7
  @contextmanager
10
8
  def synchronized():
11
- with __lock:
9
+ with RLock():
12
10
  yield
@@ -0,0 +1,73 @@
1
+ from collections.abc import Callable, Iterable, Iterator
2
+ from inspect import get_annotations, isfunction
3
+ from types import UnionType
4
+ from typing import (
5
+ Annotated,
6
+ Any,
7
+ NamedTuple,
8
+ Self,
9
+ Union,
10
+ get_args,
11
+ get_origin,
12
+ )
13
+
14
+ __all__ = ("TypeInfo", "TypeReport", "analyze_types", "get_return_types")
15
+
16
+ type TypeInfo[T] = type[T] | Callable[..., T] | Iterable[TypeInfo[T]] | UnionType
17
+
18
+
19
+ class TypeReport[T](NamedTuple):
20
+ origin: type[T]
21
+ args: tuple[Any, ...]
22
+
23
+ @property
24
+ def cls(self) -> type[T]:
25
+ if self.args:
26
+ return self.origin[*self.args]
27
+
28
+ return self.origin
29
+
30
+ @property
31
+ def no_args(self) -> Self:
32
+ if self.args:
33
+ return type(self)(self.origin, ())
34
+
35
+ return self
36
+
37
+
38
+ def analyze_types(*types: type | Any) -> Iterator[TypeReport[Any]]:
39
+ for tp in types:
40
+ if tp is None:
41
+ continue
42
+
43
+ origin = get_origin(tp)
44
+
45
+ if origin is Union or isinstance(tp, UnionType):
46
+ inner_types = get_args(tp)
47
+
48
+ elif origin is Annotated:
49
+ inner_types = get_args(tp)[:1]
50
+
51
+ else:
52
+ yield TypeReport(origin or tp, get_args(tp))
53
+ continue
54
+
55
+ yield from analyze_types(*inner_types)
56
+
57
+
58
+ def get_return_types(*args: TypeInfo[Any]) -> Iterator[type | UnionType]:
59
+ for arg in args:
60
+ if isinstance(arg, Iterable) and not isinstance(
61
+ get_origin(arg) or arg,
62
+ type | str,
63
+ ):
64
+ inner_args = arg
65
+
66
+ elif isfunction(arg):
67
+ inner_args = (get_annotations(arg, eval_str=True).get("return"),)
68
+
69
+ else:
70
+ yield arg
71
+ continue
72
+
73
+ yield from get_return_types(*inner_args)
@@ -14,7 +14,7 @@ from collections.abc import (
14
14
  )
15
15
  from contextlib import contextmanager, suppress
16
16
  from dataclasses import dataclass, field
17
- from enum import Enum
17
+ from enum import StrEnum
18
18
  from functools import partialmethod, singledispatchmethod, update_wrapper
19
19
  from inspect import Signature, isclass
20
20
  from queue import Queue
@@ -27,7 +27,7 @@ from typing import (
27
27
  NamedTuple,
28
28
  NoReturn,
29
29
  Protocol,
30
- TypeVar,
30
+ Self,
31
31
  runtime_checkable,
32
32
  )
33
33
 
@@ -35,7 +35,12 @@ from injection.common.event import Event, EventChannel, EventListener
35
35
  from injection.common.invertible import Invertible, SimpleInvertible
36
36
  from injection.common.lazy import Lazy, LazyMapping
37
37
  from injection.common.tools.threading import synchronized
38
- from injection.common.tools.type import find_types, format_type, get_origins
38
+ from injection.common.tools.type import (
39
+ TypeInfo,
40
+ TypeReport,
41
+ analyze_types,
42
+ get_return_types,
43
+ )
39
44
  from injection.exceptions import (
40
45
  InjectionError,
41
46
  ModuleError,
@@ -44,13 +49,18 @@ from injection.exceptions import (
44
49
  NoInjectable,
45
50
  )
46
51
 
47
- __all__ = ("Injectable", "Mode", "Module", "Priority")
52
+ __all__ = (
53
+ "Injectable",
54
+ "InjectableFactory",
55
+ "Mode",
56
+ "ModeStr",
57
+ "Module",
58
+ "Priority",
59
+ "PriorityStr",
60
+ )
48
61
 
49
62
  _logger = logging.getLogger(__name__)
50
63
 
51
- _T = TypeVar("_T")
52
- _T_co = TypeVar("_T_co", covariant=True)
53
-
54
64
  """
55
65
  Events
56
66
  """
@@ -63,12 +73,12 @@ class ContainerEvent(Event, ABC):
63
73
 
64
74
  @dataclass(frozen=True, slots=True)
65
75
  class ContainerDependenciesUpdated(ContainerEvent):
66
- classes: Collection[type]
76
+ reports: Collection[TypeReport]
67
77
  mode: Mode
68
78
 
69
79
  def __str__(self) -> str:
70
- length = len(self.classes)
71
- formatted_classes = ", ".join(f"`{format_type(cls)}`" for cls in self.classes)
80
+ length = len(self.reports)
81
+ formatted_classes = ", ".join(f"`{report.cls}`" for report in self.reports)
72
82
  return (
73
83
  f"{length} container dependenc{'ies' if length > 1 else 'y'} have been "
74
84
  f"updated{f': {formatted_classes}' if formatted_classes else ''}."
@@ -134,12 +144,9 @@ Injectables
134
144
 
135
145
 
136
146
  @runtime_checkable
137
- class Injectable(Protocol[_T_co]):
147
+ class Injectable[T](Protocol):
138
148
  __slots__ = ()
139
149
 
140
- def __init__(self, __factory: Callable[[], _T_co] = None, /):
141
- pass
142
-
143
150
  @property
144
151
  def is_locked(self) -> bool:
145
152
  return False
@@ -148,23 +155,23 @@ class Injectable(Protocol[_T_co]):
148
155
  return
149
156
 
150
157
  @abstractmethod
151
- def get_instance(self) -> _T_co:
158
+ def get_instance(self) -> T:
152
159
  raise NotImplementedError
153
160
 
154
161
 
155
162
  @dataclass(repr=False, frozen=True, slots=True)
156
- class BaseInjectable(Injectable[_T], ABC):
157
- factory: Callable[[], _T]
163
+ class BaseInjectable[T](Injectable[T], ABC):
164
+ factory: Callable[[], T]
158
165
 
159
166
 
160
- class NewInjectable(BaseInjectable[_T]):
167
+ class NewInjectable[T](BaseInjectable[T]):
161
168
  __slots__ = ()
162
169
 
163
- def get_instance(self) -> _T:
170
+ def get_instance(self) -> T:
164
171
  return self.factory()
165
172
 
166
173
 
167
- class SingletonInjectable(BaseInjectable[_T]):
174
+ class SingletonInjectable[T](BaseInjectable[T]):
168
175
  __slots__ = ("__dict__",)
169
176
 
170
177
  __INSTANCE_KEY: ClassVar[str] = "$instance"
@@ -180,7 +187,7 @@ class SingletonInjectable(BaseInjectable[_T]):
180
187
  def unlock(self):
181
188
  self.cache.clear()
182
189
 
183
- def get_instance(self) -> _T:
190
+ def get_instance(self) -> T:
184
191
  with suppress(KeyError):
185
192
  return self.cache[self.__INSTANCE_KEY]
186
193
 
@@ -192,11 +199,11 @@ class SingletonInjectable(BaseInjectable[_T]):
192
199
 
193
200
 
194
201
  @dataclass(repr=False, frozen=True, slots=True)
195
- class ShouldBeInjectable(Injectable[_T]):
196
- cls: type[_T]
202
+ class ShouldBeInjectable[T](Injectable[T]):
203
+ cls: type[T]
197
204
 
198
205
  def get_instance(self) -> NoReturn:
199
- raise InjectionError(f"`{format_type(self.cls)}` should be an injectable.")
206
+ raise InjectionError(f"`{self.cls}` should be an injectable.")
200
207
 
201
208
 
202
209
  """
@@ -209,7 +216,7 @@ class Broker(Protocol):
209
216
  __slots__ = ()
210
217
 
211
218
  @abstractmethod
212
- def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
219
+ def __getitem__[T](self, cls: type[T] | UnionType, /) -> Injectable[T]:
213
220
  raise NotImplementedError
214
221
 
215
222
  @abstractmethod
@@ -222,7 +229,7 @@ class Broker(Protocol):
222
229
  raise NotImplementedError
223
230
 
224
231
  @abstractmethod
225
- def unlock(self):
232
+ def unlock(self) -> Self:
226
233
  raise NotImplementedError
227
234
 
228
235
 
@@ -231,7 +238,7 @@ Container
231
238
  """
232
239
 
233
240
 
234
- class Mode(str, Enum):
241
+ class Mode(StrEnum):
235
242
  FALLBACK = "fallback"
236
243
  NORMAL = "normal"
237
244
  OVERRIDE = "override"
@@ -241,36 +248,37 @@ class Mode(str, Enum):
241
248
  return tuple(type(self)).index(self)
242
249
 
243
250
  @classmethod
244
- def get_default(cls):
251
+ def get_default(cls) -> Mode:
245
252
  return cls.NORMAL
246
253
 
247
254
 
248
- ModeStr = Literal["fallback", "normal", "override"]
255
+ type ModeStr = Literal["fallback", "normal", "override"]
249
256
 
250
257
 
251
- class Record(NamedTuple):
252
- injectable: Injectable
258
+ class Record[T](NamedTuple):
259
+ injectable: Injectable[T]
253
260
  mode: Mode
254
261
 
255
262
 
256
263
  @dataclass(repr=False, frozen=True, slots=True)
257
264
  class Container(Broker):
258
- __records: dict[type, Record] = field(default_factory=dict, init=False)
265
+ __records: dict[TypeReport, Record] = field(default_factory=dict, init=False)
259
266
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
260
267
 
261
- def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
262
- for cls in get_origins(cls):
263
- try:
264
- injectable, _ = self.__records[cls]
265
- except KeyError:
266
- continue
268
+ def __getitem__[T](self, cls: type[T] | UnionType, /) -> Injectable[T]:
269
+ for report in analyze_types(cls):
270
+ for scoped_report in dict.fromkeys((report, report.no_args)):
271
+ try:
272
+ injectable, _ = self.__records[scoped_report]
273
+ except KeyError:
274
+ continue
267
275
 
268
- return injectable
276
+ return injectable
269
277
 
270
278
  raise NoInjectable(cls)
271
279
 
272
280
  def __contains__(self, cls: type | UnionType, /) -> bool:
273
- return any(cls in self.__records for cls in get_origins(cls))
281
+ return any(report in self.__records for report in analyze_types(cls))
274
282
 
275
283
  @property
276
284
  def is_locked(self) -> bool:
@@ -281,16 +289,16 @@ class Container(Broker):
281
289
  return frozenset(injectable for injectable, _ in self.__records.values())
282
290
 
283
291
  @synchronized()
284
- def update(
292
+ def update[T](
285
293
  self,
286
- classes: Iterable[type | UnionType],
287
- injectable: Injectable,
294
+ classes: Iterable[type[T] | UnionType],
295
+ injectable: Injectable[T],
288
296
  mode: Mode | ModeStr,
289
- ):
297
+ ) -> Self:
290
298
  mode = Mode(mode)
291
299
  records = {
292
- cls: Record(injectable, mode)
293
- for cls in self.__classes_to_update(classes, mode)
300
+ report: Record(injectable, mode)
301
+ for report in self.__prepare_reports_for_updating(classes, mode)
294
302
  }
295
303
 
296
304
  if records:
@@ -302,43 +310,43 @@ class Container(Broker):
302
310
  return self
303
311
 
304
312
  @synchronized()
305
- def unlock(self):
313
+ def unlock(self) -> Self:
306
314
  for injectable in self.__injectables:
307
315
  injectable.unlock()
308
316
 
309
317
  return self
310
318
 
311
- def add_listener(self, listener: EventListener):
319
+ def add_listener(self, listener: EventListener) -> Self:
312
320
  self.__channel.add_listener(listener)
313
321
  return self
314
322
 
315
- def notify(self, event: Event):
323
+ def notify(self, event: Event) -> ContextManager:
316
324
  return self.__channel.dispatch(event)
317
325
 
318
- def __classes_to_update(
326
+ def __prepare_reports_for_updating(
319
327
  self,
320
328
  classes: Iterable[type | UnionType],
321
329
  mode: Mode,
322
- ) -> Iterator[type]:
330
+ ) -> Iterator[TypeReport]:
323
331
  rank = mode.rank
324
332
 
325
- for cls in frozenset(get_origins(*classes)):
333
+ for report in frozenset(analyze_types(*classes)):
326
334
  try:
327
- _, current_mode = self.__records[cls]
335
+ _, current_mode = self.__records[report]
328
336
 
329
337
  except KeyError:
330
338
  pass
331
339
 
332
340
  else:
333
- if mode == current_mode:
341
+ if mode == current_mode and mode != Mode.OVERRIDE:
334
342
  raise RuntimeError(
335
- f"An injectable already exists for the class `{format_type(cls)}`."
343
+ f"An injectable already exists for the class `{report.cls}`."
336
344
  )
337
345
 
338
346
  elif rank < current_mode.rank:
339
347
  continue
340
348
 
341
- yield cls
349
+ yield report
342
350
 
343
351
 
344
352
  """
@@ -346,16 +354,18 @@ Module
346
354
  """
347
355
 
348
356
 
349
- class Priority(str, Enum):
357
+ class Priority(StrEnum):
350
358
  LOW = "low"
351
359
  HIGH = "high"
352
360
 
353
361
  @classmethod
354
- def get_default(cls):
362
+ def get_default(cls) -> Priority:
355
363
  return cls.LOW
356
364
 
357
365
 
358
- PriorityStr = Literal["low", "high"]
366
+ type PriorityStr = Literal["low", "high"]
367
+
368
+ type InjectableFactory[T] = Callable[[Callable[..., T]], Injectable[T]]
359
369
 
360
370
 
361
371
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -371,14 +381,14 @@ class Module(EventListener, Broker):
371
381
  def __post_init__(self):
372
382
  self.__container.add_listener(self)
373
383
 
374
- def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
384
+ def __getitem__[T](self, cls: type[T] | UnionType, /) -> Injectable[T]:
375
385
  for broker in self.__brokers:
376
386
  with suppress(KeyError):
377
387
  return broker[cls]
378
388
 
379
389
  raise NoInjectable(cls)
380
390
 
381
- def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
391
+ def __setitem__[T](self, cls: type[T] | UnionType, injectable: Injectable[T], /):
382
392
  self.update((cls,), injectable)
383
393
 
384
394
  def __contains__(self, cls: type | UnionType, /) -> bool:
@@ -396,20 +406,20 @@ class Module(EventListener, Broker):
396
406
  yield from tuple(self.__modules)
397
407
  yield self.__container
398
408
 
399
- def injectable(
409
+ def injectable[T](
400
410
  self,
401
- wrapped: Callable[..., Any] = None,
411
+ wrapped: Callable[..., T] = None,
402
412
  /,
403
413
  *,
404
- cls: type[Injectable] = NewInjectable,
414
+ cls: InjectableFactory[T] = NewInjectable,
405
415
  inject: bool = True,
406
- on: type | Iterable[type] | UnionType = (),
416
+ on: TypeInfo[T] = (),
407
417
  mode: Mode | ModeStr = Mode.get_default(),
408
418
  ):
409
419
  def decorator(wp):
410
420
  factory = self.inject(wp, return_factory=True) if inject else wp
411
421
  injectable = cls(factory)
412
- classes = find_types(wp, on)
422
+ classes = get_return_types(wp, on)
413
423
  self.update(classes, injectable, mode)
414
424
  return wp
415
425
 
@@ -428,18 +438,18 @@ class Module(EventListener, Broker):
428
438
 
429
439
  return decorator(wrapped) if wrapped else decorator
430
440
 
431
- def set_constant(
441
+ def set_constant[T](
432
442
  self,
433
- instance: _T,
434
- on: type | Iterable[type] | UnionType = (),
443
+ instance: T,
444
+ on: TypeInfo[T] = (),
435
445
  *,
436
446
  mode: Mode | ModeStr = Mode.get_default(),
437
- ) -> _T:
447
+ ) -> T:
438
448
  cls = type(instance)
439
449
  self.injectable(
440
450
  lambda: instance,
441
451
  inject=False,
442
- on=(cls, on), # type: ignore
452
+ on=(cls, on),
443
453
  mode=mode,
444
454
  )
445
455
  return instance
@@ -467,22 +477,22 @@ class Module(EventListener, Broker):
467
477
 
468
478
  return decorator(wrapped) if wrapped else decorator
469
479
 
470
- def resolve(self, cls: type[_T]) -> _T:
480
+ def resolve[T](self, cls: type[T]) -> T:
471
481
  injectable = self[cls]
472
482
  return injectable.get_instance()
473
483
 
474
- def get_instance(self, cls: type[_T]) -> _T | None:
484
+ def get_instance[T](self, cls: type[T]) -> T | None:
475
485
  try:
476
486
  return self.resolve(cls)
477
487
  except KeyError:
478
488
  return None
479
489
 
480
- def get_lazy_instance(
490
+ def get_lazy_instance[T](
481
491
  self,
482
- cls: type[_T],
492
+ cls: type[T],
483
493
  *,
484
494
  cache: bool = False,
485
- ) -> Invertible[_T | None]:
495
+ ) -> Invertible[T | None]:
486
496
  if cache:
487
497
  return Lazy(lambda: self.get_instance(cls))
488
498
 
@@ -490,21 +500,30 @@ class Module(EventListener, Broker):
490
500
  function.set_owner(cls)
491
501
  return SimpleInvertible(function)
492
502
 
493
- def update(
503
+ def update[T](
494
504
  self,
495
- classes: Iterable[type | UnionType],
496
- injectable: Injectable,
505
+ classes: Iterable[type[T] | UnionType],
506
+ injectable: Injectable[T],
497
507
  mode: Mode | ModeStr = Mode.get_default(),
498
- ):
508
+ ) -> Self:
499
509
  self.__container.update(classes, injectable, mode)
500
510
  return self
501
511
 
512
+ def init_modules(self, *modules: Module) -> Self:
513
+ for module in tuple(self.__modules):
514
+ self.stop_using(module)
515
+
516
+ for module in modules:
517
+ self.use(module)
518
+
519
+ return self
520
+
502
521
  def use(
503
522
  self,
504
523
  module: Module,
505
524
  *,
506
525
  priority: Priority | PriorityStr = Priority.get_default(),
507
- ):
526
+ ) -> Self:
508
527
  if module is self:
509
528
  raise ModuleError("Module can't be used by itself.")
510
529
 
@@ -521,7 +540,7 @@ class Module(EventListener, Broker):
521
540
 
522
541
  return self
523
542
 
524
- def stop_using(self, module: Module):
543
+ def stop_using(self, module: Module) -> Self:
525
544
  event = ModuleRemoved(self, module)
526
545
 
527
546
  with suppress(KeyError):
@@ -542,7 +561,7 @@ class Module(EventListener, Broker):
542
561
  yield
543
562
  self.stop_using(module)
544
563
 
545
- def change_priority(self, module: Module, priority: Priority | PriorityStr):
564
+ def change_priority(self, module: Module, priority: Priority | PriorityStr) -> Self:
546
565
  priority = Priority(priority)
547
566
  event = ModulePriorityUpdated(self, module, priority)
548
567
 
@@ -552,17 +571,17 @@ class Module(EventListener, Broker):
552
571
  return self
553
572
 
554
573
  @synchronized()
555
- def unlock(self):
574
+ def unlock(self) -> Self:
556
575
  for broker in self.__brokers:
557
576
  broker.unlock()
558
577
 
559
578
  return self
560
579
 
561
- def add_listener(self, listener: EventListener):
580
+ def add_listener(self, listener: EventListener) -> Self:
562
581
  self.__channel.add_listener(listener)
563
582
  return self
564
583
 
565
- def remove_listener(self, listener: EventListener):
584
+ def remove_listener(self, listener: EventListener) -> Self:
566
585
  self.__channel.remove_listener(listener)
567
586
  return self
568
587
 
@@ -621,15 +640,15 @@ class Dependencies:
621
640
  return OrderedDict(self)
622
641
 
623
642
  @classmethod
624
- def from_mapping(cls, mapping: Mapping[str, Injectable]):
643
+ def from_mapping(cls, mapping: Mapping[str, Injectable]) -> Self:
625
644
  return cls(mapping=mapping)
626
645
 
627
646
  @classmethod
628
- def empty(cls):
647
+ def empty(cls) -> Self:
629
648
  return cls.from_mapping({})
630
649
 
631
650
  @classmethod
632
- def resolve(cls, signature: Signature, module: Module, owner: type = None):
651
+ def resolve(cls, signature: Signature, module: Module, owner: type = None) -> Self:
633
652
  dependencies = LazyMapping(cls.__resolver(signature, module, owner))
634
653
  return cls.from_mapping(dependencies)
635
654
 
@@ -705,7 +724,7 @@ class InjectedFunction(EventListener):
705
724
  arguments = self.bind(args, kwargs)
706
725
  return self.wrapped(*arguments.args, **arguments.kwargs)
707
726
 
708
- def __get__(self, instance: object = None, owner: type = None):
727
+ def __get__(self, instance: object = None, owner: type = None) -> Self | MethodType:
709
728
  if instance is None:
710
729
  return self
711
730
 
@@ -739,7 +758,7 @@ class InjectedFunction(EventListener):
739
758
  )
740
759
  return Arguments(bound.args, bound.kwargs)
741
760
 
742
- def set_owner(self, owner: type):
761
+ def set_owner(self, owner: type) -> Self:
743
762
  if self.__dependencies.are_resolved:
744
763
  raise TypeError(
745
764
  "Function owner must be assigned before dependencies are resolved."
@@ -752,7 +771,7 @@ class InjectedFunction(EventListener):
752
771
  return self
753
772
 
754
773
  @synchronized()
755
- def update(self, module: Module):
774
+ def update(self, module: Module) -> Self:
756
775
  self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
757
776
  return self
758
777
 
@@ -764,7 +783,7 @@ class InjectedFunction(EventListener):
764
783
  return decorator(wrapped) if wrapped else decorator
765
784
 
766
785
  @singledispatchmethod
767
- def on_event(self, event: Event, /) -> ContextManager | None: # type: ignore
786
+ def on_event(self, event: Event, /) -> None: # type: ignore
768
787
  return None
769
788
 
770
789
  @on_event.register
@@ -773,7 +792,7 @@ class InjectedFunction(EventListener):
773
792
  yield
774
793
  self.update(event.on_module)
775
794
 
776
- def __set_signature(self):
795
+ def __set_signature(self) -> Self:
777
796
  self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
778
797
  return self
779
798
 
@@ -1,7 +1,5 @@
1
1
  from typing import Any
2
2
 
3
- from injection.common.tools.type import format_type
4
-
5
3
  __all__ = (
6
4
  "InjectionError",
7
5
  "NoInjectable",
@@ -15,15 +13,15 @@ class InjectionError(Exception):
15
13
  pass
16
14
 
17
15
 
18
- class NoInjectable(KeyError, InjectionError):
16
+ class NoInjectable[T](KeyError, InjectionError):
19
17
  __slots__ = ("__class",)
20
18
 
21
- def __init__(self, cls: type | Any):
22
- super().__init__(f"No injectable for `{format_type(cls)}`.")
19
+ def __init__(self, cls: type[T] | Any):
20
+ super().__init__(f"No injectable for `{cls}`.")
23
21
  self.__class = cls
24
22
 
25
23
  @property
26
- def cls(self) -> type:
24
+ def cls(self) -> type[T]:
27
25
  return self.__class
28
26
 
29
27
 
@@ -1,4 +1,4 @@
1
- from typing import Any, TypeVar
1
+ from typing import Any
2
2
 
3
3
  from rodi import ContainerProtocol
4
4
 
@@ -6,8 +6,6 @@ from injection import Module, default_module
6
6
 
7
7
  __all__ = ("InjectionServices",)
8
8
 
9
- _T = TypeVar("_T")
10
-
11
9
 
12
10
  class InjectionServices(ContainerProtocol):
13
11
  """
@@ -26,5 +24,5 @@ class InjectionServices(ContainerProtocol):
26
24
  self.__module.injectable(obj_type)
27
25
  return self
28
26
 
29
- def resolve(self, obj_type: type[_T] | Any, *args, **kwargs) -> _T:
27
+ def resolve[T](self, obj_type: type[T] | Any, *args, **kwargs) -> T:
30
28
  return self.__module.resolve(obj_type)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-injection"
3
- version = "0.8.5"
3
+ version = "0.9.0"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  authors = ["remimd"]
6
6
  keywords = ["dependencies", "inject", "injection"]
@@ -10,7 +10,7 @@ readme = "documentation/basic-usage.md"
10
10
  repository = "https://github.com/100nm/python-injection"
11
11
 
12
12
  [tool.poetry.dependencies]
13
- python = ">=3.10, <4"
13
+ python = ">=3.12, <4"
14
14
 
15
15
  [tool.poetry.group.dev.dependencies]
16
16
  argon2-cffi = "*"
@@ -32,6 +32,7 @@ exclude_lines = [
32
32
 
33
33
  [tool.mypy]
34
34
  check_untyped_defs = true
35
+ enable_incomplete_feature = ["NewGenericSyntax"]
35
36
  exclude = [
36
37
  "documentation/example/",
37
38
  "tests/",
@@ -1,48 +0,0 @@
1
- from collections.abc import Iterable, Iterator
2
- from inspect import get_annotations, isfunction
3
- from types import NoneType, UnionType
4
- from typing import Annotated, Any, Union, get_args, get_origin
5
-
6
- __all__ = ("find_types", "format_type", "get_origins")
7
-
8
-
9
- def format_type(cls: type | Any) -> str:
10
- try:
11
- return f"{cls.__module__}.{cls.__qualname__}"
12
- except AttributeError:
13
- return str(cls)
14
-
15
-
16
- def get_origins(*types: type | Any) -> Iterator[type | Any]:
17
- for tp in types:
18
- origin = get_origin(tp) or tp
19
-
20
- if origin in (None, NoneType):
21
- continue
22
-
23
- elif origin in (Union, UnionType):
24
- args = get_args(tp)
25
-
26
- elif origin is Annotated is not tp:
27
- args = get_args(tp)[:1]
28
-
29
- else:
30
- yield origin
31
- continue
32
-
33
- yield from get_origins(*args)
34
-
35
-
36
- def find_types(*args: Any) -> Iterator[type | UnionType]:
37
- for argument in args:
38
- if isinstance(argument, Iterable) and not isinstance(argument, type | str):
39
- arguments = argument
40
-
41
- elif isfunction(argument):
42
- arguments = (get_annotations(argument, eval_str=True).get("return"),)
43
-
44
- else:
45
- yield argument
46
- continue
47
-
48
- yield from find_types(*arguments)