Skip to content

Commit 0053bdc

Browse files
authored
feat: ✨️ Add make_async_factory method
1 parent 4e5c378 commit 0053bdc

5 files changed

Lines changed: 67 additions & 33 deletions

File tree

injection/__init__.pyi

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ from ._core.common.type import InputType as _InputType
1010
from ._core.common.type import TypeInfo as _TypeInfo
1111
from ._core.module import InjectableFactory as _InjectableFactory
1212
from ._core.module import ModeStr, PriorityStr
13+
from ._core.module import Recipe as _Recipe
1314

1415
__MODULE: Final[Module] = ...
1516

@@ -91,7 +92,7 @@ class Module:
9192

9293
def injectable[**P, T](
9394
self,
94-
wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
95+
wrapped: _Recipe[P, T] = ...,
9596
/,
9697
*,
9798
cls: _InjectableFactory[T] = ...,
@@ -107,7 +108,7 @@ class Module:
107108

108109
def singleton[**P, T](
109110
self,
110-
wrapped: Callable[P, T] | Callable[P, Awaitable[T]] = ...,
111+
wrapped: _Recipe[P, T] = ...,
111112
/,
112113
*,
113114
inject: bool = ...,
@@ -176,6 +177,12 @@ class Module:
176177
/,
177178
threadsafe: bool = ...,
178179
) -> Callable[P, T]: ...
180+
def make_async_factory[T](
181+
self,
182+
wrapped: type[T],
183+
/,
184+
threadsafe: bool = ...,
185+
) -> Callable[..., Awaitable[T]]: ...
179186
async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
180187
def find_instance[T](self, cls: _InputType[T]) -> T:
181188
"""

injection/_core/hook.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import itertools
2-
from collections import deque
32
from collections.abc import Callable, Generator, Iterator
43
from dataclasses import dataclass, field
54
from inspect import isclass, isgeneratorfunction
@@ -14,8 +13,8 @@
1413

1514
@dataclass(eq=False, frozen=True, slots=True)
1615
class Hook[**P, T]:
17-
__functions: deque[HookFunction[P, T]] = field(
18-
default_factory=deque,
16+
__functions: list[HookFunction[P, T]] = field(
17+
default_factory=list,
1918
init=False,
2019
repr=False,
2120
)
@@ -36,7 +35,7 @@ def __stack(self) -> Iterator[HookFunction[P, T]]:
3635
return iter(self.__functions)
3736

3837
def add(self, *functions: HookFunction[P, T]) -> Self:
39-
self.__functions.extendleft(functions)
38+
self.__functions.extend(reversed(functions))
4039
return self
4140

4241
@classmethod
@@ -65,7 +64,6 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
6564
hook.throw(exc)
6665
else:
6766
hook.send(value)
68-
return value
6967

7068
except StopIteration as stop:
7169
return stop.value

injection/_core/module.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
from abc import ABC, abstractmethod
44
from collections import OrderedDict, deque
55
from collections.abc import (
6+
AsyncGenerator,
67
AsyncIterator,
78
Awaitable,
89
Callable,
910
Collection,
11+
Generator,
1012
Iterable,
1113
Iterator,
1214
Mapping,
@@ -360,6 +362,14 @@ def get_default(cls) -> Priority:
360362

361363
type PriorityStr = Literal["low", "high"]
362364

365+
type ContextManagerLikeRecipe[**P, T] = (
366+
Callable[P, ContextManager[T]] | Callable[P, AsyncContextManager[T]]
367+
)
368+
type GeneratorRecipe[**P, T] = (
369+
Callable[P, Generator[T, Any, Any]] | Callable[P, AsyncGenerator[T, Any]]
370+
)
371+
type Recipe[**P, T] = Callable[P, T] | Callable[P, Awaitable[T]]
372+
363373

364374
@dataclass(eq=False, frozen=True, slots=True)
365375
class Module(Broker, EventListener):
@@ -411,7 +421,7 @@ def __brokers(self) -> Iterator[Broker]:
411421

412422
def injectable[**P, T](
413423
self,
414-
wrapped: Callable[P, T] | Callable[P, Awaitable[T]] | None = None,
424+
wrapped: Recipe[P, T] | None = None,
415425
/,
416426
*,
417427
cls: InjectableFactory[T] = SimpleInjectable,
@@ -420,9 +430,7 @@ def injectable[**P, T](
420430
on: TypeInfo[T] = (),
421431
mode: Mode | ModeStr = Mode.get_default(),
422432
) -> Any:
423-
def decorator(
424-
wp: Callable[P, T] | Callable[P, Awaitable[T]],
425-
) -> Callable[P, T] | Callable[P, Awaitable[T]]:
433+
def decorator(wp: Recipe[P, T]) -> Recipe[P, T]:
426434
factory = extract_caller(self.make_injected_function(wp) if inject else wp)
427435
hints = on if ignore_type_hint else (wp, on)
428436
updater = Updater(
@@ -447,23 +455,10 @@ def scoped[**P, T](
447455
mode: Mode | ModeStr = Mode.get_default(),
448456
) -> Any:
449457
def decorator(
450-
wrapped: Callable[P, T]
451-
| Callable[P, Awaitable[T]]
452-
| Callable[P, Iterator[T]]
453-
| Callable[P, AsyncIterator[T]],
454-
) -> (
455-
Callable[P, T]
456-
| Callable[P, Awaitable[T]]
457-
| Callable[P, Iterator[T]]
458-
| Callable[P, AsyncIterator[T]]
459-
):
458+
wrapped: Recipe[P, T] | GeneratorRecipe[P, T],
459+
) -> Recipe[P, T] | GeneratorRecipe[P, T]:
460460
injectable_class: Callable[[Caller[P, Any], str], Injectable[T]]
461-
wrapper: (
462-
Callable[P, T]
463-
| Callable[P, Awaitable[T]]
464-
| Callable[P, ContextManager[T]]
465-
| Callable[P, AsyncContextManager[T]]
466-
)
461+
wrapper: Recipe[P, T] | ContextManagerLikeRecipe[P, T]
467462

468463
if isasyncgenfunction(wrapped):
469464
hint = get_yield_hint(wrapped)
@@ -588,6 +583,18 @@ def listen() -> None:
588583

589584
return SyncInjectedFunction(metadata)
590585

586+
def make_async_factory[T](
587+
self,
588+
wrapped: type[T],
589+
/,
590+
threadsafe: bool = False,
591+
) -> Callable[..., Awaitable[T]]:
592+
factory: InjectedFunction[..., T] = self.make_injected_function(
593+
wrapped,
594+
threadsafe,
595+
)
596+
return factory.__inject_metadata__.acall
597+
591598
async def afind_instance[T](self, cls: InputType[T]) -> T:
592599
injectable = self[cls]
593600
return await injectable.aget_instance()

tests/core/test_module.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections.abc import Iterator
2+
from dataclasses import dataclass
23
from typing import Annotated
34

45
import pytest
@@ -55,6 +56,27 @@ async def t_recipe() -> T:
5556
instance = module.get_instance(T)
5657
assert isinstance(instance, T)
5758

59+
"""
60+
make_async_factory
61+
"""
62+
63+
async def test_make_async_factory_with_success(self, module):
64+
class Dependency: ...
65+
66+
@module.injectable
67+
async def dependency_recipe() -> Dependency:
68+
return Dependency()
69+
70+
@dataclass
71+
class InnerClass:
72+
dependency: Dependency
73+
74+
async_factory = module.make_async_factory(InnerClass)
75+
instance = await async_factory()
76+
77+
assert isinstance(instance, InnerClass)
78+
assert isinstance(instance.dependency, Dependency)
79+
5880
"""
5981
aget_instance
6082
"""

uv.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)