python-injection 0.8.1.post0__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.post0 → python_injection-0.8.2}/PKG-INFO +3 -3
  2. {python_injection-0.8.1.post0 → python_injection-0.8.2}/documentation/basic-usage.md +2 -2
  3. python_injection-0.8.1.post0/injection/_pkg.py → python_injection-0.8.2/injection/__init__.py +1 -2
  4. python_injection-0.8.1.post0/injection/_pkg.pyi → python_injection-0.8.2/injection/__init__.pyi +11 -14
  5. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/core/module.py +103 -74
  6. {python_injection-0.8.1.post0 → python_injection-0.8.2}/pyproject.toml +1 -1
  7. python_injection-0.8.1.post0/injection/__init__.py +0 -1
  8. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/__init__.py +0 -0
  9. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/event.py +0 -0
  10. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/invertible.py +0 -0
  11. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/lazy.py +0 -0
  12. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/queue.py +0 -0
  13. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/tools/__init__.py +0 -0
  14. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/tools/threading.py +0 -0
  15. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/common/tools/type.py +0 -0
  16. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/core/__init__.py +0 -0
  17. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/exceptions.py +0 -0
  18. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/integrations/__init__.py +0 -0
  19. {python_injection-0.8.1.post0 → python_injection-0.8.2}/injection/integrations/blacksheep.py +0 -0
  20. {python_injection-0.8.1.post0 → 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.post0
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
@@ -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 `override` 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
@@ -145,7 +145,7 @@ class InaccessibleService:
145
145
 
146
146
  # ...
147
147
 
148
- @injectable(on=InaccessibleService, override=True)
148
+ @injectable(on=InaccessibleService, mode="override")
149
149
  class ServiceOverload(InaccessibleService):
150
150
  ...
151
151
  ```
@@ -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 `override` 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
@@ -128,7 +128,7 @@ class InaccessibleService:
128
128
 
129
129
  # ...
130
130
 
131
- @injectable(on=InaccessibleService, override=True)
131
+ @injectable(on=InaccessibleService, mode="override")
132
132
  class ServiceOverload(InaccessibleService):
133
133
  ...
134
134
  ```
@@ -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,11 +11,9 @@ 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
17
  from functools import partialmethod, singledispatchmethod, update_wrapper
20
18
  from inspect import Signature, isclass
21
19
  from types import MethodType, UnionType
@@ -23,11 +21,13 @@ from typing import (
23
21
  Any,
24
22
  ClassVar,
25
23
  ContextManager,
24
+ Literal,
26
25
  NamedTuple,
27
26
  NoReturn,
28
27
  Protocol,
29
28
  TypeVar,
30
29
  cast,
30
+ get_args,
31
31
  runtime_checkable,
32
32
  )
33
33
 
@@ -45,12 +45,21 @@ from injection.exceptions import (
45
45
  NoInjectable,
46
46
  )
47
47
 
48
- __all__ = ("Injectable", "Module", "ModulePriority")
48
+ __all__ = ("Injectable", "Module")
49
49
 
50
50
  _logger = logging.getLogger(__name__)
51
51
 
52
52
  _T = TypeVar("_T")
53
- 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"]
54
63
 
55
64
 
56
65
  """
@@ -66,7 +75,7 @@ class ContainerEvent(Event, ABC):
66
75
  @dataclass(frozen=True, slots=True)
67
76
  class ContainerDependenciesUpdated(ContainerEvent):
68
77
  classes: Collection[type]
69
- override: bool
78
+ mode: _Mode
70
79
 
71
80
  def __str__(self) -> str:
72
81
  length = len(self.classes)
@@ -120,11 +129,11 @@ class ModuleRemoved(ModuleEvent):
120
129
  @dataclass(frozen=True, slots=True)
121
130
  class ModulePriorityUpdated(ModuleEvent):
122
131
  module_updated: Module
123
- priority: ModulePriority
132
+ priority: _Priority
124
133
 
125
134
  def __str__(self) -> str:
126
135
  return (
127
- f"In `{self.on_module}`, the priority `{self.priority.name}` "
136
+ f"In `{self.on_module}`, the priority `{self.priority}` "
128
137
  f"has been applied to `{self.module_updated}`."
129
138
  )
130
139
 
@@ -153,13 +162,6 @@ class Injectable(Protocol[_T]):
153
162
  raise NotImplementedError
154
163
 
155
164
 
156
- class FallbackInjectable(Injectable[_T], ABC):
157
- __slots__ = ()
158
-
159
- def __bool__(self) -> bool:
160
- return False
161
-
162
-
163
165
  @dataclass(repr=False, frozen=True, slots=True)
164
166
  class BaseInjectable(Injectable[_T], ABC):
165
167
  factory: Callable[[], _T]
@@ -200,7 +202,7 @@ class SingletonInjectable(BaseInjectable[_T]):
200
202
 
201
203
 
202
204
  @dataclass(repr=False, frozen=True, slots=True)
203
- class ShouldBeInjectable(FallbackInjectable[_T]):
205
+ class ShouldBeInjectable(Injectable[_T]):
204
206
  cls: type[_T]
205
207
 
206
208
  def get_instance(self) -> NoReturn:
@@ -239,49 +241,50 @@ Container
239
241
  """
240
242
 
241
243
 
244
+ class Record(NamedTuple):
245
+ injectable: Injectable
246
+ mode: _Mode
247
+
248
+
242
249
  @dataclass(repr=False, frozen=True, slots=True)
243
250
  class Container(Broker):
244
- __data: dict[type, Injectable] = field(default_factory=dict, init=False)
251
+ __data: dict[type, Record] = field(default_factory=dict, init=False)
245
252
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
246
253
 
247
254
  def __getitem__(self, cls: type[_T] | UnionType, /) -> Injectable[_T]:
248
- for origin in get_origins(cls):
249
- with suppress(KeyError):
250
- 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
251
262
 
252
263
  raise NoInjectable(cls)
253
264
 
254
265
  def __contains__(self, cls: type | UnionType, /) -> bool:
255
- 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))
256
267
 
257
268
  @property
258
269
  def is_locked(self) -> bool:
259
270
  return any(injectable.is_locked for injectable in self.__injectables)
260
271
 
261
- @property
262
- def __classes(self) -> frozenset[type]:
263
- return frozenset(self.__data)
264
-
265
272
  @property
266
273
  def __injectables(self) -> frozenset[Injectable]:
267
- return frozenset(self.__data.values())
274
+ return frozenset(injectable for injectable, _ in self.__data.values())
268
275
 
269
276
  @synchronized()
270
- def update(self, classes: Iterable[type], injectable: Injectable, override: bool):
271
- classes = frozenset(get_origins(*classes))
272
-
273
- if not injectable:
274
- classes -= self.__classes
275
- 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
+ }
276
282
 
277
- if classes:
278
- event = ContainerDependenciesUpdated(self, classes, override)
283
+ if records:
284
+ event = ContainerDependenciesUpdated(self, records.keys(), mode)
279
285
 
280
286
  with self.notify(event):
281
- if not override:
282
- self.__check_if_exists(classes)
283
-
284
- self.__data.update((cls, injectable) for cls in classes)
287
+ self.__data.update(records)
285
288
 
286
289
  return self
287
290
 
@@ -299,28 +302,32 @@ class Container(Broker):
299
302
  def notify(self, event: Event) -> ContextManager | ContextDecorator:
300
303
  return self.__channel.dispatch(event)
301
304
 
302
- def __check_if_exists(self, classes: Set[type]):
303
- 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)
304
308
 
305
- for cls in intersection:
306
- if self.__data[cls]:
307
- raise RuntimeError(
308
- f"An injectable already exists for the class `{format_type(cls)}`."
309
- )
309
+ for cls in frozenset(get_origins(*classes)):
310
+ try:
311
+ _, current_mode = self.__data[cls]
310
312
 
313
+ except KeyError:
314
+ pass
311
315
 
312
- """
313
- Module
314
- """
316
+ else:
317
+ if mode == current_mode:
318
+ raise RuntimeError(
319
+ f"An injectable already exists for the class `{format_type(cls)}`."
320
+ )
315
321
 
322
+ elif rank < modes.index(current_mode):
323
+ continue
316
324
 
317
- class ModulePriority(Enum):
318
- HIGH = auto()
319
- LOW = auto()
325
+ yield cls
320
326
 
321
- @classmethod
322
- def get_default(cls):
323
- return cls.LOW
327
+
328
+ """
329
+ Module
330
+ """
324
331
 
325
332
 
326
333
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -368,14 +375,14 @@ class Module(EventListener, Broker):
368
375
  *,
369
376
  cls: type[Injectable] = NewInjectable,
370
377
  inject: bool = True,
371
- on: type | Types = None,
372
- override: bool = False,
378
+ on: type | _Types = None,
379
+ mode: _Mode = "normal",
373
380
  ):
374
381
  def decorator(wp):
375
382
  factory = self.inject(wp, return_factory=True) if inject else wp
376
383
  injectable = cls(factory)
377
384
  classes = find_types(wp, on)
378
- self.update(classes, injectable, override)
385
+ self.update(classes, injectable, mode)
379
386
  return wp
380
387
 
381
388
  return decorator(wrapped) if wrapped else decorator
@@ -384,7 +391,11 @@ class Module(EventListener, Broker):
384
391
 
385
392
  def should_be_injectable(self, wrapped: type = None, /):
386
393
  def decorator(wp):
387
- self[wp] = ShouldBeInjectable(wp)
394
+ self.update(
395
+ (wp,),
396
+ ShouldBeInjectable(wp),
397
+ mode="fallback",
398
+ )
388
399
  return wp
389
400
 
390
401
  return decorator(wrapped) if wrapped else decorator
@@ -392,16 +403,16 @@ class Module(EventListener, Broker):
392
403
  def set_constant(
393
404
  self,
394
405
  instance: _T,
395
- on: type | Types = None,
406
+ on: type | _Types = None,
396
407
  *,
397
- override: bool = False,
408
+ mode: _Mode = "normal",
398
409
  ) -> _T:
399
410
  cls = type(instance)
400
411
  self.injectable(
401
412
  lambda: instance,
402
413
  inject=False,
403
414
  on=(cls, on),
404
- override=override,
415
+ mode=mode,
405
416
  )
406
417
  return instance
407
418
 
@@ -428,7 +439,7 @@ class Module(EventListener, Broker):
428
439
 
429
440
  return decorator(wrapped) if wrapped else decorator
430
441
 
431
- 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:
432
443
  try:
433
444
  injectable = self[cls]
434
445
  except KeyError as exc:
@@ -443,6 +454,7 @@ class Module(EventListener, Broker):
443
454
  def get_lazy_instance(
444
455
  self,
445
456
  cls: type[_T],
457
+ *,
446
458
  cache: bool = False,
447
459
  ) -> Invertible[_T | None]:
448
460
  if cache:
@@ -456,16 +468,12 @@ class Module(EventListener, Broker):
456
468
  self,
457
469
  classes: Iterable[type],
458
470
  injectable: Injectable,
459
- override: bool = False,
471
+ mode: _Mode = "normal",
460
472
  ):
461
- self.__container.update(classes, injectable, override)
473
+ self.__container.update(classes, injectable, mode)
462
474
  return self
463
475
 
464
- def use(
465
- self,
466
- module: Module,
467
- priority: ModulePriority = ModulePriority.get_default(),
468
- ):
476
+ def use(self, module: Module, *, priority: _Priority = "low"):
469
477
  if module is self:
470
478
  raise ModuleError("Module can't be used by itself.")
471
479
 
@@ -495,13 +503,14 @@ class Module(EventListener, Broker):
495
503
  def use_temporarily(
496
504
  self,
497
505
  module: Module,
498
- priority: ModulePriority = ModulePriority.get_default(),
506
+ *,
507
+ priority: _Priority = "low",
499
508
  ) -> ContextManager | ContextDecorator:
500
- self.use(module, priority)
509
+ self.use(module, priority=priority)
501
510
  yield
502
511
  self.stop_using(module)
503
512
 
504
- def change_priority(self, module: Module, priority: ModulePriority):
513
+ def change_priority(self, module: Module, priority: _Priority):
505
514
  event = ModulePriorityUpdated(self, module, priority)
506
515
 
507
516
  with self.notify(event):
@@ -540,8 +549,8 @@ class Module(EventListener, Broker):
540
549
  if self.is_locked:
541
550
  raise ModuleLockError(f"`{self}` is locked.")
542
551
 
543
- def __move_module(self, module: Module, priority: ModulePriority):
544
- last = priority == ModulePriority.LOW
552
+ def __move_module(self, module: Module, priority: _Priority):
553
+ last = priority != "high"
545
554
 
546
555
  try:
547
556
  self.__modules.move_to_end(module, last=last)
@@ -637,7 +646,15 @@ class InjectedFunction(EventListener):
637
646
  )
638
647
 
639
648
  def __init__(self, wrapped: Callable[..., Any], /):
640
- update_wrapper(self, wrapped)
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=())
641
658
  self.__dependencies = Dependencies.empty()
642
659
  self.__owner = None
643
660
  self.__setup_queue = LimitedQueue[Callable[[], Any]]()
@@ -727,3 +744,15 @@ class InjectedFunction(EventListener):
727
744
  def __set_signature(self):
728
745
  self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
729
746
  return self
747
+
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.post0"
3
+ version = "0.8.2"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  authors = ["remimd"]
6
6
  keywords = ["dependencies", "inject", "injection"]
@@ -1 +0,0 @@
1
- from ._pkg import *