python-injection 0.6.9__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

injection/_pkg.pyi CHANGED
@@ -38,12 +38,21 @@ class Module:
38
38
 
39
39
  def __init__(self, name: str = ...): ...
40
40
  def __contains__(self, cls: type | UnionType, /) -> bool: ...
41
- def inject(self, wrapped: Callable[..., Any] = ..., /):
41
+ def inject(
42
+ self,
43
+ wrapped: Callable[..., Any] = ...,
44
+ /,
45
+ *,
46
+ force: bool = ...,
47
+ ):
42
48
  """
43
49
  Decorator applicable to a class or function. Inject function dependencies using
44
50
  parameter type annotations. If applied to a class, the dependencies resolved
45
51
  will be those of the `__init__` method.
52
+
53
+ With `force=True`, parameters passed to replace dependencies will be ignored.
46
54
  """
55
+
47
56
  def injectable(
48
57
  self,
49
58
  wrapped: Callable[..., Any] = ...,
@@ -51,39 +60,48 @@ class Module:
51
60
  *,
52
61
  cls: type[Injectable] = ...,
53
62
  on: type | Iterable[type] | UnionType = ...,
63
+ override: bool = ...,
54
64
  ):
55
65
  """
56
66
  Decorator applicable to a class or function. It is used to indicate how the
57
67
  injectable will be constructed. At injection time, a new instance will be
58
68
  injected each time.
59
69
  """
70
+
60
71
  def singleton(
61
72
  self,
62
73
  wrapped: Callable[..., Any] = ...,
63
74
  /,
64
75
  *,
65
76
  on: type | Iterable[type] | UnionType = ...,
77
+ override: bool = ...,
66
78
  ):
67
79
  """
68
80
  Decorator applicable to a class or function. It is used to indicate how the
69
81
  singleton will be constructed. At injection time, the injected instance will
70
82
  always be the same.
71
83
  """
84
+
72
85
  def set_constant(
73
86
  self,
74
87
  instance: _T,
75
88
  on: type | Iterable[type] | UnionType = ...,
89
+ *,
90
+ override: bool = ...,
76
91
  ) -> _T:
77
92
  """
78
93
  Function for registering a specific instance to be injected. This is useful for
79
94
  registering global variables. The difference with the singleton decorator is
80
95
  that no dependencies are resolved, so the module doesn't need to be locked.
81
96
  """
82
- def get_instance(self, cls: type[_T]) -> _T | None:
97
+
98
+ def get_instance(self, cls: type[_T], none: bool = ...) -> _T | None:
83
99
  """
84
100
  Function used to retrieve an instance associated with the type passed in
85
- parameter or return `None`.
101
+ parameter or return `None` but if `none` parameter is `False` an exception
102
+ will be raised.
86
103
  """
104
+
87
105
  def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
88
106
  """
89
107
  Function used to retrieve an instance associated with the type passed in
@@ -92,16 +110,19 @@ class Module:
92
110
 
93
111
  Example: instance = ~lazy_instance
94
112
  """
113
+
95
114
  def use(self, module: Module, priority: ModulePriorities = ...):
96
115
  """
97
116
  Function for using another module. Using another module replaces the module's
98
117
  dependencies with those of the module used. If the dependency is not found, it
99
118
  will be searched for in the module's dependency container.
100
119
  """
120
+
101
121
  def stop_using(self, module: Module):
102
122
  """
103
123
  Function to remove a module in use.
104
124
  """
125
+
105
126
  def use_temporarily(
106
127
  self,
107
128
  module: Module,
@@ -110,6 +131,7 @@ class Module:
110
131
  """
111
132
  Context manager or decorator for temporary use of a module.
112
133
  """
134
+
113
135
  def change_priority(self, module: Module, priority: ModulePriorities):
114
136
  """
115
137
  Function for changing the priority of a module in use.
@@ -118,6 +140,7 @@ class Module:
118
140
  * **LOW**: The module concerned becomes the least important of the modules used.
119
141
  * **HIGH**: The module concerned becomes the most important of the modules used.
120
142
  """
143
+
121
144
  def unlock(self):
122
145
  """
123
146
  Function to unlock the module by deleting cached instances of singletons.
@@ -130,7 +153,7 @@ class ModulePriorities(Enum):
130
153
 
131
154
  @runtime_checkable
132
155
  class Injectable(Protocol[_T]):
133
- def __init__(self, factory: Callable[[], _T] = ..., *args, **kwargs): ...
156
+ def __init__(self, factory: Callable[[], _T] = ..., /): ...
134
157
  @property
135
158
  def is_locked(self) -> bool: ...
136
159
  def unlock(self): ...
injection/core/module.py CHANGED
@@ -61,6 +61,7 @@ class ContainerEvent(Event, ABC):
61
61
  @dataclass(frozen=True, slots=True)
62
62
  class ContainerDependenciesUpdated(ContainerEvent):
63
63
  classes: Collection[type]
64
+ override: bool
64
65
 
65
66
  def __str__(self) -> str:
66
67
  length = len(self.classes)
@@ -132,15 +133,13 @@ Injectables
132
133
  class Injectable(Protocol[_T]):
133
134
  __slots__ = ()
134
135
 
135
- def __init__(self, factory: Callable[[], _T] = ..., *args, **kwargs):
136
- ...
136
+ def __init__(self, __factory: Callable[[], _T] = ..., /): ...
137
137
 
138
138
  @property
139
139
  def is_locked(self) -> bool:
140
140
  return False
141
141
 
142
- def unlock(self):
143
- ...
142
+ def unlock(self): ...
144
143
 
145
144
  @abstractmethod
146
145
  def get_instance(self) -> _T:
@@ -214,26 +213,19 @@ class Container:
214
213
  def __injectables(self) -> frozenset[Injectable]:
215
214
  return frozenset(self.__data.values())
216
215
 
217
- def update(self, classes: Types, injectable: Injectable):
218
- classes = frozenset(get_origins(*classes))
216
+ def update(self, classes: Types, injectable: Injectable, override: bool):
217
+ values = {origin: injectable for origin in get_origins(*classes)}
219
218
 
220
- if classes:
221
- event = ContainerDependenciesUpdated(self, classes)
219
+ if values:
220
+ event = ContainerDependenciesUpdated(self, values, override)
222
221
 
223
222
  with self.notify(event):
224
- self.__data.update(
225
- (self.check_if_exists(cls), injectable) for cls in classes
226
- )
227
-
228
- return self
223
+ if not override:
224
+ self.__check_if_exists(*values)
229
225
 
230
- def check_if_exists(self, cls: type) -> type:
231
- if cls in self.__data:
232
- raise RuntimeError(
233
- f"An injectable already exists for the class `{format_type(cls)}`."
234
- )
226
+ self.__data.update(values)
235
227
 
236
- return cls
228
+ return self
237
229
 
238
230
  def unlock(self):
239
231
  for injectable in self.__injectables:
@@ -246,6 +238,13 @@ class Container:
246
238
  def notify(self, event: Event) -> ContextManager | ContextDecorator:
247
239
  return self.__channel.dispatch(event)
248
240
 
241
+ def __check_if_exists(self, *classes: type):
242
+ for cls in classes:
243
+ if cls in self.__data:
244
+ raise RuntimeError(
245
+ f"An injectable already exists for the class `{format_type(cls)}`."
246
+ )
247
+
249
248
 
250
249
  """
251
250
  Module
@@ -282,7 +281,7 @@ class Module(EventListener):
282
281
  raise NoInjectable(cls)
283
282
 
284
283
  def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
285
- self.update((cls,), injectable)
284
+ self.update((cls,), injectable, override=True)
286
285
 
287
286
  def __contains__(self, cls: type | UnionType, /) -> bool:
288
287
  return any(cls in broker for broker in self.__brokers)
@@ -306,22 +305,29 @@ class Module(EventListener):
306
305
  *,
307
306
  cls: type[Injectable] = NewInjectable,
308
307
  on: type | Types = None,
308
+ override: bool = False,
309
309
  ):
310
310
  def decorator(wp):
311
311
  factory = self.inject(wp, return_factory=True)
312
312
  injectable = cls(factory)
313
313
  classes = find_types(wp, on)
314
- self.update(classes, injectable)
314
+ self.update(classes, injectable, override)
315
315
  return wp
316
316
 
317
317
  return decorator(wrapped) if wrapped else decorator
318
318
 
319
319
  singleton = partialmethod(injectable, cls=SingletonInjectable)
320
320
 
321
- def set_constant(self, instance: _T, on: type | Types = None) -> _T:
321
+ def set_constant(
322
+ self,
323
+ instance: _T,
324
+ on: type | Types = None,
325
+ *,
326
+ override: bool = False,
327
+ ) -> _T:
322
328
  cls = type(instance)
323
329
 
324
- @self.injectable(on=(cls, on))
330
+ @self.injectable(on=(cls, on), override=override)
325
331
  def get_constant():
326
332
  return instance
327
333
 
@@ -332,29 +338,33 @@ class Module(EventListener):
332
338
  wrapped: Callable[..., Any] = None,
333
339
  /,
334
340
  *,
341
+ force: bool = False,
335
342
  return_factory: bool = False,
336
343
  ):
337
344
  def decorator(wp):
338
345
  if not return_factory and isclass(wp):
339
- wp.__init__ = decorator(wp.__init__)
346
+ wp.__init__ = self.inject(wp.__init__, force=force)
340
347
  return wp
341
348
 
342
349
  lazy_binder = Lazy[Binder](lambda: self.__new_binder(wp))
343
350
 
344
351
  @wraps(wp)
345
352
  def wrapper(*args, **kwargs):
346
- arguments = (~lazy_binder).bind(*args, **kwargs)
353
+ arguments = (~lazy_binder).bind(args, kwargs, force)
347
354
  return wp(*arguments.args, **arguments.kwargs)
348
355
 
349
356
  return wrapper
350
357
 
351
358
  return decorator(wrapped) if wrapped else decorator
352
359
 
353
- def get_instance(self, cls: type[_T]) -> _T | None:
360
+ def get_instance(self, cls: type[_T], none: bool = True) -> _T | None:
354
361
  try:
355
362
  injectable = self[cls]
356
- except KeyError:
357
- return None
363
+ except KeyError as exc:
364
+ if none:
365
+ return None
366
+
367
+ raise exc from exc
358
368
 
359
369
  instance = injectable.get_instance()
360
370
  return cast(cls, instance)
@@ -362,8 +372,8 @@ class Module(EventListener):
362
372
  def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
363
373
  return Lazy(lambda: self.get_instance(cls))
364
374
 
365
- def update(self, classes: Types, injectable: Injectable):
366
- self.__container.update(classes, injectable)
375
+ def update(self, classes: Types, injectable: Injectable, override: bool = False):
376
+ self.__container.update(classes, injectable, override)
367
377
  return self
368
378
 
369
379
  def use(
@@ -476,8 +486,8 @@ class Dependencies:
476
486
  yield name, injectable.get_instance()
477
487
 
478
488
  @property
479
- def arguments(self) -> dict[str, Any]:
480
- return dict(self)
489
+ def arguments(self) -> OrderedDict[str, Any]:
490
+ return OrderedDict(self)
481
491
 
482
492
  @classmethod
483
493
  def from_mapping(cls, mapping: Mapping[str, Injectable]):
@@ -519,12 +529,26 @@ class Binder(EventListener):
519
529
  self.__signature = signature
520
530
  self.__dependencies = Dependencies.empty()
521
531
 
522
- def bind(self, /, *args, **kwargs) -> Arguments:
532
+ def bind(
533
+ self,
534
+ args: Iterable[Any] = (),
535
+ kwargs: Mapping[str, Any] = None,
536
+ force: bool = False,
537
+ ) -> Arguments:
538
+ if kwargs is None:
539
+ kwargs = {}
540
+
523
541
  if not self.__dependencies:
524
542
  return Arguments(args, kwargs)
525
543
 
526
544
  bound = self.__signature.bind_partial(*args, **kwargs)
527
- bound.arguments = self.__dependencies.arguments | bound.arguments
545
+ dependencies = self.__dependencies.arguments
546
+
547
+ if force:
548
+ bound.arguments |= dependencies
549
+ else:
550
+ bound.arguments = dependencies | bound.arguments
551
+
528
552
  return Arguments(bound.args, bound.kwargs)
529
553
 
530
554
  def update(self, module: Module):
@@ -532,8 +556,7 @@ class Binder(EventListener):
532
556
  return self
533
557
 
534
558
  @singledispatchmethod
535
- def on_event(self, event: Event, /):
536
- ...
559
+ def on_event(self, event: Event, /): ...
537
560
 
538
561
  @on_event.register
539
562
  @contextmanager
@@ -1,7 +1,8 @@
1
1
  from typing import Any, TypeVar
2
2
 
3
3
  from injection import Module, default_module
4
- from injection.exceptions import NoInjectable
4
+
5
+ __all__ = ("InjectionServices",)
5
6
 
6
7
  _T = TypeVar("_T")
7
8
 
@@ -24,9 +25,4 @@ class InjectionServices:
24
25
  return self
25
26
 
26
27
  def resolve(self, cls: type[_T] | Any, *__args, **__kwargs) -> _T:
27
- instance = self.__module.get_instance(cls)
28
-
29
- if instance is None:
30
- raise NoInjectable(cls)
31
-
32
- return instance
28
+ return self.__module.get_instance(cls, none=False)
@@ -0,0 +1,15 @@
1
+ from typer import Option
2
+
3
+ __all__ = ("ignore",)
4
+
5
+
6
+ def ignore():
7
+ """
8
+ Typer option for the CLI to ignore this option and replace it with `None`.
9
+ """
10
+
11
+ return Option(
12
+ default_factory=str,
13
+ parser=lambda _: None,
14
+ hidden=True,
15
+ )
injection/utils.py CHANGED
@@ -5,11 +5,14 @@ from types import ModuleType
5
5
  __all__ = ("load_package",)
6
6
 
7
7
 
8
- def load_package(package: ModuleType):
8
+ def load_package(package: ModuleType | str):
9
9
  """
10
10
  Function for importing all modules in a Python package.
11
11
  """
12
12
 
13
+ if isinstance(package, str):
14
+ package = import_module(package)
15
+
13
16
  try:
14
17
  path = package.__path__
15
18
  except AttributeError as exc:
@@ -17,7 +20,7 @@ def load_package(package: ModuleType):
17
20
  "Package has no `__path__` attribute, as it's probably a module."
18
21
  ) from exc
19
22
 
20
- for info in walk_packages(path, prefix=f"{package.__name__}."):
23
+ for info in walk_packages(path=path, prefix=f"{package.__name__}."):
21
24
  if info.ispkg:
22
25
  continue
23
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.6.9
3
+ Version: 0.7.0
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -137,6 +137,20 @@ class C(B):
137
137
  ...
138
138
  ```
139
139
 
140
+ If a class is registered in a package and you want to override it, there is the `override` parameter:
141
+
142
+ ```python
143
+ @singleton
144
+ class A:
145
+ ...
146
+
147
+ # ...
148
+
149
+ @singleton(on=A, override=True)
150
+ class B(A):
151
+ ...
152
+ ```
153
+
140
154
  ## Recipes
141
155
 
142
156
  A recipe is a function that tells the injector how to construct the instance to be injected. It is important to specify
@@ -1,17 +1,18 @@
1
1
  injection/__init__.py,sha256=9_AVJILxKIBiL_6KJSh2RRydgSHXH38uahAGD0S1-dI,20
2
2
  injection/_pkg.py,sha256=dM48pyMks5CDaUdl4Mv6h_BudbAl8QfYsLbYW9-8Wfw,564
3
- injection/_pkg.pyi,sha256=EFMxWfYDqf2aI3UosWNXacof8kPsoiu_9GuqFZg4pOQ,4602
3
+ injection/_pkg.pyi,sha256=s0e9dUdpVxUbC3YD02MFTiPGHz6-R_HtQOxRejxnWK8,4944
4
4
  injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  injection/common/event.py,sha256=uFoGRnxxkohH53JEStn4tN2Pn79HlgGExh7VkXdBwVQ,1316
6
6
  injection/common/lazy.py,sha256=HIefQ1z7ivgU791MDSwBmUcdST3bOv0sivSMyR2DfHc,1493
7
7
  injection/common/tools/__init__.py,sha256=S2y9DaQ4EaTRty9fKpBtPXA7hSjkzgM5N2jFf5jcmJU,21
8
8
  injection/common/tools/_type.py,sha256=-zL0dtoVZme71Mscvav7iEWxY2-JltzNTekbWOCPSFo,1276
9
9
  injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
10
- injection/core/module.py,sha256=d_dkGRQzEkR_frxdNbxBxbp0jqB-mU0UZ4mwg5aIATA,14407
10
+ injection/core/module.py,sha256=xdRHR4M2JvtOI15yVaKoEg9cijZERmckxRoutR482CM,15055
11
11
  injection/exceptions.py,sha256=wd4OxmpneGEmlZ0yeNBfnCYfPYqUksfbmA2mopLNm_s,688
12
12
  injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- injection/integrations/blacksheep.py,sha256=F4h85VS9dLGHn-ARDARYMB02Htsd3nKMT9Y-EF4cOqE,818
14
- injection/utils.py,sha256=a4w2SXyXP1A8AC_4hyEypByVUZOwSFIA3lh8StLxuLY,590
15
- python_injection-0.6.9.dist-info/METADATA,sha256=Y8KBbVJPazkix_y012d70rivwFLvdRwrhEnbpCItOXA,3227
16
- python_injection-0.6.9.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
17
- python_injection-0.6.9.dist-info/RECORD,,
13
+ injection/integrations/blacksheep.py,sha256=vcLil1IccS7JtXpuVu7s2LqN5Zravfe_7xpAt5cTIU0,723
14
+ injection/integrations/typer.py,sha256=XtQjQhcd7k-513Wp5cP-7ES8-uWI5iI1T1o4kIEFvjU,266
15
+ injection/utils.py,sha256=_79aiciimZpuoUTz5lojKySUMMzpkU-e7SotiHIFTI8,676
16
+ python_injection-0.7.0.dist-info/METADATA,sha256=B_bbwe6RMbP1ox55Q526h_MeyAdtJ72ibHkRfgdCHzY,3433
17
+ python_injection-0.7.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
18
+ python_injection-0.7.0.dist-info/RECORD,,