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

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) 

8 

9import anyio 1abcdefghijklmnopqrst

10 

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) 

23 

24if TYPE_CHECKING: 1abcdefghijklmnopqrst

25 from fast_depends.dependencies.provider import Key, Provider 

26 

27 

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 ) 

47 

48 alias_arguments: tuple[str, ...] 1abcdefghijkls

49 

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

54 

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

66 

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

88 

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

93 

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 ) 

105 

106 self.dependencies = dependencies or {} 1abcdefghijklmnopqrst

107 self.extra_dependencies = tuple(extra_dependencies or ()) 1abcdefghijklmnopqrst

108 self.custom_fields = custom_fields or {} 1abcdefghijklmnopqrst

109 

110 self.params = params 1abcdefghijklmnopqrst

111 self.dependency_provider = dependency_provider 1abcdefghijklmnopqrst

112 self.serializer_cls = serializer_cls 1abcdefghijklmnopqrst

113 

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

130 

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

135 

136 if self.kwargs_name in self.alias_arguments: 1abcdefghijklmnopqrst

137 kw[self.kwargs_name] = kwargs 1abcdefghijklmnopqr

138 else: 

139 kw.update(kwargs) 1abcdefghijklmnopqrst

140 

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

147 

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

152 

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

158 

159 if arg not in self.dependencies and arg not in kw: 1abcdefghijklmnopqrst

160 kw[arg], args = args[0], args[1:] 1abcdefghijklmnopqrst

161 

162 solved_kw: dict[str, Any] 

163 solved_kw = yield args, kw 1abcdefghijklmnopqrst

164 

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

169 

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

177 

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

181 

182 response = yield args_, kwargs_ 1abcdefghijklmnopqrst

183 

184 if not self.is_generator: 1abcdefghijklmnopqrst

185 response = self._cast_response(response) 1abcdefghijklmnopqrst

186 

187 if self.use_cache: # pragma: no branch 1abcdefghijklmnopqrst

188 cache_dependencies[self.call] = response 1abcdefghijklmnopqrst

189 

190 return response 1abcdefghijklmnopqrst

191 

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

196 

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

217 

218 if dependency_provider: 1abcdefghijklmnopqrst

219 provider = self.dependency_provider.merge(dependency_provider) 1abcdefghijklmnopqrst

220 else: 

221 provider = self.dependency_provider 1abcdefghijklmnopqrst

222 

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 ) 

231 

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 ) 

241 

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

248 

249 final_args, final_kwargs = cast_gen.send(kwargs) 1abcdefghijklmnopqrst

250 

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 ) 

258 

259 else: 

260 response = self.call(*final_args, **final_kwargs) 1abcdefghijklmnopqrst

261 

262 try: 1abcdefghijklmnopqrst

263 cast_gen.send(response) 1abcdefghijklmnopqrst

264 except StopIteration as e: 1abcdefghijklmnopqrst

265 value = e.value 1abcdefghijklmnopqrst

266 

267 if self.serializer is None or nested or not self.is_generator: 1abcdefghijklmnopqrst

268 return value 1abcdefghijklmnopqrst

269 

270 else: 

271 return map(self._cast_response, value) 1abcdefghijklmnopqr

272 

273 raise AssertionError("unreachable") 

274 

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

295 

296 if dependency_provider: 1abcdefghijklmnopqrst

297 provider = self.dependency_provider.merge(dependency_provider) 1abcdefghijklmnopqrst

298 else: 

299 provider = self.dependency_provider 1abcdefghijklmnopqrst

300 

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 ) 

310 

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 ) 

322 

323 if self.custom_fields: 1abcdefghijklmnopqrst

324 custom_to_solve: list[CustomField] = [] 1abcdefghijklmnopqrst

325 

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

333 

334 except ExceptionGroup as exgr: 1abcdefghijklmnopqrst

335 for ex in exgr.exceptions: # pragma: no branch 1abcdefghijklmnopqrst

336 raise ex from None 1abcdefghijklmnopqrst

337 

338 for j in custom_to_solve: 1abcdefghijklmnopqr

339 kwargs = await run_async(j.use, **kwargs) 1abcdefghijklmnopqr

340 

341 final_args, final_kwargs = cast_gen.send(kwargs) 1abcdefghijklmnopqrst

342 

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

352 

353 try: 1abcdefghijklmnopqrst

354 cast_gen.send(response) 1abcdefghijklmnopqrst

355 except StopIteration as e: 1abcdefghijklmnopqrst

356 value = e.value 1abcdefghijklmnopqrst

357 

358 if self.serializer is None or nested or not self.is_generator: 1abcdefghijklmnopqrst

359 return value 1abcdefghijklmnopqrst

360 

361 else: 

362 return async_map(self._cast_response, value) 1abcdefghijklmnopqr

363 

364 raise AssertionError("unreachable")