Coverage for fast_depends/core/model.py: 100%
156 statements
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-13 05:19 +0000
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-13 05:19 +0000
1from collections.abc import Callable, Generator, Iterable, Sequence 1abcdefghijklmnopqrst
2from contextlib import AsyncExitStack, ExitStack 1abcdefghijklmnopqrst
3from inspect import Parameter, unwrap 1abcdefghijklmnopqrst
4from typing import ( 1abcdefghijklmnopqrst
5 TYPE_CHECKING,
6 Any,
7)
9import anyio 1abcdefghijklmnopqrst
11from fast_depends._compat import ExceptionGroup 1abcdefghijklmnopqrst
12from fast_depends.library.model import CustomField 1abcdefghijklmnopqrst
13from fast_depends.library.serializer import OptionItem, Serializer, SerializerProto 1abcdefghijklmnopqrst
14from fast_depends.utils import ( 1abcdefghijklmnopqrst
15 async_map,
16 is_async_gen_callable,
17 is_coroutine_callable,
18 is_gen_callable,
19 run_async,
20 solve_generator_async,
21 solve_generator_sync,
22)
24if TYPE_CHECKING: 1abcdefghijklmnopqrst
25 from fast_depends.dependencies.provider import Key, Provider
28class CallModel: 1abcdefghijklmnopqrst
29 __slots__ = ( 1abcdefghijklmnopqrst
30 "call",
31 "is_async",
32 "is_generator",
33 "params",
34 "alias_arguments",
35 "args_name",
36 "positional_args",
37 "kwargs_name",
38 "keyword_args",
39 "dependencies",
40 "extra_dependencies",
41 "custom_fields",
42 "use_cache",
43 "serializer",
44 "dependency_provider",
45 "serializer_cls",
46 )
48 alias_arguments: tuple[str, ...] 1abcdefghijkls
50 @property 1abcdefghijklmnopqrst
51 def call_name(self) -> str: 1abcdefghijklmnopqrst
52 call = unwrap(self.call) 1abcdefghijklmnopqrst
53 return getattr(call, "__name__", type(call).__name__) 1abcdefghijklmnopqrst
55 @property 1abcdefghijklmnopqrst
56 def flat_params(self) -> list[OptionItem]: 1abcdefghijklmnopqrst
57 params = list(self.params) 1abcdefghijklmnopqrst
58 for d in map( 1abcdefghijklmnopqrst
59 self.dependency_provider.get_dependant,
60 (*self.dependencies.values(), *self.extra_dependencies),
61 ):
62 for p in d.flat_params: 1abcdefghijklmnopqrst
63 if p.field_name not in (i.field_name for i in params): 1abcdefghijklmnopqrst
64 params.append(p) 1abcdefghijklmnopqrst
65 return params 1abcdefghijklmnopqrst
67 def __init__( 1abcdefghijklmnopqrst
68 self,
69 *,
70 call: Callable[..., Any],
71 serializer: Serializer | None,
72 params: tuple[OptionItem, ...],
73 use_cache: bool,
74 is_async: bool,
75 is_generator: bool,
76 args_name: str | None,
77 kwargs_name: str | None,
78 dependencies: dict[str, "Key"],
79 extra_dependencies: Iterable["Key"],
80 keyword_args: list[str],
81 positional_args: list[str],
82 custom_fields: dict[str, CustomField],
83 dependency_provider: "Provider",
84 serializer_cls: SerializerProto | None,
85 ):
86 self.call = call 1abcdefghijklmnopqrst
87 self.serializer = serializer 1abcdefghijklmnopqrst
89 if serializer is not None: 1abcdefghijklmnopqrst
90 self.alias_arguments = serializer.get_aliases() 1abcdefghijklmnopqrst
91 else: # pragma: no cover
92 self.alias_arguments = () 1abcdefghijklmnopqrst
94 self.args_name = args_name 1abcdefghijklmnopqrst
95 self.keyword_args = tuple(keyword_args or ()) 1abcdefghijklmnopqrst
96 self.kwargs_name = kwargs_name 1abcdefghijklmnopqrst
97 self.positional_args = tuple(positional_args or ()) 1abcdefghijklmnopqrst
98 self.use_cache = use_cache 1abcdefghijklmnopqrst
99 self.is_async = ( 1abcdefghijklmnopqrst
100 is_async or is_coroutine_callable(call) or is_async_gen_callable(call)
101 )
102 self.is_generator = ( 1abcdefghijklmnopqrst
103 is_generator or is_gen_callable(call) or is_async_gen_callable(call)
104 )
106 self.dependencies = dependencies or {} 1abcdefghijklmnopqrst
107 self.extra_dependencies = tuple(extra_dependencies or ()) 1abcdefghijklmnopqrst
108 self.custom_fields = custom_fields or {} 1abcdefghijklmnopqrst
110 self.params = params 1abcdefghijklmnopqrst
111 self.dependency_provider = dependency_provider 1abcdefghijklmnopqrst
112 self.serializer_cls = serializer_cls 1abcdefghijklmnopqrst
114 def _solve( 1abcdefghijklmnopqrst
115 self,
116 /,
117 *args: tuple[Any, ...],
118 cache_dependencies: dict[Callable[..., Any], Any],
119 **kwargs: dict[str, Any],
120 ) -> Generator[
121 tuple[
122 tuple[Any, ...],
123 dict[str, Any],
124 ],
125 Any,
126 Any,
127 ]:
128 if self.use_cache and self.call in cache_dependencies: 1abcdefghijklmnopqrst
129 return cache_dependencies[self.call] 1abcdefghijklmnopqrst
131 kw: dict[str, Any] = {} 1abcdefghijklmnopqrst
132 for arg in self.keyword_args: 1abcdefghijklmnopqrst
133 if (v := kwargs.pop(arg, Parameter.empty)) is not Parameter.empty: 1abcdefghijklmnopqrst
134 kw[arg] = v 1abcdefghijklmnopqrst
136 if self.kwargs_name in self.alias_arguments: 1abcdefghijklmnopqrst
137 kw[self.kwargs_name] = kwargs 1abcdefghijklmnopqr
138 else:
139 kw.update(kwargs) 1abcdefghijklmnopqrst
141 for arg in self.positional_args: 1abcdefghijklmnopqrst
142 if arg not in kw: 1abcdefghijklmnopqrst
143 if args: 1abcdefghijklmnopqrst
144 kw[arg], args = args[0], args[1:] 1abcdefghijklmnopqrst
145 else:
146 break 1abcdefghijklmnopqrst
148 keyword_args: Iterable[str]
149 if self.args_name in self.alias_arguments: 1abcdefghijklmnopqrst
150 kw[self.args_name] = args 1abcdefghijklmnopqr
151 keyword_args = self.keyword_args 1abcdefghijklmnopqr
153 else:
154 keyword_args = self.keyword_args + self.positional_args 1abcdefghijklmnopqrst
155 for arg in keyword_args: 1abcdefghijklmnopqrst
156 if not args: 1abcdefghijklmnopqrst
157 break 1abcdefghijklmnopqrst
159 if arg not in self.dependencies and arg not in kw: 1abcdefghijklmnopqrst
160 kw[arg], args = args[0], args[1:] 1abcdefghijklmnopqrst
162 solved_kw: dict[str, Any]
163 solved_kw = yield args, kw 1abcdefghijklmnopqrst
165 args_: Sequence[Any]
166 if self.serializer is not None: 1abcdefghijklmnopqrst
167 casted_options = self.serializer(solved_kw) 1abcdefghijklmnopqrst
168 solved_kw.update(casted_options) 1abcdefghijklmnopqrst
170 if self.args_name: 1abcdefghijklmnopqrst
171 args_ = ( 1abcdefghijklmnopqrst
172 *map(solved_kw.pop, self.positional_args),
173 *solved_kw.get(self.args_name, args),
174 )
175 else:
176 args_ = () 1abcdefghijklmnopqrst
178 kwargs_ = {arg: solved_kw.pop(arg) for arg in keyword_args if arg in solved_kw} 1abcdefghijklmnopqrst
179 if self.kwargs_name: 1abcdefghijklmnopqrst
180 kwargs_.update(solved_kw.get(self.kwargs_name, solved_kw)) 1abcdefghijklmnopqrst
182 response = yield args_, kwargs_ 1abcdefghijklmnopqrst
184 if not self.is_generator: 1abcdefghijklmnopqrst
185 response = self._cast_response(response) 1abcdefghijklmnopqrst
187 if self.use_cache: # pragma: no branch 1abcdefghijklmnopqrst
188 cache_dependencies[self.call] = response 1abcdefghijklmnopqrst
190 return response 1abcdefghijklmnopqrst
192 def _cast_response(self, /, value: Any) -> Any: 1abcdefghijklmnopqrst
193 if self.serializer is not None: 1abcdefghijklmnopqrst
194 return self.serializer.response(value) 1abcdefghijklmnopqrst
195 return value 1abcdefghijklmnopqrst
197 def solve( 1abcdefghijklmnopqrst
198 self,
199 /,
200 *args: tuple[Any, ...],
201 stack: ExitStack,
202 cache_dependencies: dict[Callable[..., Any], Any],
203 nested: bool = False,
204 dependency_provider: "Provider | None" = None,
205 **kwargs: dict[str, Any],
206 ) -> Any:
207 cast_gen = self._solve( 1abcdefghijklmnopqrst
208 *args,
209 cache_dependencies=cache_dependencies,
210 **kwargs,
211 )
212 try: 1abcdefghijklmnopqrst
213 args, kwargs = next(cast_gen) 1abcdefghijklmnopqrst
214 except StopIteration as e: 1abcdefghijklmnopqrst
215 cached_value = e.value 1abcdefghijklmnopqrst
216 return cached_value 1abcdefghijklmnopqrst
218 if dependency_provider: 1abcdefghijklmnopqrst
219 provider = self.dependency_provider.merge(dependency_provider) 1abcdefghijklmnopqrst
220 else:
221 provider = self.dependency_provider 1abcdefghijklmnopqrst
223 for dep in map(provider.get_dependant, self.extra_dependencies): 1abcdefghijklmnopqrst
224 dep.solve( 1abcdefghijklmnopqrst
225 *args,
226 stack=stack,
227 cache_dependencies=cache_dependencies,
228 nested=True,
229 **kwargs,
230 )
232 for dep_arg, dep_key in self.dependencies.items(): 1abcdefghijklmnopqrst
233 if dep_arg not in kwargs: 1abcdefghijklmnopqrst
234 kwargs[dep_arg] = provider.get_dependant(dep_key).solve( 1abcdefghijklmnopqrst
235 *args,
236 stack=stack,
237 cache_dependencies=cache_dependencies,
238 nested=True,
239 **kwargs,
240 )
242 if self.custom_fields: 1abcdefghijklmnopqrst
243 for custom in self.custom_fields.values(): 1abcdefghijklmnopqrst
244 if custom.field: 1abcdefghijklmnopqrst
245 custom.use_field(kwargs) 1abcdefghijklmnopqr
246 else:
247 kwargs = custom.use(**kwargs) 1abcdefghijklmnopqrst
249 final_args, final_kwargs = cast_gen.send(kwargs) 1abcdefghijklmnopqrst
251 if self.is_generator and nested: 1abcdefghijklmnopqrst
252 response = solve_generator_sync( 1abcdefghijklmnopqrst
253 *final_args,
254 call=self.call,
255 stack=stack,
256 **final_kwargs,
257 )
259 else:
260 response = self.call(*final_args, **final_kwargs) 1abcdefghijklmnopqrst
262 try: 1abcdefghijklmnopqrst
263 cast_gen.send(response) 1abcdefghijklmnopqrst
264 except StopIteration as e: 1abcdefghijklmnopqrst
265 value = e.value 1abcdefghijklmnopqrst
267 if self.serializer is None or nested or not self.is_generator: 1abcdefghijklmnopqrst
268 return value 1abcdefghijklmnopqrst
270 else:
271 return map(self._cast_response, value) 1abcdefghijklmnopqr
273 raise AssertionError("unreachable")
275 async def asolve( 1abcdefghijklmnopqrst
276 self,
277 /,
278 *args: tuple[Any, ...],
279 stack: AsyncExitStack,
280 cache_dependencies: dict[Callable[..., Any], Any],
281 nested: bool = False,
282 dependency_provider: "Provider | None" = None,
283 **kwargs: dict[str, Any],
284 ) -> Any:
285 cast_gen = self._solve( 1abcdefghijklmnopqrst
286 *args,
287 cache_dependencies=cache_dependencies,
288 **kwargs,
289 )
290 try: 1abcdefghijklmnopqrst
291 args, kwargs = next(cast_gen) 1abcdefghijklmnopqrst
292 except StopIteration as e: 1abcdefghijklmnopqrst
293 cached_value = e.value 1abcdefghijklmnopqrst
294 return cached_value 1abcdefghijklmnopqrst
296 if dependency_provider: 1abcdefghijklmnopqrst
297 provider = self.dependency_provider.merge(dependency_provider) 1abcdefghijklmnopqrst
298 else:
299 provider = self.dependency_provider 1abcdefghijklmnopqrst
301 for dep in map(provider.get_dependant, self.extra_dependencies): 1abcdefghijklmnopqrst
302 # TODO: run concurrently
303 await dep.asolve( 1abcdefghijklmnopqrst
304 *args,
305 stack=stack,
306 cache_dependencies=cache_dependencies,
307 nested=True,
308 **kwargs,
309 )
311 for dep_arg, dep_key in self.dependencies.items(): 1abcdefghijklmnopqrst
312 if dep_arg not in kwargs: 1abcdefghijklmnopqrst
313 kwargs[dep_arg] = await provider.get_dependant( 1abcdefghijklmnopqrst
314 dep_key
315 ).asolve(
316 *args,
317 stack=stack,
318 cache_dependencies=cache_dependencies,
319 nested=True,
320 **kwargs,
321 )
323 if self.custom_fields: 1abcdefghijklmnopqrst
324 custom_to_solve: list[CustomField] = [] 1abcdefghijklmnopqrst
326 try: 1abcdefghijklmnopqrst
327 async with anyio.create_task_group() as tg: 1abcdefghijklmnopqrst
328 for custom in self.custom_fields.values(): 1abcdefghijklmnopqrst
329 if custom.field: 1abcdefghijklmnopqrst
330 tg.start_soon(run_async, custom.use_field, kwargs) 1abcdefghijklmnopqrst
331 else:
332 custom_to_solve.append(custom) 1abcdefghijklmnopqr
334 except ExceptionGroup as exgr: 1abcdefghijklmnopqrst
335 for ex in exgr.exceptions: # pragma: no branch 1abcdefghijklmnopqrst
336 raise ex from None 1abcdefghijklmnopqrst
338 for j in custom_to_solve: 1abcdefghijklmnopqr
339 kwargs = await run_async(j.use, **kwargs) 1abcdefghijklmnopqr
341 final_args, final_kwargs = cast_gen.send(kwargs) 1abcdefghijklmnopqrst
343 if self.is_generator and nested: 1abcdefghijklmnopqrst
344 response = await solve_generator_async( 1abcdefghijklmnopqrst
345 *final_args,
346 call=self.call,
347 stack=stack,
348 **final_kwargs,
349 )
350 else:
351 response = await run_async(self.call, *final_args, **final_kwargs) 1abcdefghijklmnopqrst
353 try: 1abcdefghijklmnopqrst
354 cast_gen.send(response) 1abcdefghijklmnopqrst
355 except StopIteration as e: 1abcdefghijklmnopqrst
356 value = e.value 1abcdefghijklmnopqrst
358 if self.serializer is None or nested or not self.is_generator: 1abcdefghijklmnopqrst
359 return value 1abcdefghijklmnopqrst
361 else:
362 return async_map(self._cast_response, value) 1abcdefghijklmnopqr
364 raise AssertionError("unreachable")