python-injection 0.8.0__tar.gz → 0.8.1__tar.gz

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

Potentially problematic release.


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

Files changed (21) hide show
  1. {python_injection-0.8.0 → python_injection-0.8.1}/PKG-INFO +1 -1
  2. {python_injection-0.8.0 → python_injection-0.8.1}/injection/_pkg.pyi +9 -4
  3. {python_injection-0.8.0 → python_injection-0.8.1}/injection/common/event.py +1 -3
  4. python_injection-0.8.1/injection/common/invertible.py +23 -0
  5. {python_injection-0.8.0 → python_injection-0.8.1}/injection/common/lazy.py +6 -8
  6. {python_injection-0.8.0 → python_injection-0.8.1}/injection/common/queue.py +7 -12
  7. python_injection-0.8.1/injection/common/tools/threading.py +13 -0
  8. {python_injection-0.8.0 → python_injection-0.8.1}/injection/core/module.py +58 -47
  9. {python_injection-0.8.0 → python_injection-0.8.1}/pyproject.toml +1 -1
  10. python_injection-0.8.0/injection/common/tools/threading.py +0 -28
  11. {python_injection-0.8.0 → python_injection-0.8.1}/documentation/basic-usage.md +0 -0
  12. {python_injection-0.8.0 → python_injection-0.8.1}/injection/__init__.py +0 -0
  13. {python_injection-0.8.0 → python_injection-0.8.1}/injection/_pkg.py +0 -0
  14. {python_injection-0.8.0 → python_injection-0.8.1}/injection/common/__init__.py +0 -0
  15. {python_injection-0.8.0 → python_injection-0.8.1}/injection/common/tools/__init__.py +0 -0
  16. {python_injection-0.8.0 → python_injection-0.8.1}/injection/common/tools/type.py +0 -0
  17. {python_injection-0.8.0 → python_injection-0.8.1}/injection/core/__init__.py +0 -0
  18. {python_injection-0.8.0 → python_injection-0.8.1}/injection/exceptions.py +0 -0
  19. {python_injection-0.8.0 → python_injection-0.8.1}/injection/integrations/__init__.py +0 -0
  20. {python_injection-0.8.0 → python_injection-0.8.1}/injection/integrations/blacksheep.py +0 -0
  21. {python_injection-0.8.0 → python_injection-0.8.1}/injection/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -13,7 +13,7 @@ from typing import (
13
13
  runtime_checkable,
14
14
  )
15
15
 
16
- from injection.common.lazy import Lazy
16
+ from injection.common.invertible import Invertible
17
17
 
18
18
  _T = TypeVar("_T")
19
19
 
@@ -104,11 +104,16 @@ class Module:
104
104
  will be raised.
105
105
  """
106
106
 
107
- def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
107
+ def get_lazy_instance(
108
+ self,
109
+ cls: type[_T],
110
+ cache: bool = ...,
111
+ ) -> Invertible[_T | None]:
108
112
  """
109
113
  Function used to retrieve an instance associated with the type passed in
110
- parameter or `None`. Return a `Lazy` object. To access the instance contained
111
- in a lazy object, simply use a wavy line (~).
114
+ parameter or `None`. Return a `Invertible` object. To access the instance
115
+ contained in an invertible object, simply use a wavy line (~).
116
+ With `cache=True`, the instance retrieved will always be the same.
112
117
 
113
118
  Example: instance = ~lazy_instance
114
119
  """
@@ -4,8 +4,6 @@ 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
-
9
7
  __all__ = ("Event", "EventChannel", "EventListener")
10
8
 
11
9
 
@@ -28,7 +26,7 @@ class EventChannel:
28
26
  @contextmanager
29
27
  def dispatch(self, event: Event) -> ContextManager | ContextDecorator:
30
28
  with ExitStack() as stack:
31
- for listener in frozen_collection(self.__listeners):
29
+ for listener in tuple(self.__listeners):
32
30
  context_manager = listener.on_event(event)
33
31
 
34
32
  if context_manager is None:
@@ -0,0 +1,23 @@
1
+ from abc import abstractmethod
2
+ from collections.abc import Callable
3
+ from dataclasses import dataclass
4
+ from typing import Protocol, TypeVar, runtime_checkable
5
+
6
+ __all__ = ("Invertible", "SimpleInvertible")
7
+
8
+ _T = TypeVar("_T")
9
+
10
+
11
+ @runtime_checkable
12
+ class Invertible(Protocol[_T]):
13
+ @abstractmethod
14
+ def __invert__(self) -> _T:
15
+ raise NotImplementedError
16
+
17
+
18
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
19
+ class SimpleInvertible(Invertible[_T]):
20
+ callable: Callable[[], _T]
21
+
22
+ def __invert__(self) -> _T:
23
+ return self.callable()
@@ -1,8 +1,8 @@
1
1
  from collections.abc import Callable, Iterator, Mapping
2
2
  from types import MappingProxyType
3
- from typing import Generic, TypeVar
3
+ from typing import TypeVar
4
4
 
5
- from injection.common.tools.threading import thread_lock
5
+ from injection.common.invertible import Invertible
6
6
 
7
7
  __all__ = ("Lazy", "LazyMapping")
8
8
 
@@ -11,7 +11,7 @@ _K = TypeVar("_K")
11
11
  _V = TypeVar("_V")
12
12
 
13
13
 
14
- class Lazy(Generic[_T]):
14
+ class Lazy(Invertible[_T]):
15
15
  __slots__ = ("__cache", "__is_set")
16
16
 
17
17
  def __init__(self, factory: Callable[[], _T]):
@@ -25,18 +25,16 @@ class Lazy(Generic[_T]):
25
25
  return self.__is_set
26
26
 
27
27
  def __setup_cache(self, factory: Callable[[], _T]):
28
- def new_cache() -> Iterator[_T]:
29
- with thread_lock:
30
- self.__is_set = True
31
-
28
+ def cache_generator() -> Iterator[_T]:
32
29
  nonlocal factory
33
30
  cached = factory()
31
+ self.__is_set = True
34
32
  del factory
35
33
 
36
34
  while True:
37
35
  yield cached
38
36
 
39
- self.__cache = new_cache()
37
+ self.__cache = cache_generator()
40
38
  self.__is_set = False
41
39
 
42
40
 
@@ -4,8 +4,6 @@ from collections.abc import Iterator
4
4
  from dataclasses import dataclass, field
5
5
  from typing import NoReturn, Protocol, TypeVar
6
6
 
7
- from injection.common.tools.threading import thread_lock
8
-
9
7
  __all__ = ("LimitedQueue",)
10
8
 
11
9
  _T = TypeVar("_T")
@@ -34,7 +32,7 @@ class SimpleQueue(Queue[_T]):
34
32
  return self
35
33
 
36
34
 
37
- class NoQueue(Queue[_T]):
35
+ class DeadQueue(Queue[_T]):
38
36
  __slots__ = ()
39
37
 
40
38
  def __bool__(self) -> bool:
@@ -44,25 +42,22 @@ class NoQueue(Queue[_T]):
44
42
  raise StopIteration
45
43
 
46
44
  def add(self, item: _T) -> NoReturn:
47
- raise TypeError("Queue doesn't exist.")
45
+ raise TypeError("Queue is dead.")
48
46
 
49
47
 
50
48
  @dataclass(repr=False, slots=True)
51
49
  class LimitedQueue(Queue[_T]):
52
- __queue: Queue[_T] = field(default_factory=SimpleQueue)
50
+ __state: Queue[_T] = field(default_factory=SimpleQueue)
53
51
 
54
52
  def __next__(self) -> _T:
55
- if not self.__queue:
56
- raise StopIteration
57
-
58
53
  try:
59
- return next(self.__queue)
54
+ return next(self.__state)
60
55
  except StopIteration as exc:
61
- with thread_lock:
62
- self.__queue = NoQueue()
56
+ if self.__state:
57
+ self.__state = DeadQueue()
63
58
 
64
59
  raise exc
65
60
 
66
61
  def add(self, item: _T):
67
- self.__queue.add(item)
62
+ self.__state.add(item)
68
63
  return self
@@ -0,0 +1,13 @@
1
+ from contextlib import ContextDecorator, contextmanager
2
+ from threading import RLock
3
+ from typing import ContextManager
4
+
5
+ __all__ = ("synchronized",)
6
+
7
+ __lock = RLock()
8
+
9
+
10
+ @contextmanager
11
+ def synchronized() -> ContextManager | ContextDecorator:
12
+ with __lock:
13
+ yield
@@ -37,13 +37,10 @@ from typing import (
37
37
  )
38
38
 
39
39
  from injection.common.event import Event, EventChannel, EventListener
40
+ from injection.common.invertible import Invertible, SimpleInvertible
40
41
  from injection.common.lazy import Lazy, LazyMapping
41
42
  from injection.common.queue import LimitedQueue
42
- from injection.common.tools.threading import (
43
- frozen_collection,
44
- synchronized,
45
- thread_lock,
46
- )
43
+ from injection.common.tools.threading import synchronized
47
44
  from injection.common.tools.type import find_types, format_type, get_origins
48
45
  from injection.exceptions import (
49
46
  InjectionError,
@@ -161,6 +158,13 @@ class Injectable(Protocol[_T]):
161
158
  raise NotImplementedError
162
159
 
163
160
 
161
+ class FallbackInjectable(Injectable[_T], ABC):
162
+ __slots__ = ()
163
+
164
+ def __bool__(self) -> bool:
165
+ return False
166
+
167
+
164
168
  @dataclass(repr=False, frozen=True, slots=True)
165
169
  class BaseInjectable(Injectable[_T], ABC):
166
170
  factory: Callable[[], _T]
@@ -193,7 +197,7 @@ class SingletonInjectable(BaseInjectable[_T]):
193
197
  with suppress(KeyError):
194
198
  return self.cache[self.__INSTANCE_KEY]
195
199
 
196
- with thread_lock:
200
+ with synchronized():
197
201
  instance = self.factory()
198
202
  self.cache[self.__INSTANCE_KEY] = instance
199
203
 
@@ -201,12 +205,9 @@ class SingletonInjectable(BaseInjectable[_T]):
201
205
 
202
206
 
203
207
  @dataclass(repr=False, frozen=True, slots=True)
204
- class ShouldBeInjectable(Injectable[_T]):
208
+ class ShouldBeInjectable(FallbackInjectable[_T]):
205
209
  cls: type[_T]
206
210
 
207
- def __bool__(self) -> bool:
208
- return False
209
-
210
211
  def get_instance(self) -> NoReturn:
211
212
  raise InjectionError(f"`{format_type(self.cls)}` should be an injectable.")
212
213
 
@@ -264,36 +265,38 @@ class Container(Broker):
264
265
 
265
266
  @property
266
267
  def __classes(self) -> frozenset[type]:
267
- return frozenset(self.__data.keys())
268
+ return frozenset(self.__data)
268
269
 
269
270
  @property
270
271
  def __injectables(self) -> frozenset[Injectable]:
271
272
  return frozenset(self.__data.values())
272
273
 
274
+ @synchronized()
273
275
  def update(self, classes: Iterable[type], injectable: Injectable, override: bool):
274
276
  classes = frozenset(get_origins(*classes))
275
277
 
276
- with thread_lock:
277
- if not injectable:
278
- classes -= self.__classes
279
- override = True
278
+ if not injectable:
279
+ classes -= self.__classes
280
+ override = True
280
281
 
281
- if classes:
282
- event = ContainerDependenciesUpdated(self, classes, override)
282
+ if classes:
283
+ event = ContainerDependenciesUpdated(self, classes, override)
283
284
 
284
- with self.notify(event):
285
- if not override:
286
- self.__check_if_exists(classes)
285
+ with self.notify(event):
286
+ if not override:
287
+ self.__check_if_exists(classes)
287
288
 
288
- self.__data.update((cls, injectable) for cls in classes)
289
+ self.__data.update((cls, injectable) for cls in classes)
289
290
 
290
291
  return self
291
292
 
292
- @synchronized
293
+ @synchronized()
293
294
  def unlock(self):
294
295
  for injectable in self.__injectables:
295
296
  injectable.unlock()
296
297
 
298
+ return self
299
+
297
300
  def add_listener(self, listener: EventListener):
298
301
  self.__channel.add_listener(listener)
299
302
  return self
@@ -360,7 +363,7 @@ class Module(EventListener, Broker):
360
363
 
361
364
  @property
362
365
  def __brokers(self) -> Iterator[Broker]:
363
- yield from frozen_collection(self.__modules)
366
+ yield from tuple(self.__modules)
364
367
  yield self.__container
365
368
 
366
369
  def injectable(
@@ -421,7 +424,7 @@ class Module(EventListener, Broker):
421
424
 
422
425
  function = InjectedFunction(wp)
423
426
 
424
- @function.setup
427
+ @function.on_setup
425
428
  def listen():
426
429
  function.update(self)
427
430
  self.add_listener(function)
@@ -442,8 +445,17 @@ class Module(EventListener, Broker):
442
445
  instance = injectable.get_instance()
443
446
  return cast(cls, instance)
444
447
 
445
- def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
446
- return Lazy(lambda: self.get_instance(cls))
448
+ def get_lazy_instance(
449
+ self,
450
+ cls: type[_T],
451
+ cache: bool = False,
452
+ ) -> Invertible[_T | None]:
453
+ if cache:
454
+ return Lazy(lambda: self.get_instance(cls))
455
+
456
+ function = self.inject(lambda instance=None: instance)
457
+ function.set_owner(cls)
458
+ return SimpleInvertible(function)
447
459
 
448
460
  def update(
449
461
  self,
@@ -502,11 +514,13 @@ class Module(EventListener, Broker):
502
514
 
503
515
  return self
504
516
 
505
- @synchronized
517
+ @synchronized()
506
518
  def unlock(self):
507
519
  for broker in self.__brokers:
508
520
  broker.unlock()
509
521
 
522
+ return self
523
+
510
524
  def add_listener(self, listener: EventListener):
511
525
  self.__channel.add_listener(listener)
512
526
  return self
@@ -640,7 +654,7 @@ class InjectedFunction(EventListener):
640
654
  self.__dependencies = Dependencies.empty()
641
655
  self.__owner = None
642
656
  self.__setup_queue = LimitedQueue[Callable[[], Any]]()
643
- self.setup(
657
+ self.on_setup(
644
658
  lambda: self.__set_signature(
645
659
  inspect.signature(
646
660
  wrapped,
@@ -665,15 +679,7 @@ class InjectedFunction(EventListener):
665
679
  return self.__wrapper.__get__(instance, owner)
666
680
 
667
681
  def __set_name__(self, owner: type, name: str):
668
- if self.__dependencies.are_resolved:
669
- raise TypeError(
670
- "Function owner must be assigned before dependencies are resolved."
671
- )
672
-
673
- if self.__owner:
674
- raise TypeError("Function owner is already defined.")
675
-
676
- self.__owner = owner
682
+ self.set_owner(owner)
677
683
 
678
684
  @property
679
685
  def signature(self) -> Signature:
@@ -696,17 +702,24 @@ class InjectedFunction(EventListener):
696
702
  )
697
703
  return Arguments(bound.args, bound.kwargs)
698
704
 
699
- def update(self, module: Module):
700
- with thread_lock:
701
- self.__dependencies = Dependencies.resolve(
702
- self.signature,
703
- module,
704
- self.__owner,
705
+ def set_owner(self, owner: type):
706
+ if self.__dependencies.are_resolved:
707
+ raise TypeError(
708
+ "Function owner must be assigned before dependencies are resolved."
705
709
  )
706
710
 
711
+ if self.__owner:
712
+ raise TypeError("Function owner is already defined.")
713
+
714
+ self.__owner = owner
707
715
  return self
708
716
 
709
- def setup(self, wrapped: Callable[[], Any] = None, /):
717
+ @synchronized()
718
+ def update(self, module: Module):
719
+ self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
720
+ return self
721
+
722
+ def on_setup(self, wrapped: Callable[[], Any] = None, /):
710
723
  def decorator(wp):
711
724
  self.__setup_queue.add(wp)
712
725
  return wp
@@ -730,7 +743,5 @@ class InjectedFunction(EventListener):
730
743
  return self
731
744
 
732
745
  def __set_signature(self, signature: Signature):
733
- with thread_lock:
734
- self.__signature__ = signature
735
-
746
+ self.__signature__ = signature
736
747
  return self
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-injection"
3
- version = "0.8.0"
3
+ version = "0.8.1"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  authors = ["remimd"]
6
6
  keywords = ["dependencies", "inject", "injection"]
@@ -1,28 +0,0 @@
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