python-injection 0.7.3__py3-none-any.whl → 0.7.5__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.py CHANGED
@@ -1,9 +1,9 @@
1
- from .core import Injectable, Module, ModulePriorities
1
+ from .core import Injectable, Module, ModulePriority
2
2
 
3
3
  __all__ = (
4
4
  "Injectable",
5
5
  "Module",
6
- "ModulePriorities",
6
+ "ModulePriority",
7
7
  "default_module",
8
8
  "get_instance",
9
9
  "get_lazy_instance",
injection/_pkg.pyi CHANGED
@@ -39,19 +39,11 @@ class Module:
39
39
 
40
40
  def __init__(self, name: str = ...): ...
41
41
  def __contains__(self, cls: type | UnionType, /) -> bool: ...
42
- def inject(
43
- self,
44
- wrapped: Callable[..., Any] = ...,
45
- /,
46
- *,
47
- force: bool = ...,
48
- ):
42
+ def inject(self, wrapped: Callable[..., Any] = ..., /):
49
43
  """
50
44
  Decorator applicable to a class or function. Inject function dependencies using
51
45
  parameter type annotations. If applied to a class, the dependencies resolved
52
46
  will be those of the `__init__` method.
53
-
54
- With `force=True`, parameters passed to replace dependencies will be ignored.
55
47
  """
56
48
 
57
49
  def injectable(
@@ -85,14 +77,7 @@ class Module:
85
77
  always be the same.
86
78
  """
87
79
 
88
- def should_be_injectable(
89
- self,
90
- wrapped: Callable[..., Any] = ...,
91
- /,
92
- *,
93
- on: type | Iterable[type] | UnionType = ...,
94
- override: bool = ...,
95
- ):
80
+ def should_be_injectable(self, wrapped: type = ..., /):
96
81
  """
97
82
  Decorator applicable to a class. It is used to specify whether an injectable
98
83
  should be registered. Raise an exception at injection time if the class isn't
@@ -128,7 +113,7 @@ class Module:
128
113
  Example: instance = ~lazy_instance
129
114
  """
130
115
 
131
- def use(self, module: Module, priority: ModulePriorities = ...):
116
+ def use(self, module: Module, priority: ModulePriority = ...):
132
117
  """
133
118
  Function for using another module. Using another module replaces the module's
134
119
  dependencies with those of the module used. If the dependency is not found, it
@@ -143,13 +128,13 @@ class Module:
143
128
  def use_temporarily(
144
129
  self,
145
130
  module: Module,
146
- priority: ModulePriorities = ...,
131
+ priority: ModulePriority = ...,
147
132
  ) -> ContextManager | ContextDecorator:
148
133
  """
149
134
  Context manager or decorator for temporary use of a module.
150
135
  """
151
136
 
152
- def change_priority(self, module: Module, priority: ModulePriorities):
137
+ def change_priority(self, module: Module, priority: ModulePriority):
153
138
  """
154
139
  Function for changing the priority of a module in use.
155
140
  There are two priority values:
@@ -164,7 +149,7 @@ class Module:
164
149
  """
165
150
 
166
151
  @final
167
- class ModulePriorities(Enum):
152
+ class ModulePriority(Enum):
168
153
  HIGH = ...
169
154
  LOW = ...
170
155
 
injection/common/event.py CHANGED
@@ -4,6 +4,8 @@ from dataclasses import dataclass, field
4
4
  from typing import ContextManager
5
5
  from weakref import WeakSet
6
6
 
7
+ from injection.common.tools.threading import frozen_collection
8
+
7
9
  __all__ = ("Event", "EventChannel", "EventListener")
8
10
 
9
11
 
@@ -26,7 +28,7 @@ class EventChannel:
26
28
  @contextmanager
27
29
  def dispatch(self, event: Event) -> ContextManager | ContextDecorator:
28
30
  with ExitStack() as stack:
29
- for listener in tuple(self.__listeners):
31
+ for listener in frozen_collection(self.__listeners):
30
32
  context_manager = listener.on_event(event)
31
33
 
32
34
  if context_manager is None:
injection/common/lazy.py CHANGED
@@ -1,12 +1,11 @@
1
1
  from collections.abc import Callable, Iterator, Mapping
2
- from threading import RLock
2
+ from contextlib import suppress
3
3
  from types import MappingProxyType
4
4
  from typing import Any, Generic, TypeVar
5
5
 
6
- __all__ = ("Lazy", "LazyMapping")
6
+ from injection.common.tools.threading import thread_lock
7
7
 
8
- _sentinel = object()
9
- _thread_lock = RLock()
8
+ __all__ = ("Lazy", "LazyMapping")
10
9
 
11
10
  _T = TypeVar("_T")
12
11
  _K = TypeVar("_K")
@@ -14,39 +13,40 @@ _V = TypeVar("_V")
14
13
 
15
14
 
16
15
  class Lazy(Generic[_T]):
17
- __slots__ = ("__factory", "__value")
16
+ __slots__ = ("is_set", "__generator")
18
17
 
19
18
  def __init__(self, factory: Callable[[], _T]):
20
- self.__factory = factory
21
- self.__value = _sentinel
19
+ def generator() -> Iterator[_T]:
20
+ nonlocal factory
21
+
22
+ with thread_lock:
23
+ value = factory()
24
+ self.is_set = True
25
+ del factory
26
+
27
+ while True:
28
+ yield value
29
+
30
+ self.is_set = False
31
+ self.__generator = generator()
22
32
 
23
33
  def __invert__(self) -> _T:
24
- if not self.is_set:
25
- with _thread_lock:
26
- self.__value = self.__factory()
27
- self.__factory = _sentinel
34
+ return next(self.__generator)
28
35
 
29
- return self.__value
36
+ def __call__(self) -> _T:
37
+ return ~self
30
38
 
31
39
  def __setattr__(self, name: str, value: Any, /):
32
- if self.is_set:
33
- raise TypeError(f"`{self}` is frozen.")
40
+ with suppress(AttributeError):
41
+ if self.is_set:
42
+ raise TypeError(f"`{self}` is frozen.")
34
43
 
35
44
  return super().__setattr__(name, value)
36
45
 
37
- @property
38
- def is_set(self) -> bool:
39
- try:
40
- return self.__factory is _sentinel
41
- except AttributeError:
42
- return False
43
-
44
46
 
45
47
  class LazyMapping(Mapping[_K, _V]):
46
48
  __slots__ = ("__lazy",)
47
49
 
48
- __lazy: Lazy[MappingProxyType[_K, _V]]
49
-
50
50
  def __init__(self, iterator: Iterator[tuple[_K, _V]]):
51
51
  self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
52
52
 
@@ -58,3 +58,7 @@ class LazyMapping(Mapping[_K, _V]):
58
58
 
59
59
  def __len__(self) -> int:
60
60
  return len(~self.__lazy)
61
+
62
+ @property
63
+ def is_set(self) -> bool:
64
+ return self.__lazy.is_set
@@ -1 +0,0 @@
1
- from ._type import *
@@ -0,0 +1,28 @@
1
+ from collections.abc import Callable, Collection, Iterator
2
+ from functools import wraps
3
+ from threading import RLock
4
+ from typing import Any, TypeVar
5
+
6
+ __all__ = ("frozen_collection", "synchronized", "thread_lock")
7
+
8
+ _T = TypeVar("_T")
9
+ thread_lock = RLock()
10
+
11
+
12
+ def synchronized(function: Callable[..., Any] = None, /):
13
+ def decorator(fn):
14
+ @wraps(fn)
15
+ def wrapper(*args, **kwargs):
16
+ with thread_lock:
17
+ return fn(*args, **kwargs)
18
+
19
+ return wrapper
20
+
21
+ return decorator(function) if function else decorator
22
+
23
+
24
+ def frozen_collection(collection: Collection[_T]) -> Iterator[_T]:
25
+ with thread_lock:
26
+ t = tuple(collection)
27
+
28
+ yield from t
injection/core/module.py CHANGED
@@ -11,16 +11,22 @@ from collections.abc import (
11
11
  Iterator,
12
12
  Mapping,
13
13
  MutableMapping,
14
+ Set,
14
15
  )
15
16
  from contextlib import ContextDecorator, contextmanager, suppress
16
17
  from dataclasses import dataclass, field
17
18
  from enum import Enum, auto
18
- from functools import partialmethod, singledispatchmethod, wraps
19
+ from functools import (
20
+ partialmethod,
21
+ singledispatchmethod,
22
+ update_wrapper,
23
+ wraps,
24
+ )
19
25
  from inspect import Signature, isclass
20
- from threading import RLock
21
- from types import MappingProxyType, UnionType
26
+ from types import UnionType
22
27
  from typing import (
23
28
  Any,
29
+ ClassVar,
24
30
  ContextManager,
25
31
  NamedTuple,
26
32
  NoReturn,
@@ -32,7 +38,12 @@ from typing import (
32
38
 
33
39
  from injection.common.event import Event, EventChannel, EventListener
34
40
  from injection.common.lazy import Lazy, LazyMapping
35
- from injection.common.tools import find_types, format_type, get_origins
41
+ from injection.common.tools.threading import (
42
+ frozen_collection,
43
+ synchronized,
44
+ thread_lock,
45
+ )
46
+ from injection.common.tools.type import find_types, format_type, get_origins
36
47
  from injection.exceptions import (
37
48
  InjectionError,
38
49
  ModuleError,
@@ -41,10 +52,9 @@ from injection.exceptions import (
41
52
  NoInjectable,
42
53
  )
43
54
 
44
- __all__ = ("Injectable", "Module", "ModulePriorities")
55
+ __all__ = ("Injectable", "Module", "ModulePriority")
45
56
 
46
57
  _logger = logging.getLogger(__name__)
47
- _thread_lock = RLock()
48
58
 
49
59
  _T = TypeVar("_T")
50
60
  Types = Iterable[type] | UnionType
@@ -117,7 +127,7 @@ class ModuleRemoved(ModuleEvent):
117
127
  @dataclass(frozen=True, slots=True)
118
128
  class ModulePriorityUpdated(ModuleEvent):
119
129
  module_updated: Module
120
- priority: ModulePriorities
130
+ priority: ModulePriority
121
131
 
122
132
  def __str__(self) -> str:
123
133
  return (
@@ -135,13 +145,15 @@ Injectables
135
145
  class Injectable(Protocol[_T]):
136
146
  __slots__ = ()
137
147
 
138
- def __init__(self, __factory: Callable[[], _T] = ..., /): ...
148
+ def __init__(self, __factory: Callable[[], _T] = None, /):
149
+ pass
139
150
 
140
151
  @property
141
152
  def is_locked(self) -> bool:
142
153
  return False
143
154
 
144
- def unlock(self): ...
155
+ def unlock(self):
156
+ pass
145
157
 
146
158
  @abstractmethod
147
159
  def get_instance(self) -> _T:
@@ -163,7 +175,7 @@ class NewInjectable(BaseInjectable[_T]):
163
175
  class SingletonInjectable(BaseInjectable[_T]):
164
176
  __slots__ = ("__dict__",)
165
177
 
166
- __INSTANCE_KEY = "$instance"
178
+ __INSTANCE_KEY: ClassVar[str] = "$instance"
167
179
 
168
180
  @property
169
181
  def cache(self) -> MutableMapping[str, Any]:
@@ -180,38 +192,22 @@ class SingletonInjectable(BaseInjectable[_T]):
180
192
  with suppress(KeyError):
181
193
  return self.cache[self.__INSTANCE_KEY]
182
194
 
183
- with _thread_lock:
195
+ with thread_lock:
184
196
  instance = self.factory()
185
197
  self.cache[self.__INSTANCE_KEY] = instance
186
198
 
187
199
  return instance
188
200
 
189
201
 
190
- class InjectableWarning(BaseInjectable[_T], ABC):
191
- __slots__ = ()
202
+ @dataclass(repr=False, frozen=True, slots=True)
203
+ class ShouldBeInjectable(Injectable[_T]):
204
+ cls: type[_T]
192
205
 
193
206
  def __bool__(self) -> bool:
194
207
  return False
195
208
 
196
- @property
197
- def formatted_type(self) -> str:
198
- return format_type(self.factory)
199
-
200
- @property
201
- @abstractmethod
202
- def exception(self) -> BaseException:
203
- raise NotImplementedError
204
-
205
209
  def get_instance(self) -> NoReturn:
206
- raise self.exception
207
-
208
-
209
- class ShouldBeInjectable(InjectableWarning[_T]):
210
- __slots__ = ()
211
-
212
- @property
213
- def exception(self) -> BaseException:
214
- return InjectionError(f"`{self.formatted_type}` should be an injectable.")
210
+ raise InjectionError(f"`{format_type(self.cls)}` should be an injectable.")
215
211
 
216
212
 
217
213
  """
@@ -265,26 +261,34 @@ class Container(Broker):
265
261
  def is_locked(self) -> bool:
266
262
  return any(injectable.is_locked for injectable in self.__injectables)
267
263
 
264
+ @property
265
+ def __classes(self) -> frozenset[type]:
266
+ return frozenset(self.__data.keys())
267
+
268
268
  @property
269
269
  def __injectables(self) -> frozenset[Injectable]:
270
270
  return frozenset(self.__data.values())
271
271
 
272
- def update(self, classes: Types, injectable: Injectable, override: bool):
273
- values = MappingProxyType(
274
- {origin: injectable for origin in get_origins(*classes)}
275
- )
272
+ def update(self, classes: Iterable[type], injectable: Injectable, override: bool):
273
+ classes = frozenset(get_origins(*classes))
276
274
 
277
- if values:
278
- event = ContainerDependenciesUpdated(self, values, override)
275
+ with thread_lock:
276
+ if not injectable:
277
+ classes -= self.__classes
278
+ override = True
279
279
 
280
- with self.notify(event):
281
- if not override:
282
- self.__check_if_exists(*values)
280
+ if classes:
281
+ event = ContainerDependenciesUpdated(self, classes, override)
282
+
283
+ with self.notify(event):
284
+ if not override:
285
+ self.__check_if_exists(classes)
283
286
 
284
- self.__data.update(values)
287
+ self.__data.update((cls, injectable) for cls in classes)
285
288
 
286
289
  return self
287
290
 
291
+ @synchronized
288
292
  def unlock(self):
289
293
  for injectable in self.__injectables:
290
294
  injectable.unlock()
@@ -296,9 +300,11 @@ class Container(Broker):
296
300
  def notify(self, event: Event) -> ContextManager | ContextDecorator:
297
301
  return self.__channel.dispatch(event)
298
302
 
299
- def __check_if_exists(self, *classes: type):
300
- for cls in classes:
301
- if self.__data.get(cls):
303
+ def __check_if_exists(self, classes: Set[type]):
304
+ intersection = classes & self.__classes
305
+
306
+ for cls in intersection:
307
+ if self.__data[cls]:
302
308
  raise RuntimeError(
303
309
  f"An injectable already exists for the class `{format_type(cls)}`."
304
310
  )
@@ -309,7 +315,7 @@ Module
309
315
  """
310
316
 
311
317
 
312
- class ModulePriorities(Enum):
318
+ class ModulePriority(Enum):
313
319
  HIGH = auto()
314
320
  LOW = auto()
315
321
 
@@ -339,7 +345,7 @@ class Module(EventListener, Broker):
339
345
  raise NoInjectable(cls)
340
346
 
341
347
  def __setitem__(self, cls: type | UnionType, injectable: Injectable, /):
342
- self.update((cls,), injectable, override=True)
348
+ self.update((cls,), injectable)
343
349
 
344
350
  def __contains__(self, cls: type | UnionType, /) -> bool:
345
351
  return any(cls in broker for broker in self.__brokers)
@@ -353,7 +359,7 @@ class Module(EventListener, Broker):
353
359
 
354
360
  @property
355
361
  def __brokers(self) -> Iterator[Broker]:
356
- yield from tuple(self.__modules)
362
+ yield from frozen_collection(self.__modules)
357
363
  yield self.__container
358
364
 
359
365
  def injectable(
@@ -376,11 +382,13 @@ class Module(EventListener, Broker):
376
382
  return decorator(wrapped) if wrapped else decorator
377
383
 
378
384
  singleton = partialmethod(injectable, cls=SingletonInjectable)
379
- should_be_injectable = partialmethod(
380
- injectable,
381
- cls=ShouldBeInjectable,
382
- inject=False,
383
- )
385
+
386
+ def should_be_injectable(self, wrapped: type = None, /):
387
+ def decorator(wp):
388
+ self[wp] = ShouldBeInjectable(wp)
389
+ return wp
390
+
391
+ return decorator(wrapped) if wrapped else decorator
384
392
 
385
393
  def set_constant(
386
394
  self,
@@ -403,21 +411,15 @@ class Module(EventListener, Broker):
403
411
  wrapped: Callable[..., Any] = None,
404
412
  /,
405
413
  *,
406
- force: bool = False,
407
414
  return_factory: bool = False,
408
415
  ):
409
416
  def decorator(wp):
410
417
  if not return_factory and isclass(wp):
411
- wp.__init__ = self.inject(wp.__init__, force=force)
418
+ wp.__init__ = self.inject(wp.__init__)
412
419
  return wp
413
420
 
414
- lazy_binder = Lazy[Binder](lambda: self.__new_binder(wp))
415
-
416
- @wraps(wp)
417
- def wrapper(*args, **kwargs):
418
- arguments = (~lazy_binder).bind(args, kwargs, force)
419
- return wp(*arguments.args, **arguments.kwargs)
420
-
421
+ wrapper = InjectedFunction(wp).update(self)
422
+ self.add_listener(wrapper)
421
423
  return wrapper
422
424
 
423
425
  return decorator(wrapped) if wrapped else decorator
@@ -437,14 +439,19 @@ class Module(EventListener, Broker):
437
439
  def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
438
440
  return Lazy(lambda: self.get_instance(cls))
439
441
 
440
- def update(self, classes: Types, injectable: Injectable, override: bool = False):
442
+ def update(
443
+ self,
444
+ classes: Iterable[type],
445
+ injectable: Injectable,
446
+ override: bool = False,
447
+ ):
441
448
  self.__container.update(classes, injectable, override)
442
449
  return self
443
450
 
444
451
  def use(
445
452
  self,
446
453
  module: Module,
447
- priority: ModulePriorities = ModulePriorities.get_default(),
454
+ priority: ModulePriority = ModulePriority.get_default(),
448
455
  ):
449
456
  if module is self:
450
457
  raise ModuleError("Module can't be used by itself.")
@@ -475,13 +482,13 @@ class Module(EventListener, Broker):
475
482
  def use_temporarily(
476
483
  self,
477
484
  module: Module,
478
- priority: ModulePriorities = ModulePriorities.get_default(),
485
+ priority: ModulePriority = ModulePriority.get_default(),
479
486
  ) -> ContextManager | ContextDecorator:
480
487
  self.use(module, priority)
481
488
  yield
482
489
  self.stop_using(module)
483
490
 
484
- def change_priority(self, module: Module, priority: ModulePriorities):
491
+ def change_priority(self, module: Module, priority: ModulePriority):
485
492
  event = ModulePriorityUpdated(self, module, priority)
486
493
 
487
494
  with self.notify(event):
@@ -489,6 +496,7 @@ class Module(EventListener, Broker):
489
496
 
490
497
  return self
491
498
 
499
+ @synchronized
492
500
  def unlock(self):
493
501
  for broker in self.__brokers:
494
502
  broker.unlock()
@@ -517,8 +525,8 @@ class Module(EventListener, Broker):
517
525
  if self.is_locked:
518
526
  raise ModuleLockError(f"`{self}` is locked.")
519
527
 
520
- def __move_module(self, module: Module, priority: ModulePriorities):
521
- last = priority == ModulePriorities.LOW
528
+ def __move_module(self, module: Module, priority: ModulePriority):
529
+ last = priority == ModulePriority.LOW
522
530
 
523
531
  try:
524
532
  self.__modules.move_to_end(module, last=last)
@@ -527,44 +535,45 @@ class Module(EventListener, Broker):
527
535
  f"`{module}` can't be found in the modules used by `{self}`."
528
536
  ) from exc
529
537
 
530
- def __new_binder(self, target: Callable[..., Any]) -> Binder:
531
- signature = inspect.signature(target, eval_str=True)
532
- binder = Binder(signature).update(self)
533
- self.add_listener(binder)
534
- return binder
535
-
536
538
 
537
539
  """
538
- Binder
540
+ InjectedFunction
539
541
  """
540
542
 
541
543
 
542
544
  @dataclass(repr=False, frozen=True, slots=True)
543
545
  class Dependencies:
544
- __mapping: MappingProxyType[str, Injectable]
546
+ mapping: Mapping[str, Injectable]
545
547
 
546
548
  def __bool__(self) -> bool:
547
- return bool(self.__mapping)
549
+ return bool(self.mapping)
548
550
 
549
551
  def __iter__(self) -> Iterator[tuple[str, Any]]:
550
- for name, injectable in self.__mapping.items():
552
+ for name, injectable in self.mapping.items():
551
553
  yield name, injectable.get_instance()
552
554
 
555
+ @property
556
+ def are_resolved(self) -> bool:
557
+ if isinstance(self.mapping, LazyMapping) and not self.mapping.is_set:
558
+ return False
559
+
560
+ return bool(self)
561
+
553
562
  @property
554
563
  def arguments(self) -> OrderedDict[str, Any]:
555
564
  return OrderedDict(self)
556
565
 
557
566
  @classmethod
558
567
  def from_mapping(cls, mapping: Mapping[str, Injectable]):
559
- return cls(MappingProxyType(mapping))
568
+ return cls(mapping=mapping)
560
569
 
561
570
  @classmethod
562
571
  def empty(cls):
563
572
  return cls.from_mapping({})
564
573
 
565
574
  @classmethod
566
- def resolve(cls, signature: Signature, module: Module):
567
- dependencies = LazyMapping(cls.__resolver(signature, module))
575
+ def resolve(cls, signature: Signature, module: Module, owner: type = None):
576
+ dependencies = LazyMapping(cls.__resolver(signature, module, owner))
568
577
  return cls.from_mapping(dependencies)
569
578
 
570
579
  @classmethod
@@ -572,33 +581,88 @@ class Dependencies:
572
581
  cls,
573
582
  signature: Signature,
574
583
  module: Module,
584
+ owner: type = None,
575
585
  ) -> Iterator[tuple[str, Injectable]]:
576
- for name, parameter in signature.parameters.items():
586
+ for name, annotation in cls.__get_annotations(signature, owner):
577
587
  try:
578
- injectable = module[parameter.annotation]
588
+ injectable = module[annotation]
579
589
  except KeyError:
580
590
  continue
581
591
 
582
592
  yield name, injectable
583
593
 
594
+ @staticmethod
595
+ def __get_annotations(
596
+ signature: Signature,
597
+ owner: type = None,
598
+ ) -> Iterator[tuple[str, type | Any]]:
599
+ parameters = iter(signature.parameters.items())
600
+
601
+ if owner:
602
+ name, _ = next(parameters)
603
+ yield name, owner
604
+
605
+ for name, parameter in parameters:
606
+ yield name, parameter.annotation
607
+
584
608
 
585
609
  class Arguments(NamedTuple):
586
610
  args: Iterable[Any]
587
611
  kwargs: Mapping[str, Any]
588
612
 
589
613
 
590
- class Binder(EventListener):
591
- __slots__ = ("__signature", "__dependencies")
614
+ class InjectedFunction(EventListener):
615
+ __slots__ = ("__dict__", "__wrapper", "__dependencies", "__owner")
616
+
617
+ def __init__(self, wrapped: Callable[..., Any], /):
618
+ update_wrapper(self, wrapped)
619
+ self.__signature__ = Lazy[Signature](
620
+ lambda: inspect.signature(wrapped, eval_str=True)
621
+ )
622
+
623
+ @wraps(wrapped)
624
+ def wrapper(*args, **kwargs):
625
+ args, kwargs = self.bind(args, kwargs)
626
+ return wrapped(*args, **kwargs)
592
627
 
593
- def __init__(self, signature: Signature):
594
- self.__signature = signature
628
+ self.__wrapper = wrapper
595
629
  self.__dependencies = Dependencies.empty()
630
+ self.__owner = None
631
+
632
+ def __repr__(self) -> str:
633
+ return repr(self.__wrapper)
634
+
635
+ def __str__(self) -> str:
636
+ return str(self.__wrapper)
637
+
638
+ def __call__(self, /, *args, **kwargs) -> Any:
639
+ return self.__wrapper(*args, **kwargs)
640
+
641
+ def __get__(self, instance: object | None, owner: type):
642
+ if instance is None:
643
+ return self
644
+
645
+ return self.__wrapper.__get__(instance, owner)
646
+
647
+ def __set_name__(self, owner: type, name: str):
648
+ if self.__dependencies.are_resolved:
649
+ raise TypeError(
650
+ "`__set_name__` is called after dependencies have been resolved."
651
+ )
652
+
653
+ if self.__owner:
654
+ raise TypeError("Function owner is already defined.")
655
+
656
+ self.__owner = owner
657
+
658
+ @property
659
+ def signature(self) -> Signature:
660
+ return self.__signature__()
596
661
 
597
662
  def bind(
598
663
  self,
599
664
  args: Iterable[Any] = (),
600
665
  kwargs: Mapping[str, Any] = None,
601
- force: bool = False,
602
666
  ) -> Arguments:
603
667
  if kwargs is None:
604
668
  kwargs = {}
@@ -606,22 +670,25 @@ class Binder(EventListener):
606
670
  if not self.__dependencies:
607
671
  return Arguments(args, kwargs)
608
672
 
609
- bound = self.__signature.bind_partial(*args, **kwargs)
673
+ bound = self.signature.bind_partial(*args, **kwargs)
610
674
  dependencies = self.__dependencies.arguments
611
-
612
- if force:
613
- bound.arguments |= dependencies
614
- else:
615
- bound.arguments = dependencies | bound.arguments
675
+ bound.arguments = dependencies | bound.arguments
616
676
 
617
677
  return Arguments(bound.args, bound.kwargs)
618
678
 
619
679
  def update(self, module: Module):
620
- self.__dependencies = Dependencies.resolve(self.__signature, module)
680
+ with thread_lock:
681
+ self.__dependencies = Dependencies.resolve(
682
+ self.signature,
683
+ module,
684
+ self.__owner,
685
+ )
686
+
621
687
  return self
622
688
 
623
689
  @singledispatchmethod
624
- def on_event(self, event: Event, /): ...
690
+ def on_event(self, event: Event, /):
691
+ pass
625
692
 
626
693
  @on_event.register
627
694
  @contextmanager
injection/exceptions.py CHANGED
@@ -1,4 +1,4 @@
1
- from injection.common.tools import format_type
1
+ from injection.common.tools.type import format_type
2
2
 
3
3
  __all__ = (
4
4
  "InjectionError",
@@ -10,7 +10,7 @@ __all__ = (
10
10
 
11
11
 
12
12
  class InjectionError(Exception):
13
- __slots__ = ()
13
+ pass
14
14
 
15
15
 
16
16
  class NoInjectable(KeyError, InjectionError):
@@ -26,12 +26,12 @@ class NoInjectable(KeyError, InjectionError):
26
26
 
27
27
 
28
28
  class ModuleError(InjectionError):
29
- __slots__ = ()
29
+ pass
30
30
 
31
31
 
32
32
  class ModuleLockError(ModuleError):
33
- __slots__ = ()
33
+ pass
34
34
 
35
35
 
36
36
  class ModuleNotUsedError(KeyError, ModuleError):
37
- __slots__ = ()
37
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.7.3
3
+ Version: 0.7.5
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -0,0 +1,18 @@
1
+ injection/__init__.py,sha256=9_AVJILxKIBiL_6KJSh2RRydgSHXH38uahAGD0S1-dI,20
2
+ injection/_pkg.py,sha256=nMIRLAQG6096bcR2Mz1egxe5FItBUsw8zR9yzjt1HDM,647
3
+ injection/_pkg.pyi,sha256=5IE5vbi_sSeKjcFtUT9UuCWeIIwEi1hy3qmTdZeZSMI,5171
4
+ injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ injection/common/event.py,sha256=Lq3XTi2iTK9Y51uHtmsxoqeKJsgretqQQAb00HR454c,1392
6
+ injection/common/lazy.py,sha256=BmjkcwxI-R4I81lzqF0dHmTlfBtflhGKXeBK9lJmG18,1596
7
+ injection/common/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ injection/common/tools/threading.py,sha256=doz_TQJWmo3qIaSAhFEyCmdVG7RZoXDmdyfbkYS_vgU,688
9
+ injection/common/tools/type.py,sha256=-zL0dtoVZme71Mscvav7iEWxY2-JltzNTekbWOCPSFo,1276
10
+ injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
11
+ injection/core/module.py,sha256=J0BpNFlO3MlzBM6-4TPc9nMcSFeTU8eoxbjfcZwLAxM,18084
12
+ injection/exceptions.py,sha256=nE56jW00ZB1T-Z-dvfPczPShs3CwIc7tIvdYlOXlaXA,653
13
+ injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ injection/integrations/blacksheep.py,sha256=vcLil1IccS7JtXpuVu7s2LqN5Zravfe_7xpAt5cTIU0,723
15
+ injection/utils.py,sha256=_79aiciimZpuoUTz5lojKySUMMzpkU-e7SotiHIFTI8,676
16
+ python_injection-0.7.5.dist-info/METADATA,sha256=czSlDaVt9ev1mgzLpQOnrV2wb_Cx6VC1f4js-i-ClzE,3433
17
+ python_injection-0.7.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
18
+ python_injection-0.7.5.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- injection/__init__.py,sha256=9_AVJILxKIBiL_6KJSh2RRydgSHXH38uahAGD0S1-dI,20
2
- injection/_pkg.py,sha256=VOgHhc4YXs4g0jIhXnqYllI64szC_cyO05IC2qE5BBg,651
3
- injection/_pkg.pyi,sha256=LcSXTYS7Bg7oAECClxrTq-uoW_DwMQZ99ksksBTwks4,5474
4
- injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- injection/common/event.py,sha256=uFoGRnxxkohH53JEStn4tN2Pn79HlgGExh7VkXdBwVQ,1316
6
- injection/common/lazy.py,sha256=HIefQ1z7ivgU791MDSwBmUcdST3bOv0sivSMyR2DfHc,1493
7
- injection/common/tools/__init__.py,sha256=S2y9DaQ4EaTRty9fKpBtPXA7hSjkzgM5N2jFf5jcmJU,21
8
- injection/common/tools/_type.py,sha256=-zL0dtoVZme71Mscvav7iEWxY2-JltzNTekbWOCPSFo,1276
9
- injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
10
- injection/core/module.py,sha256=Rx3Ys1WrYpTBR7QNojpyn18wGettKxxqZdRh7_egP3M,16443
11
- injection/exceptions.py,sha256=wd4OxmpneGEmlZ0yeNBfnCYfPYqUksfbmA2mopLNm_s,688
12
- injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- injection/integrations/blacksheep.py,sha256=vcLil1IccS7JtXpuVu7s2LqN5Zravfe_7xpAt5cTIU0,723
14
- injection/utils.py,sha256=_79aiciimZpuoUTz5lojKySUMMzpkU-e7SotiHIFTI8,676
15
- python_injection-0.7.3.dist-info/METADATA,sha256=Bh5D-zSRRwFxOcjWMqNg3LQvgaNr-hdTLJp82wnt0jI,3433
16
- python_injection-0.7.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
17
- python_injection-0.7.3.dist-info/RECORD,,
File without changes