python-injection 0.8.0__tar.gz → 0.8.1.post0__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.post0}/PKG-INFO +27 -28
  2. {python_injection-0.8.0 → python_injection-0.8.1.post0}/documentation/basic-usage.md +26 -27
  3. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/_pkg.pyi +9 -4
  4. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/common/event.py +1 -3
  5. python_injection-0.8.1.post0/injection/common/invertible.py +23 -0
  6. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/common/lazy.py +6 -8
  7. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/common/queue.py +7 -12
  8. python_injection-0.8.1.post0/injection/common/tools/threading.py +13 -0
  9. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/core/module.py +74 -81
  10. {python_injection-0.8.0 → python_injection-0.8.1.post0}/pyproject.toml +4 -2
  11. python_injection-0.8.0/injection/common/tools/threading.py +0 -28
  12. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/__init__.py +0 -0
  13. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/_pkg.py +0 -0
  14. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/common/__init__.py +0 -0
  15. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/common/tools/__init__.py +0 -0
  16. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/common/tools/type.py +0 -0
  17. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/core/__init__.py +0 -0
  18. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/exceptions.py +0 -0
  19. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/integrations/__init__.py +0 -0
  20. {python_injection-0.8.0 → python_injection-0.8.1.post0}/injection/integrations/blacksheep.py +0 -0
  21. {python_injection-0.8.0 → python_injection-0.8.1.post0}/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.post0
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
139
  If a class is registered in a package and you want to override it, there is the `override` 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, override=True)
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
122
  If a class is registered in a package and you want to override it, there is the `override` 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, override=True)
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
  ```
@@ -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
@@ -16,14 +16,9 @@ from collections.abc import (
16
16
  from contextlib import ContextDecorator, contextmanager, suppress
17
17
  from dataclasses import dataclass, field
18
18
  from enum import Enum, auto
19
- from functools import (
20
- partialmethod,
21
- singledispatchmethod,
22
- update_wrapper,
23
- wraps,
24
- )
19
+ from functools import partialmethod, singledispatchmethod, update_wrapper
25
20
  from inspect import Signature, isclass
26
- from types import UnionType
21
+ from types import MethodType, UnionType
27
22
  from typing import (
28
23
  Any,
29
24
  ClassVar,
@@ -37,13 +32,10 @@ from typing import (
37
32
  )
38
33
 
39
34
  from injection.common.event import Event, EventChannel, EventListener
35
+ from injection.common.invertible import Invertible, SimpleInvertible
40
36
  from injection.common.lazy import Lazy, LazyMapping
41
37
  from injection.common.queue import LimitedQueue
42
- from injection.common.tools.threading import (
43
- frozen_collection,
44
- synchronized,
45
- thread_lock,
46
- )
38
+ from injection.common.tools.threading import synchronized
47
39
  from injection.common.tools.type import find_types, format_type, get_origins
48
40
  from injection.exceptions import (
49
41
  InjectionError,
@@ -161,6 +153,13 @@ class Injectable(Protocol[_T]):
161
153
  raise NotImplementedError
162
154
 
163
155
 
156
+ class FallbackInjectable(Injectable[_T], ABC):
157
+ __slots__ = ()
158
+
159
+ def __bool__(self) -> bool:
160
+ return False
161
+
162
+
164
163
  @dataclass(repr=False, frozen=True, slots=True)
165
164
  class BaseInjectable(Injectable[_T], ABC):
166
165
  factory: Callable[[], _T]
@@ -193,7 +192,7 @@ class SingletonInjectable(BaseInjectable[_T]):
193
192
  with suppress(KeyError):
194
193
  return self.cache[self.__INSTANCE_KEY]
195
194
 
196
- with thread_lock:
195
+ with synchronized():
197
196
  instance = self.factory()
198
197
  self.cache[self.__INSTANCE_KEY] = instance
199
198
 
@@ -201,12 +200,9 @@ class SingletonInjectable(BaseInjectable[_T]):
201
200
 
202
201
 
203
202
  @dataclass(repr=False, frozen=True, slots=True)
204
- class ShouldBeInjectable(Injectable[_T]):
203
+ class ShouldBeInjectable(FallbackInjectable[_T]):
205
204
  cls: type[_T]
206
205
 
207
- def __bool__(self) -> bool:
208
- return False
209
-
210
206
  def get_instance(self) -> NoReturn:
211
207
  raise InjectionError(f"`{format_type(self.cls)}` should be an injectable.")
212
208
 
@@ -264,36 +260,38 @@ class Container(Broker):
264
260
 
265
261
  @property
266
262
  def __classes(self) -> frozenset[type]:
267
- return frozenset(self.__data.keys())
263
+ return frozenset(self.__data)
268
264
 
269
265
  @property
270
266
  def __injectables(self) -> frozenset[Injectable]:
271
267
  return frozenset(self.__data.values())
272
268
 
269
+ @synchronized()
273
270
  def update(self, classes: Iterable[type], injectable: Injectable, override: bool):
274
271
  classes = frozenset(get_origins(*classes))
275
272
 
276
- with thread_lock:
277
- if not injectable:
278
- classes -= self.__classes
279
- override = True
273
+ if not injectable:
274
+ classes -= self.__classes
275
+ override = True
280
276
 
281
- if classes:
282
- event = ContainerDependenciesUpdated(self, classes, override)
277
+ if classes:
278
+ event = ContainerDependenciesUpdated(self, classes, override)
283
279
 
284
- with self.notify(event):
285
- if not override:
286
- self.__check_if_exists(classes)
280
+ with self.notify(event):
281
+ if not override:
282
+ self.__check_if_exists(classes)
287
283
 
288
- self.__data.update((cls, injectable) for cls in classes)
284
+ self.__data.update((cls, injectable) for cls in classes)
289
285
 
290
286
  return self
291
287
 
292
- @synchronized
288
+ @synchronized()
293
289
  def unlock(self):
294
290
  for injectable in self.__injectables:
295
291
  injectable.unlock()
296
292
 
293
+ return self
294
+
297
295
  def add_listener(self, listener: EventListener):
298
296
  self.__channel.add_listener(listener)
299
297
  return self
@@ -360,7 +358,7 @@ class Module(EventListener, Broker):
360
358
 
361
359
  @property
362
360
  def __brokers(self) -> Iterator[Broker]:
363
- yield from frozen_collection(self.__modules)
361
+ yield from tuple(self.__modules)
364
362
  yield self.__container
365
363
 
366
364
  def injectable(
@@ -421,7 +419,7 @@ class Module(EventListener, Broker):
421
419
 
422
420
  function = InjectedFunction(wp)
423
421
 
424
- @function.setup
422
+ @function.on_setup
425
423
  def listen():
426
424
  function.update(self)
427
425
  self.add_listener(function)
@@ -442,8 +440,17 @@ class Module(EventListener, Broker):
442
440
  instance = injectable.get_instance()
443
441
  return cast(cls, instance)
444
442
 
445
- def get_lazy_instance(self, cls: type[_T]) -> Lazy[_T | None]:
446
- return Lazy(lambda: self.get_instance(cls))
443
+ def get_lazy_instance(
444
+ self,
445
+ cls: type[_T],
446
+ cache: bool = False,
447
+ ) -> Invertible[_T | None]:
448
+ if cache:
449
+ return Lazy(lambda: self.get_instance(cls))
450
+
451
+ function = self.inject(lambda instance=None: instance)
452
+ function.set_owner(cls)
453
+ return SimpleInvertible(function)
447
454
 
448
455
  def update(
449
456
  self,
@@ -502,11 +509,13 @@ class Module(EventListener, Broker):
502
509
 
503
510
  return self
504
511
 
505
- @synchronized
512
+ @synchronized()
506
513
  def unlock(self):
507
514
  for broker in self.__brokers:
508
515
  broker.unlock()
509
516
 
517
+ return self
518
+
510
519
  def add_listener(self, listener: EventListener):
511
520
  self.__channel.add_listener(listener)
512
521
  return self
@@ -621,64 +630,49 @@ class InjectedFunction(EventListener):
621
630
  __slots__ = (
622
631
  "__dict__",
623
632
  "__signature__",
633
+ "__wrapped__",
624
634
  "__dependencies",
625
635
  "__owner",
626
636
  "__setup_queue",
627
- "__wrapper",
628
637
  )
629
638
 
630
639
  def __init__(self, wrapped: Callable[..., Any], /):
631
640
  update_wrapper(self, wrapped)
632
-
633
- @wraps(wrapped)
634
- def wrapper(*args, **kwargs):
635
- self.__consume_setup_queue()
636
- args, kwargs = self.bind(args, kwargs)
637
- return wrapped(*args, **kwargs)
638
-
639
- self.__wrapper = wrapper
640
641
  self.__dependencies = Dependencies.empty()
641
642
  self.__owner = None
642
643
  self.__setup_queue = LimitedQueue[Callable[[], Any]]()
643
- self.setup(
644
- lambda: self.__set_signature(
645
- inspect.signature(
646
- wrapped,
647
- eval_str=True,
648
- )
649
- )
650
- )
644
+ self.on_setup(self.__set_signature)
651
645
 
652
646
  def __repr__(self) -> str:
653
- return repr(self.__wrapper)
647
+ return repr(self.wrapped)
654
648
 
655
649
  def __str__(self) -> str:
656
- return str(self.__wrapper)
650
+ return str(self.wrapped)
657
651
 
658
652
  def __call__(self, /, *args, **kwargs) -> Any:
659
- return self.__wrapper(*args, **kwargs)
653
+ for function in self.__setup_queue:
654
+ function()
655
+
656
+ args, kwargs = self.bind(args, kwargs)
657
+ return self.wrapped(*args, **kwargs)
660
658
 
661
659
  def __get__(self, instance: object = None, owner: type = None):
662
660
  if instance is None:
663
661
  return self
664
662
 
665
- return self.__wrapper.__get__(instance, owner)
663
+ return MethodType(self, instance)
666
664
 
667
665
  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
666
+ self.set_owner(owner)
677
667
 
678
668
  @property
679
669
  def signature(self) -> Signature:
680
670
  return self.__signature__
681
671
 
672
+ @property
673
+ def wrapped(self) -> Callable[..., Any]:
674
+ return self.__wrapped__
675
+
682
676
  def bind(
683
677
  self,
684
678
  args: Iterable[Any] = (),
@@ -696,17 +690,24 @@ class InjectedFunction(EventListener):
696
690
  )
697
691
  return Arguments(bound.args, bound.kwargs)
698
692
 
699
- def update(self, module: Module):
700
- with thread_lock:
701
- self.__dependencies = Dependencies.resolve(
702
- self.signature,
703
- module,
704
- self.__owner,
693
+ def set_owner(self, owner: type):
694
+ if self.__dependencies.are_resolved:
695
+ raise TypeError(
696
+ "Function owner must be assigned before dependencies are resolved."
705
697
  )
706
698
 
699
+ if self.__owner:
700
+ raise TypeError("Function owner is already defined.")
701
+
702
+ self.__owner = owner
703
+ return self
704
+
705
+ @synchronized()
706
+ def update(self, module: Module):
707
+ self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
707
708
  return self
708
709
 
709
- def setup(self, wrapped: Callable[[], Any] = None, /):
710
+ def on_setup(self, wrapped: Callable[[], Any] = None, /):
710
711
  def decorator(wp):
711
712
  self.__setup_queue.add(wp)
712
713
  return wp
@@ -723,14 +724,6 @@ class InjectedFunction(EventListener):
723
724
  yield
724
725
  self.update(event.on_module)
725
726
 
726
- def __consume_setup_queue(self):
727
- for function in self.__setup_queue:
728
- function()
729
-
730
- return self
731
-
732
- def __set_signature(self, signature: Signature):
733
- with thread_lock:
734
- self.__signature__ = signature
735
-
727
+ def __set_signature(self):
728
+ self.__signature__ = inspect.signature(self.wrapped, eval_str=True)
736
729
  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.post0"
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,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