python-injection 0.8.2__tar.gz → 0.8.4__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 (21) hide show
  1. {python_injection-0.8.2 → python_injection-0.8.4}/PKG-INFO +2 -2
  2. {python_injection-0.8.2 → python_injection-0.8.4}/documentation/basic-usage.md +1 -1
  3. {python_injection-0.8.2 → python_injection-0.8.4}/injection/__init__.py +4 -0
  4. {python_injection-0.8.2 → python_injection-0.8.4}/injection/__init__.pyi +44 -17
  5. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/event.py +2 -2
  6. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/invertible.py +1 -1
  7. python_injection-0.8.4/injection/common/tools/threading.py +12 -0
  8. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/tools/type.py +1 -1
  9. {python_injection-0.8.2 → python_injection-0.8.4}/injection/core/module.py +131 -97
  10. {python_injection-0.8.2 → python_injection-0.8.4}/injection/exceptions.py +3 -1
  11. python_injection-0.8.4/injection/integrations/blacksheep.py +30 -0
  12. {python_injection-0.8.2 → python_injection-0.8.4}/pyproject.toml +10 -1
  13. python_injection-0.8.2/injection/common/tools/threading.py +0 -13
  14. python_injection-0.8.2/injection/integrations/blacksheep.py +0 -28
  15. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/__init__.py +0 -0
  16. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/lazy.py +0 -0
  17. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/queue.py +0 -0
  18. {python_injection-0.8.2 → python_injection-0.8.4}/injection/common/tools/__init__.py +0 -0
  19. {python_injection-0.8.2 → python_injection-0.8.4}/injection/core/__init__.py +0 -0
  20. {python_injection-0.8.2 → python_injection-0.8.4}/injection/integrations/__init__.py +0 -0
  21. {python_injection-0.8.2 → python_injection-0.8.4}/injection/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -136,7 +136,7 @@ class ConcreteServiceOverload(ConcreteService):
136
136
  ...
137
137
  ```
138
138
 
139
- If a class is registered in a package and you want to override it, there is the `mode` parameter:
139
+ If a class is registered in a package, and you want to override it, there is the `mode` parameter:
140
140
 
141
141
  ```python
142
142
  @injectable
@@ -119,7 +119,7 @@ class ConcreteServiceOverload(ConcreteService):
119
119
  ...
120
120
  ```
121
121
 
122
- If a class is registered in a package and you want to override it, there is the `mode` parameter:
122
+ If a class is registered in a package, and you want to override it, there is the `mode` parameter:
123
123
 
124
124
  ```python
125
125
  @injectable
@@ -1,8 +1,12 @@
1
1
  from .core import Injectable, Module
2
+ from .core import Mode as InjectableMode
3
+ from .core import Priority as ModulePriority
2
4
 
3
5
  __all__ = (
4
6
  "Injectable",
7
+ "InjectableMode",
5
8
  "Module",
9
+ "ModulePriority",
6
10
  "default_module",
7
11
  "get_instance",
8
12
  "get_lazy_instance",
@@ -1,6 +1,7 @@
1
1
  from abc import abstractmethod
2
2
  from collections.abc import Callable, Iterable
3
3
  from contextlib import ContextDecorator
4
+ from enum import Enum
4
5
  from types import UnionType
5
6
  from typing import (
6
7
  Any,
@@ -15,7 +16,8 @@ from typing import (
15
16
 
16
17
  from .common.invertible import Invertible
17
18
 
18
- _T = TypeVar("_T")
19
+ _In_T = TypeVar("_In_T", covariant=False)
20
+ _Co_T = TypeVar("_Co_T", covariant=True)
19
21
 
20
22
  default_module: Final[Module] = ...
21
23
 
@@ -54,7 +56,7 @@ class Module:
54
56
  cls: type[Injectable] = ...,
55
57
  inject: bool = ...,
56
58
  on: type | Iterable[type] | UnionType = ...,
57
- mode: Literal["fallback", "normal", "override"] = ...,
59
+ mode: InjectableMode | Literal["fallback", "normal", "override"] = ...,
58
60
  ):
59
61
  """
60
62
  Decorator applicable to a class or function. It is used to indicate how the
@@ -69,7 +71,7 @@ class Module:
69
71
  *,
70
72
  inject: bool = ...,
71
73
  on: type | Iterable[type] | UnionType = ...,
72
- mode: Literal["fallback", "normal", "override"] = ...,
74
+ mode: InjectableMode | Literal["fallback", "normal", "override"] = ...,
73
75
  ):
74
76
  """
75
77
  Decorator applicable to a class or function. It is used to indicate how the
@@ -86,30 +88,35 @@ class Module:
86
88
 
87
89
  def set_constant(
88
90
  self,
89
- instance: _T,
91
+ instance: _In_T,
90
92
  on: type | Iterable[type] | UnionType = ...,
91
93
  *,
92
- mode: Literal["fallback", "normal", "override"] = ...,
93
- ) -> _T:
94
+ mode: InjectableMode | Literal["fallback", "normal", "override"] = ...,
95
+ ) -> _In_T:
94
96
  """
95
97
  Function for registering a specific instance to be injected. This is useful for
96
98
  registering global variables. The difference with the singleton decorator is
97
99
  that no dependencies are resolved, so the module doesn't need to be locked.
98
100
  """
99
101
 
100
- def get_instance(self, cls: type[_T], *, none: bool = ...) -> _T | None:
102
+ def resolve(self, cls: type[_In_T]) -> _In_T:
101
103
  """
102
104
  Function used to retrieve an instance associated with the type passed in
103
- parameter or return `None` but if `none` parameter is `False` an exception
104
- will be raised.
105
+ parameter or an exception will be raised.
106
+ """
107
+
108
+ def get_instance(self, cls: type[_In_T]) -> _In_T | None:
109
+ """
110
+ Function used to retrieve an instance associated with the type passed in
111
+ parameter or return `None`.
105
112
  """
106
113
 
107
114
  def get_lazy_instance(
108
115
  self,
109
- cls: type[_T],
116
+ cls: type[_In_T],
110
117
  *,
111
118
  cache: bool = ...,
112
- ) -> Invertible[_T | None]:
119
+ ) -> Invertible[_In_T | None]:
113
120
  """
114
121
  Function used to retrieve an instance associated with the type passed in
115
122
  parameter or `None`. Return a `Invertible` object. To access the instance
@@ -119,7 +126,12 @@ class Module:
119
126
  Example: instance = ~lazy_instance
120
127
  """
121
128
 
122
- def use(self, module: Module, *, priority: Literal["low", "high"] = ...):
129
+ def use(
130
+ self,
131
+ module: Module,
132
+ *,
133
+ priority: ModulePriority | Literal["low", "high"] = ...,
134
+ ):
123
135
  """
124
136
  Function for using another module. Using another module replaces the module's
125
137
  dependencies with those of the module used. If the dependency is not found, it
@@ -135,13 +147,17 @@ class Module:
135
147
  self,
136
148
  module: Module,
137
149
  *,
138
- priority: Literal["low", "high"] = ...,
150
+ priority: ModulePriority | Literal["low", "high"] = ...,
139
151
  ) -> ContextManager | ContextDecorator:
140
152
  """
141
153
  Context manager or decorator for temporary use of a module.
142
154
  """
143
155
 
144
- def change_priority(self, module: Module, priority: Literal["low", "high"]):
156
+ def change_priority(
157
+ self,
158
+ module: Module,
159
+ priority: ModulePriority | Literal["low", "high"],
160
+ ):
145
161
  """
146
162
  Function for changing the priority of a module in use.
147
163
  There are two priority values:
@@ -155,11 +171,22 @@ class Module:
155
171
  Function to unlock the module by deleting cached instances of singletons.
156
172
  """
157
173
 
174
+ @final
175
+ class ModulePriority(str, Enum):
176
+ LOW = ...
177
+ HIGH = ...
178
+
158
179
  @runtime_checkable
159
- class Injectable(Protocol[_T]):
160
- def __init__(self, factory: Callable[[], _T] = ..., /): ...
180
+ class Injectable(Protocol[_Co_T]):
181
+ def __init__(self, factory: Callable[[], _Co_T] = ..., /): ...
161
182
  @property
162
183
  def is_locked(self) -> bool: ...
163
184
  def unlock(self): ...
164
185
  @abstractmethod
165
- def get_instance(self) -> _T: ...
186
+ def get_instance(self) -> _Co_T: ...
187
+
188
+ @final
189
+ class InjectableMode(str, Enum):
190
+ FALLBACK = ...
191
+ NORMAL = ...
192
+ OVERRIDE = ...
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from contextlib import ContextDecorator, ExitStack, contextmanager, suppress
2
+ from contextlib import ExitStack, contextmanager, suppress
3
3
  from dataclasses import dataclass, field
4
4
  from typing import ContextManager
5
5
  from weakref import WeakSet
@@ -24,7 +24,7 @@ class EventChannel:
24
24
  __listeners: WeakSet[EventListener] = field(default_factory=WeakSet, init=False)
25
25
 
26
26
  @contextmanager
27
- def dispatch(self, event: Event) -> ContextManager | ContextDecorator:
27
+ def dispatch(self, event: Event):
28
28
  with ExitStack() as stack:
29
29
  for listener in tuple(self.__listeners):
30
30
  context_manager = listener.on_event(event)
@@ -5,7 +5,7 @@ from typing import Protocol, TypeVar, runtime_checkable
5
5
 
6
6
  __all__ = ("Invertible", "SimpleInvertible")
7
7
 
8
- _T = TypeVar("_T")
8
+ _T = TypeVar("_T", covariant=True)
9
9
 
10
10
 
11
11
  @runtime_checkable
@@ -0,0 +1,12 @@
1
+ from contextlib import contextmanager
2
+ from threading import RLock
3
+
4
+ __all__ = ("synchronized",)
5
+
6
+ __lock = RLock()
7
+
8
+
9
+ @contextmanager
10
+ def synchronized():
11
+ with __lock:
12
+ yield
@@ -24,7 +24,7 @@ def get_origins(*types: type | Any) -> Iterator[type | Any]:
24
24
  args = get_args(tp)
25
25
 
26
26
  elif origin is Annotated is not tp:
27
- args = (tp.__origin__,)
27
+ args = get_args(tp)[:1]
28
28
 
29
29
  else:
30
30
  yield origin
@@ -12,8 +12,9 @@ from collections.abc import (
12
12
  Mapping,
13
13
  MutableMapping,
14
14
  )
15
- from contextlib import ContextDecorator, contextmanager, suppress
15
+ from contextlib import contextmanager, suppress
16
16
  from dataclasses import dataclass, field
17
+ from enum import Enum
17
18
  from functools import partialmethod, singledispatchmethod, update_wrapper
18
19
  from inspect import Signature, isclass
19
20
  from types import MethodType, UnionType
@@ -26,8 +27,6 @@ from typing import (
26
27
  NoReturn,
27
28
  Protocol,
28
29
  TypeVar,
29
- cast,
30
- get_args,
31
30
  runtime_checkable,
32
31
  )
33
32
 
@@ -45,21 +44,12 @@ from injection.exceptions import (
45
44
  NoInjectable,
46
45
  )
47
46
 
48
- __all__ = ("Injectable", "Module")
47
+ __all__ = ("Injectable", "Mode", "Module", "Priority")
49
48
 
50
49
  _logger = logging.getLogger(__name__)
51
50
 
52
- _T = TypeVar("_T")
53
- _Types = Iterable[type] | UnionType
54
-
55
-
56
- """
57
- Literal types
58
- """
59
-
60
-
61
- _Mode = Literal["fallback", "normal", "override"]
62
- _Priority = Literal["low", "high"]
51
+ _In_T = TypeVar("_In_T", covariant=False)
52
+ _Co_T = TypeVar("_Co_T", covariant=True)
63
53
 
64
54
 
65
55
  """
@@ -75,7 +65,7 @@ class ContainerEvent(Event, ABC):
75
65
  @dataclass(frozen=True, slots=True)
76
66
  class ContainerDependenciesUpdated(ContainerEvent):
77
67
  classes: Collection[type]
78
- mode: _Mode
68
+ mode: Mode
79
69
 
80
70
  def __str__(self) -> str:
81
71
  length = len(self.classes)
@@ -113,6 +103,7 @@ class ModuleEventProxy(ModuleEvent):
113
103
  @dataclass(frozen=True, slots=True)
114
104
  class ModuleAdded(ModuleEvent):
115
105
  module_added: Module
106
+ priority: Priority
116
107
 
117
108
  def __str__(self) -> str:
118
109
  return f"`{self.on_module}` now uses `{self.module_added}`."
@@ -129,7 +120,7 @@ class ModuleRemoved(ModuleEvent):
129
120
  @dataclass(frozen=True, slots=True)
130
121
  class ModulePriorityUpdated(ModuleEvent):
131
122
  module_updated: Module
132
- priority: _Priority
123
+ priority: Priority
133
124
 
134
125
  def __str__(self) -> str:
135
126
  return (
@@ -144,10 +135,10 @@ Injectables
144
135
 
145
136
 
146
137
  @runtime_checkable
147
- class Injectable(Protocol[_T]):
138
+ class Injectable(Protocol[_Co_T]):
148
139
  __slots__ = ()
149
140
 
150
- def __init__(self, __factory: Callable[[], _T] = None, /):
141
+ def __init__(self, __factory: Callable[[], _Co_T] = None, /):
151
142
  pass
152
143
 
153
144
  @property
@@ -155,26 +146,26 @@ class Injectable(Protocol[_T]):
155
146
  return False
156
147
 
157
148
  def unlock(self):
158
- pass
149
+ return
159
150
 
160
151
  @abstractmethod
161
- def get_instance(self) -> _T:
152
+ def get_instance(self) -> _Co_T:
162
153
  raise NotImplementedError
163
154
 
164
155
 
165
156
  @dataclass(repr=False, frozen=True, slots=True)
166
- class BaseInjectable(Injectable[_T], ABC):
167
- factory: Callable[[], _T]
157
+ class BaseInjectable(Injectable[_In_T], ABC):
158
+ factory: Callable[[], _In_T]
168
159
 
169
160
 
170
- class NewInjectable(BaseInjectable[_T]):
161
+ class NewInjectable(BaseInjectable[_In_T]):
171
162
  __slots__ = ()
172
163
 
173
- def get_instance(self) -> _T:
164
+ def get_instance(self) -> _In_T:
174
165
  return self.factory()
175
166
 
176
167
 
177
- class SingletonInjectable(BaseInjectable[_T]):
168
+ class SingletonInjectable(BaseInjectable[_In_T]):
178
169
  __slots__ = ("__dict__",)
179
170
 
180
171
  __INSTANCE_KEY: ClassVar[str] = "$instance"
@@ -190,7 +181,7 @@ class SingletonInjectable(BaseInjectable[_T]):
190
181
  def unlock(self):
191
182
  self.cache.clear()
192
183
 
193
- def get_instance(self) -> _T:
184
+ def get_instance(self) -> _In_T:
194
185
  with suppress(KeyError):
195
186
  return self.cache[self.__INSTANCE_KEY]
196
187
 
@@ -202,8 +193,8 @@ class SingletonInjectable(BaseInjectable[_T]):
202
193
 
203
194
 
204
195
  @dataclass(repr=False, frozen=True, slots=True)
205
- class ShouldBeInjectable(Injectable[_T]):
206
- cls: type[_T]
196
+ class ShouldBeInjectable(Injectable[_In_T]):
197
+ cls: type[_In_T]
207
198
 
208
199
  def get_instance(self) -> NoReturn:
209
200
  raise InjectionError(f"`{format_type(self.cls)}` should be an injectable.")
@@ -219,7 +210,7 @@ class Broker(Protocol):
219
210
  __slots__ = ()
220
211
 
221
212
  @abstractmethod
222
- def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
213
+ def __getitem__(self, cls: type[_In_T] | UnionType, /) -> Injectable[_In_T]:
223
214
  raise NotImplementedError
224
215
 
225
216
  @abstractmethod
@@ -241,20 +232,37 @@ Container
241
232
  """
242
233
 
243
234
 
235
+ class Mode(str, Enum):
236
+ FALLBACK = "fallback"
237
+ NORMAL = "normal"
238
+ OVERRIDE = "override"
239
+
240
+ @property
241
+ def rank(self) -> int:
242
+ return tuple(type(self)).index(self)
243
+
244
+ @classmethod
245
+ def get_default(cls):
246
+ return cls.NORMAL
247
+
248
+
249
+ ModeStr = Literal["fallback", "normal", "override"]
250
+
251
+
244
252
  class Record(NamedTuple):
245
253
  injectable: Injectable
246
- mode: _Mode
254
+ mode: Mode
247
255
 
248
256
 
249
257
  @dataclass(repr=False, frozen=True, slots=True)
250
258
  class Container(Broker):
251
- __data: dict[type, Record] = field(default_factory=dict, init=False)
259
+ __records: dict[type, Record] = field(default_factory=dict, init=False)
252
260
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
253
261
 
254
- def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
262
+ def __getitem__(self, cls: type[_In_T] | UnionType, /) -> Injectable[_In_T]:
255
263
  for cls in get_origins(cls):
256
264
  try:
257
- injectable, _ = self.__data[cls]
265
+ injectable, _ = self.__records[cls]
258
266
  except KeyError:
259
267
  continue
260
268
 
@@ -263,7 +271,7 @@ class Container(Broker):
263
271
  raise NoInjectable(cls)
264
272
 
265
273
  def __contains__(self, cls: type | UnionType, /) -> bool:
266
- return any(cls in self.__data for cls in get_origins(cls))
274
+ return any(cls in self.__records for cls in get_origins(cls))
267
275
 
268
276
  @property
269
277
  def is_locked(self) -> bool:
@@ -271,10 +279,16 @@ class Container(Broker):
271
279
 
272
280
  @property
273
281
  def __injectables(self) -> frozenset[Injectable]:
274
- return frozenset(injectable for injectable, _ in self.__data.values())
282
+ return frozenset(injectable for injectable, _ in self.__records.values())
275
283
 
276
284
  @synchronized()
277
- def update(self, classes: Iterable[type], injectable: Injectable, mode: _Mode):
285
+ def update(
286
+ self,
287
+ classes: Iterable[type | UnionType],
288
+ injectable: Injectable,
289
+ mode: Mode | ModeStr,
290
+ ):
291
+ mode = Mode(mode)
278
292
  records = {
279
293
  cls: Record(injectable, mode)
280
294
  for cls in self.__filter_classes(classes, mode)
@@ -284,7 +298,7 @@ class Container(Broker):
284
298
  event = ContainerDependenciesUpdated(self, records.keys(), mode)
285
299
 
286
300
  with self.notify(event):
287
- self.__data.update(records)
301
+ self.__records.update(records)
288
302
 
289
303
  return self
290
304
 
@@ -299,16 +313,19 @@ class Container(Broker):
299
313
  self.__channel.add_listener(listener)
300
314
  return self
301
315
 
302
- def notify(self, event: Event) -> ContextManager | ContextDecorator:
316
+ def notify(self, event: Event):
303
317
  return self.__channel.dispatch(event)
304
318
 
305
- def __filter_classes(self, classes: Iterable[type], mode: _Mode) -> Iterator[type]:
306
- modes = get_args(_Mode)
307
- rank = modes.index(mode)
319
+ def __filter_classes(
320
+ self,
321
+ classes: Iterable[type | UnionType],
322
+ mode: Mode,
323
+ ) -> Iterator[type]:
324
+ rank = mode.rank
308
325
 
309
326
  for cls in frozenset(get_origins(*classes)):
310
327
  try:
311
- _, current_mode = self.__data[cls]
328
+ _, current_mode = self.__records[cls]
312
329
 
313
330
  except KeyError:
314
331
  pass
@@ -319,7 +336,7 @@ class Container(Broker):
319
336
  f"An injectable already exists for the class `{format_type(cls)}`."
320
337
  )
321
338
 
322
- elif rank < modes.index(current_mode):
339
+ elif rank < current_mode.rank:
323
340
  continue
324
341
 
325
342
  yield cls
@@ -330,9 +347,21 @@ Module
330
347
  """
331
348
 
332
349
 
350
+ class Priority(str, Enum):
351
+ LOW = "low"
352
+ HIGH = "high"
353
+
354
+ @classmethod
355
+ def get_default(cls):
356
+ return cls.LOW
357
+
358
+
359
+ PriorityStr = Literal["low", "high"]
360
+
361
+
333
362
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
334
363
  class Module(EventListener, Broker):
335
- name: str = field(default=None)
364
+ name: str | None = field(default=None)
336
365
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
337
366
  __container: Container = field(default_factory=Container, init=False)
338
367
  __modules: OrderedDict[Module, None] = field(
@@ -343,7 +372,7 @@ class Module(EventListener, Broker):
343
372
  def __post_init__(self):
344
373
  self.__container.add_listener(self)
345
374
 
346
- def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
375
+ def __getitem__(self, cls: type[_In_T] | UnionType, /) -> Injectable[_In_T]:
347
376
  for broker in self.__brokers:
348
377
  with suppress(KeyError):
349
378
  return broker[cls]
@@ -375,8 +404,8 @@ class Module(EventListener, Broker):
375
404
  *,
376
405
  cls: type[Injectable] = NewInjectable,
377
406
  inject: bool = True,
378
- on: type | _Types = None,
379
- mode: _Mode = "normal",
407
+ on: type | Iterable[type] | UnionType = (),
408
+ mode: Mode | ModeStr = Mode.get_default(),
380
409
  ):
381
410
  def decorator(wp):
382
411
  factory = self.inject(wp, return_factory=True) if inject else wp
@@ -394,7 +423,7 @@ class Module(EventListener, Broker):
394
423
  self.update(
395
424
  (wp,),
396
425
  ShouldBeInjectable(wp),
397
- mode="fallback",
426
+ mode=Mode.FALLBACK,
398
427
  )
399
428
  return wp
400
429
 
@@ -402,16 +431,16 @@ class Module(EventListener, Broker):
402
431
 
403
432
  def set_constant(
404
433
  self,
405
- instance: _T,
406
- on: type | _Types = None,
434
+ instance: _In_T,
435
+ on: type | Iterable[type] | UnionType = (),
407
436
  *,
408
- mode: _Mode = "normal",
409
- ) -> _T:
437
+ mode: Mode | ModeStr = Mode.get_default(),
438
+ ) -> _In_T:
410
439
  cls = type(instance)
411
440
  self.injectable(
412
441
  lambda: instance,
413
442
  inject=False,
414
- on=(cls, on),
443
+ on=(cls, on), # type: ignore
415
444
  mode=mode,
416
445
  )
417
446
  return instance
@@ -439,24 +468,22 @@ class Module(EventListener, Broker):
439
468
 
440
469
  return decorator(wrapped) if wrapped else decorator
441
470
 
442
- def get_instance(self, cls: type[_T], *, none: bool = True) -> _T | None:
443
- try:
444
- injectable = self[cls]
445
- except KeyError as exc:
446
- if none:
447
- return None
448
-
449
- raise exc
471
+ def resolve(self, cls: type[_In_T]) -> _In_T:
472
+ injectable = self[cls]
473
+ return injectable.get_instance()
450
474
 
451
- instance = injectable.get_instance()
452
- return cast(cls, instance)
475
+ def get_instance(self, cls: type[_In_T]) -> _In_T | None:
476
+ try:
477
+ return self.resolve(cls)
478
+ except KeyError:
479
+ return None
453
480
 
454
481
  def get_lazy_instance(
455
482
  self,
456
- cls: type[_T],
483
+ cls: type[_In_T],
457
484
  *,
458
485
  cache: bool = False,
459
- ) -> Invertible[_T | None]:
486
+ ) -> Invertible[_In_T | None]:
460
487
  if cache:
461
488
  return Lazy(lambda: self.get_instance(cls))
462
489
 
@@ -466,21 +493,27 @@ class Module(EventListener, Broker):
466
493
 
467
494
  def update(
468
495
  self,
469
- classes: Iterable[type],
496
+ classes: Iterable[type | UnionType],
470
497
  injectable: Injectable,
471
- mode: _Mode = "normal",
498
+ mode: Mode | ModeStr = Mode.get_default(),
472
499
  ):
473
500
  self.__container.update(classes, injectable, mode)
474
501
  return self
475
502
 
476
- def use(self, module: Module, *, priority: _Priority = "low"):
503
+ def use(
504
+ self,
505
+ module: Module,
506
+ *,
507
+ priority: Priority | PriorityStr = Priority.get_default(),
508
+ ):
477
509
  if module is self:
478
510
  raise ModuleError("Module can't be used by itself.")
479
511
 
480
512
  if module in self.__modules:
481
513
  raise ModuleError(f"`{self}` already uses `{module}`.")
482
514
 
483
- event = ModuleAdded(self, module)
515
+ priority = Priority(priority)
516
+ event = ModuleAdded(self, module, priority)
484
517
 
485
518
  with self.notify(event):
486
519
  self.__modules[module] = None
@@ -504,13 +537,14 @@ class Module(EventListener, Broker):
504
537
  self,
505
538
  module: Module,
506
539
  *,
507
- priority: _Priority = "low",
508
- ) -> ContextManager | ContextDecorator:
540
+ priority: Priority | PriorityStr = Priority.get_default(),
541
+ ):
509
542
  self.use(module, priority=priority)
510
543
  yield
511
544
  self.stop_using(module)
512
545
 
513
- def change_priority(self, module: Module, priority: _Priority):
546
+ def change_priority(self, module: Module, priority: Priority | PriorityStr):
547
+ priority = Priority(priority)
514
548
  event = ModulePriorityUpdated(self, module, priority)
515
549
 
516
550
  with self.notify(event):
@@ -538,7 +572,7 @@ class Module(EventListener, Broker):
538
572
  return self.notify(self_event)
539
573
 
540
574
  @contextmanager
541
- def notify(self, event: Event) -> ContextManager | ContextDecorator:
575
+ def notify(self, event: Event):
542
576
  self.__check_locking()
543
577
 
544
578
  with self.__channel.dispatch(event):
@@ -549,8 +583,8 @@ class Module(EventListener, Broker):
549
583
  if self.is_locked:
550
584
  raise ModuleLockError(f"`{self}` is locked.")
551
585
 
552
- def __move_module(self, module: Module, priority: _Priority):
553
- last = priority != "high"
586
+ def __move_module(self, module: Module, priority: Priority):
587
+ last = priority != Priority.HIGH
554
588
 
555
589
  try:
556
590
  self.__modules.move_to_end(module, last=last)
@@ -609,7 +643,7 @@ class Dependencies:
609
643
  ) -> Iterator[tuple[str, Injectable]]:
610
644
  for name, annotation in cls.__get_annotations(signature, owner):
611
645
  try:
612
- injectable = module[annotation]
646
+ injectable: Injectable = module[annotation]
613
647
  except KeyError:
614
648
  continue
615
649
 
@@ -646,32 +680,25 @@ class InjectedFunction(EventListener):
646
680
  )
647
681
 
648
682
  def __init__(self, wrapped: Callable[..., Any], /):
649
- try:
650
- variables = vars(wrapped)
651
- except TypeError:
652
- pass
653
- else:
654
- self.__update_vars(variables)
655
- del variables
656
-
683
+ self.__update_vars_from(wrapped)
657
684
  update_wrapper(self, wrapped, updated=())
658
685
  self.__dependencies = Dependencies.empty()
659
686
  self.__owner = None
660
687
  self.__setup_queue = LimitedQueue[Callable[[], Any]]()
661
688
  self.on_setup(self.__set_signature)
662
689
 
663
- def __repr__(self) -> str:
690
+ def __repr__(self) -> str: # pragma: no cover
664
691
  return repr(self.wrapped)
665
692
 
666
- def __str__(self) -> str:
693
+ def __str__(self) -> str: # pragma: no cover
667
694
  return str(self.wrapped)
668
695
 
669
696
  def __call__(self, /, *args, **kwargs) -> Any:
670
697
  for function in self.__setup_queue:
671
698
  function()
672
699
 
673
- args, kwargs = self.bind(args, kwargs)
674
- return self.wrapped(*args, **kwargs)
700
+ arguments = self.bind(args, kwargs)
701
+ return self.wrapped(*arguments.args, **arguments.kwargs)
675
702
 
676
703
  def __get__(self, instance: object = None, owner: type = None):
677
704
  if instance is None:
@@ -688,7 +715,7 @@ class InjectedFunction(EventListener):
688
715
 
689
716
  @property
690
717
  def wrapped(self) -> Callable[..., Any]:
691
- return self.__wrapped__
718
+ return self.__wrapped__ # type: ignore
692
719
 
693
720
  def bind(
694
721
  self,
@@ -716,7 +743,7 @@ class InjectedFunction(EventListener):
716
743
  if self.__owner:
717
744
  raise TypeError("Function owner is already defined.")
718
745
 
719
- self.__owner = owner
746
+ self.__owner = owner # type: ignore
720
747
  return self
721
748
 
722
749
  @synchronized()
@@ -732,12 +759,12 @@ class InjectedFunction(EventListener):
732
759
  return decorator(wrapped) if wrapped else decorator
733
760
 
734
761
  @singledispatchmethod
735
- def on_event(self, event: Event, /):
736
- pass
762
+ def on_event(self, event: Event, /) -> ContextManager | None: # type: ignore
763
+ return None
737
764
 
738
765
  @on_event.register
739
766
  @contextmanager
740
- def _(self, event: ModuleEvent, /) -> ContextManager:
767
+ def _(self, event: ModuleEvent, /):
741
768
  yield
742
769
  self.update(event.on_module)
743
770
 
@@ -745,14 +772,21 @@ class InjectedFunction(EventListener):
745
772
  self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
746
773
  return self
747
774
 
748
- def __update_vars(self, variables: Mapping[str, Any], /):
775
+ def __update_vars_from(self, obj: Any):
776
+ try:
777
+ variables = vars(obj)
778
+ except TypeError:
779
+ pass
780
+ else:
781
+ self.__update_vars(variables)
782
+
783
+ def __update_vars(self, variables: Mapping[str, Any]):
749
784
  def is_dunder(var: str) -> bool:
750
785
  return var.startswith("__") and var.endswith("__")
751
786
 
752
787
  restricted_vars = frozenset(var for var in dir(self) if not is_dunder(var))
753
- variables = (
788
+ vars(self).update(
754
789
  (var, value)
755
790
  for var, value in variables.items()
756
791
  if var not in restricted_vars
757
792
  )
758
- vars(self).update(variables)
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  from injection.common.tools.type import format_type
2
4
 
3
5
  __all__ = (
@@ -16,7 +18,7 @@ class InjectionError(Exception):
16
18
  class NoInjectable(KeyError, InjectionError):
17
19
  __slots__ = ("__class",)
18
20
 
19
- def __init__(self, cls: type):
21
+ def __init__(self, cls: type | Any):
20
22
  super().__init__(f"No injectable for `{format_type(cls)}`.")
21
23
  self.__class = cls
22
24
 
@@ -0,0 +1,30 @@
1
+ from typing import Any, TypeVar
2
+
3
+ from rodi import ContainerProtocol
4
+
5
+ from injection import Module, default_module
6
+
7
+ __all__ = ("InjectionServices",)
8
+
9
+ _T = TypeVar("_T")
10
+
11
+
12
+ class InjectionServices(ContainerProtocol):
13
+ """
14
+ BlackSheep dependency injection container implemented with `python-injection`.
15
+ """
16
+
17
+ __slots__ = ("__module",)
18
+
19
+ def __init__(self, module: Module = default_module):
20
+ self.__module = module
21
+
22
+ def __contains__(self, item: Any) -> bool:
23
+ return item in self.__module
24
+
25
+ def register(self, obj_type: type | Any, *args, **kwargs):
26
+ self.__module.injectable(obj_type)
27
+ return self
28
+
29
+ def resolve(self, obj_type: type[_T] | Any, *args, **kwargs) -> _T:
30
+ return self.__module.resolve(obj_type)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-injection"
3
- version = "0.8.2"
3
+ version = "0.8.4"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  authors = ["remimd"]
6
6
  keywords = ["dependencies", "inject", "injection"]
@@ -16,6 +16,7 @@ python = ">=3.10, <4"
16
16
  argon2-cffi = "*"
17
17
  blacksheep = "*"
18
18
  faker = "*"
19
+ mypy = "*"
19
20
  pydantic = "*"
20
21
  pytest = "*"
21
22
  pytest-asyncio = "*"
@@ -29,6 +30,14 @@ exclude_lines = [
29
30
  "raise NotImplementedError",
30
31
  ]
31
32
 
33
+ [tool.mypy]
34
+ check_untyped_defs = true
35
+ exclude = [
36
+ "documentation/example/",
37
+ "tests/",
38
+ ]
39
+ implicit_optional = true
40
+
32
41
  [tool.pytest.ini_options]
33
42
  python_files = "test_*.py"
34
43
  addopts = "-p no:warnings --tb=short"
@@ -1,13 +0,0 @@
1
- from contextlib import ContextDecorator, contextmanager
2
- from threading import RLock
3
- from typing import ContextManager
4
-
5
- __all__ = ("synchronized",)
6
-
7
- __lock = RLock()
8
-
9
-
10
- @contextmanager
11
- def synchronized() -> ContextManager | ContextDecorator:
12
- with __lock:
13
- yield
@@ -1,28 +0,0 @@
1
- from typing import Any, TypeVar
2
-
3
- from injection import Module, default_module
4
-
5
- __all__ = ("InjectionServices",)
6
-
7
- _T = TypeVar("_T")
8
-
9
-
10
- class InjectionServices:
11
- """
12
- BlackSheep dependency injection container implemented with `python-injection`.
13
- """
14
-
15
- __slots__ = ("__module",)
16
-
17
- def __init__(self, module: Module = default_module):
18
- self.__module = module
19
-
20
- def __contains__(self, cls: type | Any, /) -> bool:
21
- return cls in self.__module
22
-
23
- def register(self, cls: type | Any, *__args, **__kwargs):
24
- self.__module.injectable(cls)
25
- return self
26
-
27
- def resolve(self, cls: type[_T] | Any, *__args, **__kwargs) -> _T:
28
- return self.__module.get_instance(cls, none=False)