-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathfactory.py
More file actions
129 lines (107 loc) · 4.71 KB
/
factory.py
File metadata and controls
129 lines (107 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import dataclasses
import inspect
import typing
from modern_di import errors, types
from modern_di.providers.abstract import AbstractProvider
from modern_di.scope import Scope
from modern_di.types_parser import SignatureItem, parse_creator
if typing.TYPE_CHECKING:
from modern_di import Container
@dataclasses.dataclass(kw_only=True, slots=True)
class CacheSettings(typing.Generic[types.T_co]):
clear_cache: bool = True
finalizer: typing.Callable[[types.T_co], None | typing.Awaitable[None]] | None = None
is_async_finalizer: bool = dataclasses.field(init=False)
def __post_init__(self) -> None:
self.is_async_finalizer = bool(self.finalizer) and inspect.iscoroutinefunction(self.finalizer)
class Factory(AbstractProvider[types.T_co]):
__slots__ = [*AbstractProvider.BASE_SLOTS, "_creator", "_kwargs", "_parsed_kwargs", "cache_settings"]
def __init__( # noqa: PLR0913
self,
*,
scope: Scope = Scope.APP,
creator: typing.Callable[..., types.T_co],
bound_type: type | None = types.UNSET, # type: ignore[assignment]
kwargs: dict[str, typing.Any] | None = None,
cache_settings: CacheSettings[types.T_co] | None = None,
skip_creator_parsing: bool = False,
) -> None:
if skip_creator_parsing:
parsed_type: type | None = None
parsed_kwargs: dict[str, SignatureItem] = {}
else:
return_sig, parsed_kwargs = parse_creator(creator)
parsed_type = return_sig.arg_type
self._parsed_kwargs = parsed_kwargs
super().__init__(scope=scope, bound_type=bound_type if bound_type != types.UNSET else parsed_type)
self._creator = creator
self.cache_settings = cache_settings
self._kwargs = kwargs
def _compile_kwargs(self, container: "Container") -> dict[str, typing.Any]:
result: dict[str, typing.Any] = {}
for k, v in self._parsed_kwargs.items():
provider: AbstractProvider[types.T_co] | None = None
if v.arg_type:
provider = container.providers_registry.find_provider(v.arg_type)
if provider is self:
provider = None
else:
for x in v.args:
provider = typing.cast(
AbstractProvider[types.T_co] | None, container.providers_registry.find_provider(x)
)
if provider:
break
if provider:
result[k] = provider
continue
if (not self._kwargs or k not in self._kwargs) and v.default == types.UNSET:
raise RuntimeError(
errors.FACTORY_ARGUMENT_RESOLUTION_ERROR.format(
arg_name=k, arg_type=v.arg_type, bound_type=self.bound_type or self._creator
)
)
if self._kwargs:
result.update(self._kwargs)
return result
def validate(self, container: "Container") -> dict[str, typing.Any]:
container = container.find_container(self.scope)
cache_item = container.cache_registry.fetch_cache_item(self)
if cache_item.kwargs is not None:
kwargs = cache_item.kwargs
else:
kwargs = self._compile_kwargs(container)
cache_item.kwargs = kwargs
return {
"bound_type": self.bound_type,
"creator": self._creator,
"self": self,
"kwargs": {k: v.validate(container) if isinstance(v, AbstractProvider) else v for k, v in kwargs.items()},
"cache_settings": self.cache_settings,
}
def resolve(self, container: "Container") -> types.T_co:
container = container.find_container(self.scope)
cache_item = container.cache_registry.fetch_cache_item(self)
if cache_item.kwargs is not None:
kwargs = cache_item.kwargs
else:
kwargs = self._compile_kwargs(container)
cache_item.kwargs = kwargs
resolved_kwargs = {
k: container.resolve_provider(v) if isinstance(v, AbstractProvider) else v for k, v in kwargs.items()
}
if not self.cache_settings:
return self._creator(**resolved_kwargs)
if cache_item.cache is not None:
return typing.cast(types.T_co, cache_item.cache)
if container.lock:
container.lock.acquire()
try:
if cache_item.cache is not None:
return typing.cast(types.T_co, cache_item.cache)
instance = self._creator(**resolved_kwargs)
cache_item.cache = instance
return instance
finally:
if container.lock:
container.lock.release()