python-injection 0.8.1__tar.gz → 0.8.2__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.1 → python_injection-0.8.2}/PKG-INFO +28 -29
  2. {python_injection-0.8.1 → python_injection-0.8.2}/documentation/basic-usage.md +27 -28
  3. python_injection-0.8.1/injection/_pkg.py → python_injection-0.8.2/injection/__init__.py +1 -2
  4. python_injection-0.8.1/injection/_pkg.pyi → python_injection-0.8.2/injection/__init__.pyi +11 -14
  5. {python_injection-0.8.1 → python_injection-0.8.2}/injection/core/module.py +120 -109
  6. {python_injection-0.8.1 → python_injection-0.8.2}/pyproject.toml +4 -2
  7. python_injection-0.8.1/injection/__init__.py +0 -1
  8. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/__init__.py +0 -0
  9. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/event.py +0 -0
  10. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/invertible.py +0 -0
  11. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/lazy.py +0 -0
  12. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/queue.py +0 -0
  13. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/tools/__init__.py +0 -0
  14. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/tools/threading.py +0 -0
  15. {python_injection-0.8.1 → python_injection-0.8.2}/injection/common/tools/type.py +0 -0
  16. {python_injection-0.8.1 → python_injection-0.8.2}/injection/core/__init__.py +0 -0
  17. {python_injection-0.8.1 → python_injection-0.8.2}/injection/exceptions.py +0 -0
  18. {python_injection-0.8.1 → python_injection-0.8.2}/injection/integrations/__init__.py +0 -0
  19. {python_injection-0.8.1 → python_injection-0.8.2}/injection/integrations/blacksheep.py +0 -0
  20. {python_injection-0.8.1 → python_injection-0.8.2}/injection/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -27,7 +27,7 @@ If you wish to inject a singleton, use `singleton` decorator.
27
27
  from injection import singleton
28
28
 
29
29
  @singleton
30
- class Singleton:
30
+ class ServiceA:
31
31
  """ class implementation """
32
32
  ```
33
33
 
@@ -37,7 +37,7 @@ If you wish to inject a new instance each time, use `injectable` decorator.
37
37
  from injection import injectable
38
38
 
39
39
  @injectable
40
- class Injectable:
40
+ class ServiceB:
41
41
  """ class implementation """
42
42
  ```
43
43
 
@@ -47,7 +47,10 @@ function.
47
47
  ```python
48
48
  from injection import set_constant
49
49
 
50
- app = set_constant(Application())
50
+ class ServiceC:
51
+ """ class implementation """
52
+
53
+ service_c = set_constant(ServiceC())
51
54
  ```
52
55
 
53
56
  ## Inject an instance
@@ -59,7 +62,7 @@ _Don't forget to annotate type of parameter to inject._
59
62
  from injection import inject
60
63
 
61
64
  @inject
62
- def my_function(instance: Injectable):
65
+ def some_function(service_a: ServiceA):
63
66
  """ function implementation """
64
67
  ```
65
68
 
@@ -76,8 +79,8 @@ from injection import inject
76
79
 
77
80
  @inject
78
81
  @dataclass
79
- class DataClass:
80
- instance: Injectable = ...
82
+ class SomeDataClass:
83
+ service_a: ServiceA = ...
81
84
  ```
82
85
 
83
86
  ## Get an instance
@@ -87,7 +90,7 @@ _Example with `get_instance` function:_
87
90
  ```python
88
91
  from injection import get_instance
89
92
 
90
- instance = get_instance(Injectable)
93
+ service_a = get_instance(ServiceA)
91
94
  ```
92
95
 
93
96
  _Example with `get_lazy_instance` function:_
@@ -95,9 +98,9 @@ _Example with `get_lazy_instance` function:_
95
98
  ```python
96
99
  from injection import get_lazy_instance
97
100
 
98
- lazy_instance = get_lazy_instance(Injectable)
101
+ lazy_service_a = get_lazy_instance(ServiceA)
99
102
  # ...
100
- instance = ~lazy_instance
103
+ service_a = ~lazy_service_a
101
104
  ```
102
105
 
103
106
  ## Inheritance
@@ -111,43 +114,39 @@ classes.
111
114
  _Example with one class:_
112
115
 
113
116
  ```python
114
- from injection import singleton
115
-
116
- class A:
117
+ class AbstractService(ABC):
117
118
  ...
118
119
 
119
- @singleton(on=A)
120
- class B(A):
120
+ @injectable(on=AbstractService)
121
+ class ConcreteService(AbstractService):
121
122
  ...
122
123
  ```
123
124
 
124
125
  _Example with several classes:_
125
126
 
126
127
  ```python
127
- from injection import singleton
128
-
129
- class A:
128
+ class AbstractService(ABC):
130
129
  ...
131
130
 
132
- class B(A):
131
+ class ConcreteService(AbstractService):
133
132
  ...
134
133
 
135
- @singleton(on=(A, B))
136
- class C(B):
134
+ @injectable(on=(AbstractService, ConcreteService))
135
+ class ConcreteServiceOverload(ConcreteService):
137
136
  ...
138
137
  ```
139
138
 
140
- If a class is registered in a package and you want to override it, there is the `override` parameter:
139
+ If a class is registered in a package and you want to override it, there is the `mode` parameter:
141
140
 
142
141
  ```python
143
- @singleton
144
- class A:
142
+ @injectable
143
+ class InaccessibleService:
145
144
  ...
146
145
 
147
146
  # ...
148
147
 
149
- @singleton(on=A, override=True)
150
- class B(A):
148
+ @injectable(on=InaccessibleService, mode="override")
149
+ class ServiceOverload(InaccessibleService):
151
150
  ...
152
151
  ```
153
152
 
@@ -157,10 +156,10 @@ A recipe is a function that tells the injector how to construct the instance to
157
156
  the return type annotation when defining the recipe.
158
157
 
159
158
  ```python
160
- from injection import singleton
159
+ from injection import injectable
161
160
 
162
- @singleton
163
- def my_recipe() -> Singleton:
161
+ @injectable
162
+ def service_d_recipe() -> ServiceD:
164
163
  """ recipe implementation """
165
164
  ```
166
165
 
@@ -10,7 +10,7 @@ If you wish to inject a singleton, use `singleton` decorator.
10
10
  from injection import singleton
11
11
 
12
12
  @singleton
13
- class Singleton:
13
+ class ServiceA:
14
14
  """ class implementation """
15
15
  ```
16
16
 
@@ -20,7 +20,7 @@ If you wish to inject a new instance each time, use `injectable` decorator.
20
20
  from injection import injectable
21
21
 
22
22
  @injectable
23
- class Injectable:
23
+ class ServiceB:
24
24
  """ class implementation """
25
25
  ```
26
26
 
@@ -30,7 +30,10 @@ function.
30
30
  ```python
31
31
  from injection import set_constant
32
32
 
33
- app = set_constant(Application())
33
+ class ServiceC:
34
+ """ class implementation """
35
+
36
+ service_c = set_constant(ServiceC())
34
37
  ```
35
38
 
36
39
  ## Inject an instance
@@ -42,7 +45,7 @@ _Don't forget to annotate type of parameter to inject._
42
45
  from injection import inject
43
46
 
44
47
  @inject
45
- def my_function(instance: Injectable):
48
+ def some_function(service_a: ServiceA):
46
49
  """ function implementation """
47
50
  ```
48
51
 
@@ -59,8 +62,8 @@ from injection import inject
59
62
 
60
63
  @inject
61
64
  @dataclass
62
- class DataClass:
63
- instance: Injectable = ...
65
+ class SomeDataClass:
66
+ service_a: ServiceA = ...
64
67
  ```
65
68
 
66
69
  ## Get an instance
@@ -70,7 +73,7 @@ _Example with `get_instance` function:_
70
73
  ```python
71
74
  from injection import get_instance
72
75
 
73
- instance = get_instance(Injectable)
76
+ service_a = get_instance(ServiceA)
74
77
  ```
75
78
 
76
79
  _Example with `get_lazy_instance` function:_
@@ -78,9 +81,9 @@ _Example with `get_lazy_instance` function:_
78
81
  ```python
79
82
  from injection import get_lazy_instance
80
83
 
81
- lazy_instance = get_lazy_instance(Injectable)
84
+ lazy_service_a = get_lazy_instance(ServiceA)
82
85
  # ...
83
- instance = ~lazy_instance
86
+ service_a = ~lazy_service_a
84
87
  ```
85
88
 
86
89
  ## Inheritance
@@ -94,43 +97,39 @@ classes.
94
97
  _Example with one class:_
95
98
 
96
99
  ```python
97
- from injection import singleton
98
-
99
- class A:
100
+ class AbstractService(ABC):
100
101
  ...
101
102
 
102
- @singleton(on=A)
103
- class B(A):
103
+ @injectable(on=AbstractService)
104
+ class ConcreteService(AbstractService):
104
105
  ...
105
106
  ```
106
107
 
107
108
  _Example with several classes:_
108
109
 
109
110
  ```python
110
- from injection import singleton
111
-
112
- class A:
111
+ class AbstractService(ABC):
113
112
  ...
114
113
 
115
- class B(A):
114
+ class ConcreteService(AbstractService):
116
115
  ...
117
116
 
118
- @singleton(on=(A, B))
119
- class C(B):
117
+ @injectable(on=(AbstractService, ConcreteService))
118
+ class ConcreteServiceOverload(ConcreteService):
120
119
  ...
121
120
  ```
122
121
 
123
- If a class is registered in a package and you want to override it, there is the `override` parameter:
122
+ If a class is registered in a package and you want to override it, there is the `mode` parameter:
124
123
 
125
124
  ```python
126
- @singleton
127
- class A:
125
+ @injectable
126
+ class InaccessibleService:
128
127
  ...
129
128
 
130
129
  # ...
131
130
 
132
- @singleton(on=A, override=True)
133
- class B(A):
131
+ @injectable(on=InaccessibleService, mode="override")
132
+ class ServiceOverload(InaccessibleService):
134
133
  ...
135
134
  ```
136
135
 
@@ -140,9 +139,9 @@ A recipe is a function that tells the injector how to construct the instance to
140
139
  the return type annotation when defining the recipe.
141
140
 
142
141
  ```python
143
- from injection import singleton
142
+ from injection import injectable
144
143
 
145
- @singleton
146
- def my_recipe() -> Singleton:
144
+ @injectable
145
+ def service_d_recipe() -> ServiceD:
147
146
  """ recipe implementation """
148
147
  ```
@@ -1,9 +1,8 @@
1
- from .core import Injectable, Module, ModulePriority
1
+ from .core import Injectable, Module
2
2
 
3
3
  __all__ = (
4
4
  "Injectable",
5
5
  "Module",
6
- "ModulePriority",
7
6
  "default_module",
8
7
  "get_instance",
9
8
  "get_lazy_instance",
@@ -1,19 +1,19 @@
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
5
4
  from types import UnionType
6
5
  from typing import (
7
6
  Any,
8
7
  ContextManager,
9
8
  Final,
9
+ Literal,
10
10
  Protocol,
11
11
  TypeVar,
12
12
  final,
13
13
  runtime_checkable,
14
14
  )
15
15
 
16
- from injection.common.invertible import Invertible
16
+ from .common.invertible import Invertible
17
17
 
18
18
  _T = TypeVar("_T")
19
19
 
@@ -54,7 +54,7 @@ class Module:
54
54
  cls: type[Injectable] = ...,
55
55
  inject: bool = ...,
56
56
  on: type | Iterable[type] | UnionType = ...,
57
- override: bool = ...,
57
+ mode: Literal["fallback", "normal", "override"] = ...,
58
58
  ):
59
59
  """
60
60
  Decorator applicable to a class or function. It is used to indicate how the
@@ -69,7 +69,7 @@ class Module:
69
69
  *,
70
70
  inject: bool = ...,
71
71
  on: type | Iterable[type] | UnionType = ...,
72
- override: bool = ...,
72
+ mode: Literal["fallback", "normal", "override"] = ...,
73
73
  ):
74
74
  """
75
75
  Decorator applicable to a class or function. It is used to indicate how the
@@ -89,7 +89,7 @@ class Module:
89
89
  instance: _T,
90
90
  on: type | Iterable[type] | UnionType = ...,
91
91
  *,
92
- override: bool = ...,
92
+ mode: Literal["fallback", "normal", "override"] = ...,
93
93
  ) -> _T:
94
94
  """
95
95
  Function for registering a specific instance to be injected. This is useful for
@@ -97,7 +97,7 @@ class Module:
97
97
  that no dependencies are resolved, so the module doesn't need to be locked.
98
98
  """
99
99
 
100
- def get_instance(self, cls: type[_T], none: bool = ...) -> _T | None:
100
+ def get_instance(self, cls: type[_T], *, none: bool = ...) -> _T | None:
101
101
  """
102
102
  Function used to retrieve an instance associated with the type passed in
103
103
  parameter or return `None` but if `none` parameter is `False` an exception
@@ -107,6 +107,7 @@ class Module:
107
107
  def get_lazy_instance(
108
108
  self,
109
109
  cls: type[_T],
110
+ *,
110
111
  cache: bool = ...,
111
112
  ) -> Invertible[_T | None]:
112
113
  """
@@ -118,7 +119,7 @@ class Module:
118
119
  Example: instance = ~lazy_instance
119
120
  """
120
121
 
121
- def use(self, module: Module, priority: ModulePriority = ...):
122
+ def use(self, module: Module, *, priority: Literal["low", "high"] = ...):
122
123
  """
123
124
  Function for using another module. Using another module replaces the module's
124
125
  dependencies with those of the module used. If the dependency is not found, it
@@ -133,13 +134,14 @@ class Module:
133
134
  def use_temporarily(
134
135
  self,
135
136
  module: Module,
136
- priority: ModulePriority = ...,
137
+ *,
138
+ priority: Literal["low", "high"] = ...,
137
139
  ) -> ContextManager | ContextDecorator:
138
140
  """
139
141
  Context manager or decorator for temporary use of a module.
140
142
  """
141
143
 
142
- def change_priority(self, module: Module, priority: ModulePriority):
144
+ def change_priority(self, module: Module, priority: Literal["low", "high"]):
143
145
  """
144
146
  Function for changing the priority of a module in use.
145
147
  There are two priority values:
@@ -153,11 +155,6 @@ class Module:
153
155
  Function to unlock the module by deleting cached instances of singletons.
154
156
  """
155
157
 
156
- @final
157
- class ModulePriority(Enum):
158
- HIGH = ...
159
- LOW = ...
160
-
161
158
  @runtime_checkable
162
159
  class Injectable(Protocol[_T]):
163
160
  def __init__(self, factory: Callable[[], _T] = ..., /): ...
@@ -11,28 +11,23 @@ from collections.abc import (
11
11
  Iterator,
12
12
  Mapping,
13
13
  MutableMapping,
14
- Set,
15
14
  )
16
15
  from contextlib import ContextDecorator, contextmanager, suppress
17
16
  from dataclasses import dataclass, field
18
- from enum import Enum, auto
19
- from functools import (
20
- partialmethod,
21
- singledispatchmethod,
22
- update_wrapper,
23
- wraps,
24
- )
17
+ from functools import partialmethod, singledispatchmethod, update_wrapper
25
18
  from inspect import Signature, isclass
26
- from types import UnionType
19
+ from types import MethodType, UnionType
27
20
  from typing import (
28
21
  Any,
29
22
  ClassVar,
30
23
  ContextManager,
24
+ Literal,
31
25
  NamedTuple,
32
26
  NoReturn,
33
27
  Protocol,
34
28
  TypeVar,
35
29
  cast,
30
+ get_args,
36
31
  runtime_checkable,
37
32
  )
38
33
 
@@ -50,12 +45,21 @@ from injection.exceptions import (
50
45
  NoInjectable,
51
46
  )
52
47
 
53
- __all__ = ("Injectable", "Module", "ModulePriority")
48
+ __all__ = ("Injectable", "Module")
54
49
 
55
50
  _logger = logging.getLogger(__name__)
56
51
 
57
52
  _T = TypeVar("_T")
58
- Types = Iterable[type] | UnionType
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"]
59
63
 
60
64
 
61
65
  """
@@ -71,7 +75,7 @@ class ContainerEvent(Event, ABC):
71
75
  @dataclass(frozen=True, slots=True)
72
76
  class ContainerDependenciesUpdated(ContainerEvent):
73
77
  classes: Collection[type]
74
- override: bool
78
+ mode: _Mode
75
79
 
76
80
  def __str__(self) -> str:
77
81
  length = len(self.classes)
@@ -125,11 +129,11 @@ class ModuleRemoved(ModuleEvent):
125
129
  @dataclass(frozen=True, slots=True)
126
130
  class ModulePriorityUpdated(ModuleEvent):
127
131
  module_updated: Module
128
- priority: ModulePriority
132
+ priority: _Priority
129
133
 
130
134
  def __str__(self) -> str:
131
135
  return (
132
- f"In `{self.on_module}`, the priority `{self.priority.name}` "
136
+ f"In `{self.on_module}`, the priority `{self.priority}` "
133
137
  f"has been applied to `{self.module_updated}`."
134
138
  )
135
139
 
@@ -158,13 +162,6 @@ class Injectable(Protocol[_T]):
158
162
  raise NotImplementedError
159
163
 
160
164
 
161
- class FallbackInjectable(Injectable[_T], ABC):
162
- __slots__ = ()
163
-
164
- def __bool__(self) -> bool:
165
- return False
166
-
167
-
168
165
  @dataclass(repr=False, frozen=True, slots=True)
169
166
  class BaseInjectable(Injectable[_T], ABC):
170
167
  factory: Callable[[], _T]
@@ -205,7 +202,7 @@ class SingletonInjectable(BaseInjectable[_T]):
205
202
 
206
203
 
207
204
  @dataclass(repr=False, frozen=True, slots=True)
208
- class ShouldBeInjectable(FallbackInjectable[_T]):
205
+ class ShouldBeInjectable(Injectable[_T]):
209
206
  cls: type[_T]
210
207
 
211
208
  def get_instance(self) -> NoReturn:
@@ -244,49 +241,50 @@ Container
244
241
  """
245
242
 
246
243
 
244
+ class Record(NamedTuple):
245
+ injectable: Injectable
246
+ mode: _Mode
247
+
248
+
247
249
  @dataclass(repr=False, frozen=True, slots=True)
248
250
  class Container(Broker):
249
- __data: dict[type, Injectable] = field(default_factory=dict, init=False)
251
+ __data: dict[type, Record] = field(default_factory=dict, init=False)
250
252
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
251
253
 
252
254
  def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
253
- for origin in get_origins(cls):
254
- with suppress(KeyError):
255
- return self.__data[origin]
255
+ for cls in get_origins(cls):
256
+ try:
257
+ injectable, _ = self.__data[cls]
258
+ except KeyError:
259
+ continue
260
+
261
+ return injectable
256
262
 
257
263
  raise NoInjectable(cls)
258
264
 
259
265
  def __contains__(self, cls: type | UnionType, /) -> bool:
260
- return any(origin in self.__data for origin in get_origins(cls))
266
+ return any(cls in self.__data for cls in get_origins(cls))
261
267
 
262
268
  @property
263
269
  def is_locked(self) -> bool:
264
270
  return any(injectable.is_locked for injectable in self.__injectables)
265
271
 
266
- @property
267
- def __classes(self) -> frozenset[type]:
268
- return frozenset(self.__data)
269
-
270
272
  @property
271
273
  def __injectables(self) -> frozenset[Injectable]:
272
- return frozenset(self.__data.values())
274
+ return frozenset(injectable for injectable, _ in self.__data.values())
273
275
 
274
276
  @synchronized()
275
- def update(self, classes: Iterable[type], injectable: Injectable, override: bool):
276
- classes = frozenset(get_origins(*classes))
277
-
278
- if not injectable:
279
- classes -= self.__classes
280
- override = True
277
+ def update(self, classes: Iterable[type], injectable: Injectable, mode: _Mode):
278
+ records = {
279
+ cls: Record(injectable, mode)
280
+ for cls in self.__filter_classes(classes, mode)
281
+ }
281
282
 
282
- if classes:
283
- event = ContainerDependenciesUpdated(self, classes, override)
283
+ if records:
284
+ event = ContainerDependenciesUpdated(self, records.keys(), mode)
284
285
 
285
286
  with self.notify(event):
286
- if not override:
287
- self.__check_if_exists(classes)
288
-
289
- self.__data.update((cls, injectable) for cls in classes)
287
+ self.__data.update(records)
290
288
 
291
289
  return self
292
290
 
@@ -304,28 +302,32 @@ class Container(Broker):
304
302
  def notify(self, event: Event) -> ContextManager | ContextDecorator:
305
303
  return self.__channel.dispatch(event)
306
304
 
307
- def __check_if_exists(self, classes: Set[type]):
308
- intersection = classes & self.__classes
305
+ def __filter_classes(self, classes: Iterable[type], mode: _Mode) -> Iterator[type]:
306
+ modes = get_args(_Mode)
307
+ rank = modes.index(mode)
309
308
 
310
- for cls in intersection:
311
- if self.__data[cls]:
312
- raise RuntimeError(
313
- f"An injectable already exists for the class `{format_type(cls)}`."
314
- )
309
+ for cls in frozenset(get_origins(*classes)):
310
+ try:
311
+ _, current_mode = self.__data[cls]
315
312
 
313
+ except KeyError:
314
+ pass
316
315
 
317
- """
318
- Module
319
- """
316
+ else:
317
+ if mode == current_mode:
318
+ raise RuntimeError(
319
+ f"An injectable already exists for the class `{format_type(cls)}`."
320
+ )
320
321
 
322
+ elif rank < modes.index(current_mode):
323
+ continue
321
324
 
322
- class ModulePriority(Enum):
323
- HIGH = auto()
324
- LOW = auto()
325
+ yield cls
325
326
 
326
- @classmethod
327
- def get_default(cls):
328
- return cls.LOW
327
+
328
+ """
329
+ Module
330
+ """
329
331
 
330
332
 
331
333
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -373,14 +375,14 @@ class Module(EventListener, Broker):
373
375
  *,
374
376
  cls: type[Injectable] = NewInjectable,
375
377
  inject: bool = True,
376
- on: type | Types = None,
377
- override: bool = False,
378
+ on: type | _Types = None,
379
+ mode: _Mode = "normal",
378
380
  ):
379
381
  def decorator(wp):
380
382
  factory = self.inject(wp, return_factory=True) if inject else wp
381
383
  injectable = cls(factory)
382
384
  classes = find_types(wp, on)
383
- self.update(classes, injectable, override)
385
+ self.update(classes, injectable, mode)
384
386
  return wp
385
387
 
386
388
  return decorator(wrapped) if wrapped else decorator
@@ -389,7 +391,11 @@ class Module(EventListener, Broker):
389
391
 
390
392
  def should_be_injectable(self, wrapped: type = None, /):
391
393
  def decorator(wp):
392
- self[wp] = ShouldBeInjectable(wp)
394
+ self.update(
395
+ (wp,),
396
+ ShouldBeInjectable(wp),
397
+ mode="fallback",
398
+ )
393
399
  return wp
394
400
 
395
401
  return decorator(wrapped) if wrapped else decorator
@@ -397,16 +403,16 @@ class Module(EventListener, Broker):
397
403
  def set_constant(
398
404
  self,
399
405
  instance: _T,
400
- on: type | Types = None,
406
+ on: type | _Types = None,
401
407
  *,
402
- override: bool = False,
408
+ mode: _Mode = "normal",
403
409
  ) -> _T:
404
410
  cls = type(instance)
405
411
  self.injectable(
406
412
  lambda: instance,
407
413
  inject=False,
408
414
  on=(cls, on),
409
- override=override,
415
+ mode=mode,
410
416
  )
411
417
  return instance
412
418
 
@@ -433,7 +439,7 @@ class Module(EventListener, Broker):
433
439
 
434
440
  return decorator(wrapped) if wrapped else decorator
435
441
 
436
- def get_instance(self, cls: type[_T], none: bool = True) -> _T | None:
442
+ def get_instance(self, cls: type[_T], *, none: bool = True) -> _T | None:
437
443
  try:
438
444
  injectable = self[cls]
439
445
  except KeyError as exc:
@@ -448,6 +454,7 @@ class Module(EventListener, Broker):
448
454
  def get_lazy_instance(
449
455
  self,
450
456
  cls: type[_T],
457
+ *,
451
458
  cache: bool = False,
452
459
  ) -> Invertible[_T | None]:
453
460
  if cache:
@@ -461,16 +468,12 @@ class Module(EventListener, Broker):
461
468
  self,
462
469
  classes: Iterable[type],
463
470
  injectable: Injectable,
464
- override: bool = False,
471
+ mode: _Mode = "normal",
465
472
  ):
466
- self.__container.update(classes, injectable, override)
473
+ self.__container.update(classes, injectable, mode)
467
474
  return self
468
475
 
469
- def use(
470
- self,
471
- module: Module,
472
- priority: ModulePriority = ModulePriority.get_default(),
473
- ):
476
+ def use(self, module: Module, *, priority: _Priority = "low"):
474
477
  if module is self:
475
478
  raise ModuleError("Module can't be used by itself.")
476
479
 
@@ -500,13 +503,14 @@ class Module(EventListener, Broker):
500
503
  def use_temporarily(
501
504
  self,
502
505
  module: Module,
503
- priority: ModulePriority = ModulePriority.get_default(),
506
+ *,
507
+ priority: _Priority = "low",
504
508
  ) -> ContextManager | ContextDecorator:
505
- self.use(module, priority)
509
+ self.use(module, priority=priority)
506
510
  yield
507
511
  self.stop_using(module)
508
512
 
509
- def change_priority(self, module: Module, priority: ModulePriority):
513
+ def change_priority(self, module: Module, priority: _Priority):
510
514
  event = ModulePriorityUpdated(self, module, priority)
511
515
 
512
516
  with self.notify(event):
@@ -545,8 +549,8 @@ class Module(EventListener, Broker):
545
549
  if self.is_locked:
546
550
  raise ModuleLockError(f"`{self}` is locked.")
547
551
 
548
- def __move_module(self, module: Module, priority: ModulePriority):
549
- last = priority == ModulePriority.LOW
552
+ def __move_module(self, module: Module, priority: _Priority):
553
+ last = priority != "high"
550
554
 
551
555
  try:
552
556
  self.__modules.move_to_end(module, last=last)
@@ -635,48 +639,45 @@ class InjectedFunction(EventListener):
635
639
  __slots__ = (
636
640
  "__dict__",
637
641
  "__signature__",
642
+ "__wrapped__",
638
643
  "__dependencies",
639
644
  "__owner",
640
645
  "__setup_queue",
641
- "__wrapper",
642
646
  )
643
647
 
644
648
  def __init__(self, wrapped: Callable[..., Any], /):
645
- update_wrapper(self, wrapped)
646
-
647
- @wraps(wrapped)
648
- def wrapper(*args, **kwargs):
649
- self.__consume_setup_queue()
650
- args, kwargs = self.bind(args, kwargs)
651
- return wrapped(*args, **kwargs)
652
-
653
- self.__wrapper = wrapper
649
+ try:
650
+ variables = vars(wrapped)
651
+ except TypeError:
652
+ pass
653
+ else:
654
+ self.__update_vars(variables)
655
+ del variables
656
+
657
+ update_wrapper(self, wrapped, updated=())
654
658
  self.__dependencies = Dependencies.empty()
655
659
  self.__owner = None
656
660
  self.__setup_queue = LimitedQueue[Callable[[], Any]]()
657
- self.on_setup(
658
- lambda: self.__set_signature(
659
- inspect.signature(
660
- wrapped,
661
- eval_str=True,
662
- )
663
- )
664
- )
661
+ self.on_setup(self.__set_signature)
665
662
 
666
663
  def __repr__(self) -> str:
667
- return repr(self.__wrapper)
664
+ return repr(self.wrapped)
668
665
 
669
666
  def __str__(self) -> str:
670
- return str(self.__wrapper)
667
+ return str(self.wrapped)
671
668
 
672
669
  def __call__(self, /, *args, **kwargs) -> Any:
673
- return self.__wrapper(*args, **kwargs)
670
+ for function in self.__setup_queue:
671
+ function()
672
+
673
+ args, kwargs = self.bind(args, kwargs)
674
+ return self.wrapped(*args, **kwargs)
674
675
 
675
676
  def __get__(self, instance: object = None, owner: type = None):
676
677
  if instance is None:
677
678
  return self
678
679
 
679
- return self.__wrapper.__get__(instance, owner)
680
+ return MethodType(self, instance)
680
681
 
681
682
  def __set_name__(self, owner: type, name: str):
682
683
  self.set_owner(owner)
@@ -685,6 +686,10 @@ class InjectedFunction(EventListener):
685
686
  def signature(self) -> Signature:
686
687
  return self.__signature__
687
688
 
689
+ @property
690
+ def wrapped(self) -> Callable[..., Any]:
691
+ return self.__wrapped__
692
+
688
693
  def bind(
689
694
  self,
690
695
  args: Iterable[Any] = (),
@@ -736,12 +741,18 @@ class InjectedFunction(EventListener):
736
741
  yield
737
742
  self.update(event.on_module)
738
743
 
739
- def __consume_setup_queue(self):
740
- for function in self.__setup_queue:
741
- function()
742
-
744
+ def __set_signature(self):
745
+ self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
743
746
  return self
744
747
 
745
- def __set_signature(self, signature: Signature):
746
- self.__signature__ = signature
747
- return self
748
+ def __update_vars(self, variables: Mapping[str, Any], /):
749
+ def is_dunder(var: str) -> bool:
750
+ return var.startswith("__") and var.endswith("__")
751
+
752
+ restricted_vars = frozenset(var for var in dir(self) if not is_dunder(var))
753
+ variables = (
754
+ (var, value)
755
+ for var, value in variables.items()
756
+ if var not in restricted_vars
757
+ )
758
+ vars(self).update(variables)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-injection"
3
- version = "0.8.1"
3
+ version = "0.8.2"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  authors = ["remimd"]
6
6
  keywords = ["dependencies", "inject", "injection"]
@@ -13,7 +13,9 @@ repository = "https://github.com/100nm/python-injection"
13
13
  python = ">=3.10, <4"
14
14
 
15
15
  [tool.poetry.group.dev.dependencies]
16
+ argon2-cffi = "*"
16
17
  blacksheep = "*"
18
+ faker = "*"
17
19
  pydantic = "*"
18
20
  pytest = "*"
19
21
  pytest-asyncio = "*"
@@ -31,7 +33,7 @@ exclude_lines = [
31
33
  python_files = "test_*.py"
32
34
  addopts = "-p no:warnings --tb=short"
33
35
  asyncio_mode = "auto"
34
- testpaths = "tests/"
36
+ testpaths = "**/tests/"
35
37
 
36
38
  [tool.ruff]
37
39
  line-length = 88
@@ -1 +0,0 @@
1
- from ._pkg import *