python-injection 0.10.12.post0__py3-none-any.whl → 0.12.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
injection/__init__.py CHANGED
@@ -1,10 +1,16 @@
1
- from ._core.module import Injectable, Mode, Module, Priority
1
+ from ._core.descriptors import LazyInstance
2
+ from ._core.injectables import Injectable
3
+ from ._core.module import Mode, Module, Priority, mod
2
4
 
3
5
  __all__ = (
4
6
  "Injectable",
7
+ "LazyInstance",
5
8
  "Mode",
6
9
  "Module",
7
10
  "Priority",
11
+ "afind_instance",
12
+ "aget_instance",
13
+ "aget_lazy_instance",
8
14
  "constant",
9
15
  "find_instance",
10
16
  "get_instance",
@@ -17,14 +23,9 @@ __all__ = (
17
23
  "singleton",
18
24
  )
19
25
 
20
-
21
- def mod(name: str | None = None, /) -> Module:
22
- if name is None:
23
- return Module.default()
24
-
25
- return Module.from_name(name)
26
-
27
-
26
+ afind_instance = mod().afind_instance
27
+ aget_instance = mod().aget_instance
28
+ aget_lazy_instance = mod().aget_lazy_instance
28
29
  constant = mod().constant
29
30
  find_instance = mod().find_instance
30
31
  get_instance = mod().get_instance
injection/__init__.pyi CHANGED
@@ -1,5 +1,5 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import Callable
2
+ from collections.abc import Awaitable, Callable
3
3
  from contextlib import ContextDecorator
4
4
  from enum import Enum
5
5
  from logging import Logger
@@ -9,6 +9,7 @@ from typing import (
9
9
  Protocol,
10
10
  Self,
11
11
  final,
12
+ overload,
12
13
  runtime_checkable,
13
14
  )
14
15
 
@@ -20,6 +21,9 @@ from ._core.module import ModeStr, PriorityStr
20
21
 
21
22
  _: Module = ...
22
23
 
24
+ afind_instance = _.afind_instance
25
+ aget_instance = _.aget_instance
26
+ aget_lazy_instance = _.aget_lazy_instance
23
27
  constant = _.constant
24
28
  find_instance = _.find_instance
25
29
  get_instance = _.get_instance
@@ -52,59 +56,59 @@ class Module:
52
56
  def __contains__(self, cls: _InputType[Any], /) -> bool: ...
53
57
  @property
54
58
  def is_locked(self) -> bool: ...
55
- def inject[**P, T](self, wrapped: Callable[P, T] = ..., /): # type: ignore[no-untyped-def]
59
+ def inject[**P, T](self, wrapped: Callable[P, T] = ..., /) -> Any:
56
60
  """
57
61
  Decorator applicable to a class or function. Inject function dependencies using
58
62
  parameter type annotations. If applied to a class, the dependencies resolved
59
63
  will be those of the `__init__` method.
60
64
  """
61
65
 
62
- def injectable[**P, T]( # type: ignore[no-untyped-def]
66
+ def injectable[**P, T](
63
67
  self,
64
- wrapped: Callable[P, T] = ...,
68
+ wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
65
69
  /,
66
70
  *,
67
71
  cls: _InjectableFactory[T] = ...,
68
72
  inject: bool = ...,
69
73
  on: _TypeInfo[T] = ...,
70
74
  mode: Mode | ModeStr = ...,
71
- ):
75
+ ) -> Any:
72
76
  """
73
77
  Decorator applicable to a class or function. It is used to indicate how the
74
78
  injectable will be constructed. At injection time, a new instance will be
75
79
  injected each time.
76
80
  """
77
81
 
78
- def singleton[**P, T]( # type: ignore[no-untyped-def]
82
+ def singleton[**P, T](
79
83
  self,
80
- wrapped: Callable[P, T] = ...,
84
+ wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
81
85
  /,
82
86
  *,
83
87
  inject: bool = ...,
84
88
  on: _TypeInfo[T] = ...,
85
89
  mode: Mode | ModeStr = ...,
86
- ):
90
+ ) -> Any:
87
91
  """
88
92
  Decorator applicable to a class or function. It is used to indicate how the
89
93
  singleton will be constructed. At injection time, the injected instance will
90
94
  always be the same.
91
95
  """
92
96
 
93
- def should_be_injectable[T](self, wrapped: type[T] = ..., /): # type: ignore[no-untyped-def]
97
+ def should_be_injectable[T](self, wrapped: type[T] = ..., /) -> Any:
94
98
  """
95
99
  Decorator applicable to a class. It is used to specify whether an injectable
96
100
  should be registered. Raise an exception at injection time if the class isn't
97
101
  registered.
98
102
  """
99
103
 
100
- def constant[T]( # type: ignore[no-untyped-def]
104
+ def constant[T](
101
105
  self,
102
106
  wrapped: type[T] = ...,
103
107
  /,
104
108
  *,
105
109
  on: _TypeInfo[T] = ...,
106
110
  mode: Mode | ModeStr = ...,
107
- ):
111
+ ) -> Any:
108
112
  """
109
113
  Decorator applicable to a class or function. It is used to indicate how the
110
114
  constant is constructed. At injection time, the injected instance will always
@@ -130,29 +134,66 @@ class Module:
130
134
  wrapped: Callable[P, T],
131
135
  /,
132
136
  ) -> Callable[P, T]: ...
137
+ async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
133
138
  def find_instance[T](self, cls: _InputType[T]) -> T:
134
139
  """
135
140
  Function used to retrieve an instance associated with the type passed in
136
141
  parameter or an exception will be raised.
137
142
  """
138
143
 
144
+ @overload
145
+ async def aget_instance[T, Default](
146
+ self,
147
+ cls: _InputType[T],
148
+ default: Default,
149
+ ) -> T | Default: ...
150
+ @overload
151
+ async def aget_instance[T](
152
+ self,
153
+ cls: _InputType[T],
154
+ default: None = ...,
155
+ ) -> T | None: ...
156
+ @overload
139
157
  def get_instance[T, Default](
140
158
  self,
141
159
  cls: _InputType[T],
142
- default: Default | None = ...,
143
- ) -> T | Default | None:
160
+ default: Default,
161
+ ) -> T | Default:
144
162
  """
145
163
  Function used to retrieve an instance associated with the type passed in
146
164
  parameter or return `None`.
147
165
  """
148
166
 
167
+ @overload
168
+ def get_instance[T](
169
+ self,
170
+ cls: _InputType[T],
171
+ default: None = ...,
172
+ ) -> T | None: ...
173
+ @overload
174
+ def aget_lazy_instance[T, Default](
175
+ self,
176
+ cls: _InputType[T],
177
+ default: Default,
178
+ *,
179
+ cache: bool = ...,
180
+ ) -> Awaitable[T | Default]: ...
181
+ @overload
182
+ def aget_lazy_instance[T](
183
+ self,
184
+ cls: _InputType[T],
185
+ default: None = ...,
186
+ *,
187
+ cache: bool = ...,
188
+ ) -> Awaitable[T | None]: ...
189
+ @overload
149
190
  def get_lazy_instance[T, Default](
150
191
  self,
151
192
  cls: _InputType[T],
152
- default: Default | None = ...,
193
+ default: Default,
153
194
  *,
154
195
  cache: bool = ...,
155
- ) -> _Invertible[T | Default | None]:
196
+ ) -> _Invertible[T | Default]:
156
197
  """
157
198
  Function used to retrieve an instance associated with the type passed in
158
199
  parameter or `None`. Return a `Invertible` object. To access the instance
@@ -162,6 +203,14 @@ class Module:
162
203
  Example: instance = ~lazy_instance
163
204
  """
164
205
 
206
+ @overload
207
+ def get_lazy_instance[T](
208
+ self,
209
+ cls: _InputType[T],
210
+ default: None = ...,
211
+ *,
212
+ cache: bool = ...,
213
+ ) -> _Invertible[T | None]: ...
165
214
  def init_modules(self, *modules: Module) -> Self:
166
215
  """
167
216
  Function to clean modules in use and to use those passed as parameters.
@@ -212,6 +261,7 @@ class Module:
212
261
  Function to unlock the module by deleting cached instances of singletons.
213
262
  """
214
263
 
264
+ async def all_ready(self) -> None: ...
215
265
  def add_logger(self, logger: Logger) -> Self: ...
216
266
  @classmethod
217
267
  def from_name(cls, name: str) -> Module:
@@ -236,6 +286,8 @@ class Injectable[T](Protocol):
236
286
  def is_locked(self) -> bool: ...
237
287
  def unlock(self) -> None: ...
238
288
  @abstractmethod
289
+ async def aget_instance(self) -> T: ...
290
+ @abstractmethod
239
291
  def get_instance(self) -> T: ...
240
292
 
241
293
  @final
@@ -243,3 +295,10 @@ class Mode(Enum):
243
295
  FALLBACK = ...
244
296
  NORMAL = ...
245
297
  OVERRIDE = ...
298
+
299
+ class LazyInstance[T]:
300
+ def __init__(self, cls: _InputType[T], module: Module = ...) -> None: ...
301
+ @overload
302
+ def __get__(self, instance: object, owner: type | None = ...) -> T: ...
303
+ @overload
304
+ def __get__(self, instance: None = ..., owner: type | None = ...) -> Self: ...
@@ -49,5 +49,5 @@ def standardize_input_classes[T](
49
49
  @Locator.static_hooks.on_update
50
50
  def standardize_classes[T](*_: Any, **__: Any) -> HookGenerator[Updater[T]]:
51
51
  updater = yield
52
- updater.classes = set(standardize_types(*updater.classes))
52
+ updater.classes = frozenset(standardize_types(*updater.classes))
53
53
  return updater
@@ -0,0 +1,54 @@
1
+ from abc import abstractmethod
2
+ from collections.abc import Awaitable, Callable, Generator
3
+ from dataclasses import dataclass
4
+ from typing import Any, NoReturn, Protocol, override, runtime_checkable
5
+
6
+
7
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
8
+ class SimpleAwaitable[T](Awaitable[T]):
9
+ callable: Callable[..., Awaitable[T]]
10
+
11
+ @override
12
+ def __await__(self) -> Generator[Any, Any, T]:
13
+ return self.callable().__await__()
14
+
15
+
16
+ @runtime_checkable
17
+ class Caller[**P, T](Protocol):
18
+ __slots__ = ()
19
+
20
+ @abstractmethod
21
+ async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
22
+ raise NotImplementedError
23
+
24
+ @abstractmethod
25
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
26
+ raise NotImplementedError
27
+
28
+
29
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
30
+ class AsyncCaller[**P, T](Caller[P, T]):
31
+ callable: Callable[P, Awaitable[T]]
32
+
33
+ @override
34
+ async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
35
+ return await self.callable(*args, **kwargs)
36
+
37
+ @override
38
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> NoReturn:
39
+ raise RuntimeError(
40
+ "Synchronous call isn't supported for an asynchronous Callable."
41
+ )
42
+
43
+
44
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
45
+ class SyncCaller[**P, T](Caller[P, T]):
46
+ callable: Callable[P, T]
47
+
48
+ @override
49
+ async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
50
+ return self.callable(*args, **kwargs)
51
+
52
+ @override
53
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
54
+ return self.callable(*args, **kwargs)
@@ -13,8 +13,8 @@ class Invertible[T](Protocol):
13
13
 
14
14
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
15
15
  class SimpleInvertible[T](Invertible[T]):
16
- getter: Callable[..., T]
16
+ callable: Callable[..., T]
17
17
 
18
18
  @override
19
19
  def __invert__(self) -> T:
20
- return self.getter()
20
+ return self.callable()
@@ -1,5 +1,5 @@
1
- from collections.abc import Awaitable, Callable, Iterable, Iterator
2
- from inspect import iscoroutinefunction, isfunction
1
+ from collections.abc import Callable, Iterable, Iterator
2
+ from inspect import isfunction
3
3
  from types import GenericAlias, UnionType
4
4
  from typing import (
5
5
  Annotated,
@@ -24,9 +24,6 @@ def get_return_types(*args: TypeInfo[Any]) -> Iterator[InputType[Any]]:
24
24
  inner_args = arg
25
25
 
26
26
  elif isfunction(arg) and (return_type := get_type_hints(arg).get("return")):
27
- if iscoroutinefunction(arg):
28
- return_type = Awaitable[return_type] # type: ignore[valid-type]
29
-
30
27
  inner_args = (return_type,)
31
28
 
32
29
  else:
@@ -0,0 +1,25 @@
1
+ from typing import Self
2
+
3
+ from injection._core.common.invertible import Invertible
4
+ from injection._core.common.type import InputType
5
+ from injection._core.module import Module, mod
6
+
7
+
8
+ class LazyInstance[T]:
9
+ __slots__ = ("__value",)
10
+
11
+ __value: Invertible[T]
12
+
13
+ def __init__(self, cls: InputType[T], module: Module | None = None) -> None:
14
+ module = module or mod()
15
+ self.__value = module.get_lazy_instance(cls, default=NotImplemented)
16
+
17
+ def __get__(
18
+ self,
19
+ instance: object | None = None,
20
+ owner: type | None = None,
21
+ ) -> Self | T:
22
+ if instance is None:
23
+ return self
24
+
25
+ return ~self.__value
injection/_core/hook.py CHANGED
@@ -2,12 +2,13 @@ import itertools
2
2
  from collections.abc import Callable, Generator, Iterator
3
3
  from dataclasses import dataclass, field
4
4
  from inspect import isclass, isgeneratorfunction
5
- from typing import Any, Self
5
+ from typing import Any, Self, TypeGuard
6
6
 
7
7
  from injection.exceptions import HookError
8
8
 
9
9
  type HookGenerator[T] = Generator[None, T, T]
10
- type HookFunction[**P, T] = Callable[P, T | HookGenerator[T]]
10
+ type HookGeneratorFunction[**P, T] = Callable[P, HookGenerator[T]]
11
+ type HookFunction[**P, T] = Callable[P, T] | HookGeneratorFunction[P, T]
11
12
 
12
13
 
13
14
  @dataclass(eq=False, frozen=True, slots=True)
@@ -18,12 +19,12 @@ class Hook[**P, T]:
18
19
  repr=False,
19
20
  )
20
21
 
21
- def __call__( # type: ignore[no-untyped-def]
22
+ def __call__(
22
23
  self,
23
24
  wrapped: HookFunction[P, T] | type[HookFunction[P, T]] | None = None,
24
25
  /,
25
- ):
26
- def decorator(wp): # type: ignore[no-untyped-def]
26
+ ) -> Any:
27
+ def decorator(wp: Any) -> Any:
27
28
  self.add(wp() if isclass(wp) else wp)
28
29
  return wp
29
30
 
@@ -48,11 +49,11 @@ class Hook[**P, T]:
48
49
  handler: Callable[P, T],
49
50
  function: HookFunction[P, T],
50
51
  ) -> Callable[P, T]:
51
- if not cls.__is_generator_function(function):
52
+ if not cls.__is_hook_generator_function(function):
52
53
  return function # type: ignore[return-value]
53
54
 
54
55
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
55
- hook: HookGenerator[T] = function(*args, **kwargs) # type: ignore[assignment]
56
+ hook: HookGenerator[T] = function(*args, **kwargs)
56
57
 
57
58
  try:
58
59
  next(hook)
@@ -88,9 +89,11 @@ class Hook[**P, T]:
88
89
  return handler
89
90
 
90
91
  @staticmethod
91
- def __is_generator_function(obj: Any) -> bool:
92
- for o in obj, getattr(obj, "__call__", None):
93
- if isgeneratorfunction(o):
92
+ def __is_hook_generator_function[**_P, _T](
93
+ function: HookFunction[_P, _T],
94
+ ) -> TypeGuard[HookGeneratorFunction[_P, _T]]:
95
+ for fn in function, getattr(function, "__call__", None):
96
+ if isgeneratorfunction(fn):
94
97
  return True
95
98
 
96
99
  return False
@@ -0,0 +1,106 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import MutableMapping
3
+ from contextlib import suppress
4
+ from dataclasses import dataclass
5
+ from typing import Any, ClassVar, NoReturn, Protocol, override, runtime_checkable
6
+
7
+ from injection._core.common.asynchronous import Caller
8
+ from injection._core.common.threading import synchronized
9
+ from injection.exceptions import InjectionError
10
+
11
+
12
+ @runtime_checkable
13
+ class Injectable[T](Protocol):
14
+ __slots__ = ()
15
+
16
+ @property
17
+ def is_locked(self) -> bool:
18
+ return False
19
+
20
+ def unlock(self) -> None:
21
+ return
22
+
23
+ @abstractmethod
24
+ async def aget_instance(self) -> T:
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def get_instance(self) -> T:
29
+ raise NotImplementedError
30
+
31
+
32
+ @dataclass(repr=False, frozen=True, slots=True)
33
+ class BaseInjectable[T](Injectable[T], ABC):
34
+ factory: Caller[..., T]
35
+
36
+
37
+ class SimpleInjectable[T](BaseInjectable[T]):
38
+ __slots__ = ()
39
+
40
+ @override
41
+ async def aget_instance(self) -> T:
42
+ return await self.factory.acall()
43
+
44
+ @override
45
+ def get_instance(self) -> T:
46
+ return self.factory.call()
47
+
48
+
49
+ class SingletonInjectable[T](BaseInjectable[T]):
50
+ __slots__ = ("__dict__",)
51
+
52
+ __key: ClassVar[str] = "$instance"
53
+
54
+ @property
55
+ def cache(self) -> MutableMapping[str, Any]:
56
+ return self.__dict__
57
+
58
+ @property
59
+ @override
60
+ def is_locked(self) -> bool:
61
+ return self.__key in self.cache
62
+
63
+ @override
64
+ def unlock(self) -> None:
65
+ self.cache.clear()
66
+
67
+ @override
68
+ async def aget_instance(self) -> T:
69
+ with suppress(KeyError):
70
+ return self.__check_instance()
71
+
72
+ with synchronized():
73
+ instance = await self.factory.acall()
74
+ self.__set_instance(instance)
75
+
76
+ return instance
77
+
78
+ @override
79
+ def get_instance(self) -> T:
80
+ with suppress(KeyError):
81
+ return self.__check_instance()
82
+
83
+ with synchronized():
84
+ instance = self.factory.call()
85
+ self.__set_instance(instance)
86
+
87
+ return instance
88
+
89
+ def __check_instance(self) -> T:
90
+ return self.cache[self.__key]
91
+
92
+ def __set_instance(self, value: T) -> None:
93
+ self.cache[self.__key] = value
94
+
95
+
96
+ @dataclass(repr=False, frozen=True, slots=True)
97
+ class ShouldBeInjectable[T](Injectable[T]):
98
+ cls: type[T]
99
+
100
+ @override
101
+ async def aget_instance(self) -> T:
102
+ return self.get_instance()
103
+
104
+ @override
105
+ def get_instance(self) -> NoReturn:
106
+ raise InjectionError(f"`{self.cls}` should be an injectable.")