utils.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. import inspect
  2. from contextlib import AsyncExitStack, contextmanager
  3. from copy import deepcopy
  4. from typing import (
  5. Any,
  6. Callable,
  7. Coroutine,
  8. Dict,
  9. ForwardRef,
  10. List,
  11. Mapping,
  12. Optional,
  13. Sequence,
  14. Tuple,
  15. Type,
  16. Union,
  17. cast,
  18. )
  19. import anyio
  20. from fastapi import params
  21. from fastapi._compat import (
  22. PYDANTIC_V2,
  23. ErrorWrapper,
  24. ModelField,
  25. Required,
  26. Undefined,
  27. _regenerate_error_with_loc,
  28. copy_field_info,
  29. create_body_model,
  30. evaluate_forwardref,
  31. field_annotation_is_scalar,
  32. get_annotation_from_field_info,
  33. get_missing_field_error,
  34. is_bytes_field,
  35. is_bytes_sequence_field,
  36. is_scalar_field,
  37. is_scalar_sequence_field,
  38. is_sequence_field,
  39. is_uploadfile_or_nonable_uploadfile_annotation,
  40. is_uploadfile_sequence_annotation,
  41. lenient_issubclass,
  42. sequence_types,
  43. serialize_sequence_value,
  44. value_is_sequence,
  45. )
  46. from fastapi.background import BackgroundTasks
  47. from fastapi.concurrency import (
  48. asynccontextmanager,
  49. contextmanager_in_threadpool,
  50. )
  51. from fastapi.dependencies.models import Dependant, SecurityRequirement
  52. from fastapi.logger import logger
  53. from fastapi.security.base import SecurityBase
  54. from fastapi.security.oauth2 import OAuth2, SecurityScopes
  55. from fastapi.security.open_id_connect_url import OpenIdConnect
  56. from fastapi.utils import create_response_field, get_path_param_names
  57. from pydantic.fields import FieldInfo
  58. from starlette.background import BackgroundTasks as StarletteBackgroundTasks
  59. from starlette.concurrency import run_in_threadpool
  60. from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
  61. from starlette.requests import HTTPConnection, Request
  62. from starlette.responses import Response
  63. from starlette.websockets import WebSocket
  64. from typing_extensions import Annotated, get_args, get_origin
  65. multipart_not_installed_error = (
  66. 'Form data requires "python-multipart" to be installed. \n'
  67. 'You can install "python-multipart" with: \n\n'
  68. "pip install python-multipart\n"
  69. )
  70. multipart_incorrect_install_error = (
  71. 'Form data requires "python-multipart" to be installed. '
  72. 'It seems you installed "multipart" instead. \n'
  73. 'You can remove "multipart" with: \n\n'
  74. "pip uninstall multipart\n\n"
  75. 'And then install "python-multipart" with: \n\n'
  76. "pip install python-multipart\n"
  77. )
  78. def check_file_field(field: ModelField) -> None:
  79. field_info = field.field_info
  80. if isinstance(field_info, params.Form):
  81. try:
  82. # __version__ is available in both multiparts, and can be mocked
  83. from multipart import __version__ # type: ignore
  84. assert __version__
  85. try:
  86. # parse_options_header is only available in the right multipart
  87. from multipart.multipart import parse_options_header # type: ignore
  88. assert parse_options_header
  89. except ImportError:
  90. logger.error(multipart_incorrect_install_error)
  91. raise RuntimeError(multipart_incorrect_install_error) from None
  92. except ImportError:
  93. logger.error(multipart_not_installed_error)
  94. raise RuntimeError(multipart_not_installed_error) from None
  95. def get_param_sub_dependant(
  96. *,
  97. param_name: str,
  98. depends: params.Depends,
  99. path: str,
  100. security_scopes: Optional[List[str]] = None,
  101. ) -> Dependant:
  102. assert depends.dependency
  103. return get_sub_dependant(
  104. depends=depends,
  105. dependency=depends.dependency,
  106. path=path,
  107. name=param_name,
  108. security_scopes=security_scopes,
  109. )
  110. def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
  111. assert callable(
  112. depends.dependency
  113. ), "A parameter-less dependency must have a callable dependency"
  114. return get_sub_dependant(depends=depends, dependency=depends.dependency, path=path)
  115. def get_sub_dependant(
  116. *,
  117. depends: params.Depends,
  118. dependency: Callable[..., Any],
  119. path: str,
  120. name: Optional[str] = None,
  121. security_scopes: Optional[List[str]] = None,
  122. ) -> Dependant:
  123. security_requirement = None
  124. security_scopes = security_scopes or []
  125. if isinstance(depends, params.Security):
  126. dependency_scopes = depends.scopes
  127. security_scopes.extend(dependency_scopes)
  128. if isinstance(dependency, SecurityBase):
  129. use_scopes: List[str] = []
  130. if isinstance(dependency, (OAuth2, OpenIdConnect)):
  131. use_scopes = security_scopes
  132. security_requirement = SecurityRequirement(
  133. security_scheme=dependency, scopes=use_scopes
  134. )
  135. sub_dependant = get_dependant(
  136. path=path,
  137. call=dependency,
  138. name=name,
  139. security_scopes=security_scopes,
  140. use_cache=depends.use_cache,
  141. )
  142. if security_requirement:
  143. sub_dependant.security_requirements.append(security_requirement)
  144. return sub_dependant
  145. CacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...]]
  146. def get_flat_dependant(
  147. dependant: Dependant,
  148. *,
  149. skip_repeats: bool = False,
  150. visited: Optional[List[CacheKey]] = None,
  151. ) -> Dependant:
  152. if visited is None:
  153. visited = []
  154. visited.append(dependant.cache_key)
  155. flat_dependant = Dependant(
  156. path_params=dependant.path_params.copy(),
  157. query_params=dependant.query_params.copy(),
  158. header_params=dependant.header_params.copy(),
  159. cookie_params=dependant.cookie_params.copy(),
  160. body_params=dependant.body_params.copy(),
  161. security_schemes=dependant.security_requirements.copy(),
  162. use_cache=dependant.use_cache,
  163. path=dependant.path,
  164. )
  165. for sub_dependant in dependant.dependencies:
  166. if skip_repeats and sub_dependant.cache_key in visited:
  167. continue
  168. flat_sub = get_flat_dependant(
  169. sub_dependant, skip_repeats=skip_repeats, visited=visited
  170. )
  171. flat_dependant.path_params.extend(flat_sub.path_params)
  172. flat_dependant.query_params.extend(flat_sub.query_params)
  173. flat_dependant.header_params.extend(flat_sub.header_params)
  174. flat_dependant.cookie_params.extend(flat_sub.cookie_params)
  175. flat_dependant.body_params.extend(flat_sub.body_params)
  176. flat_dependant.security_requirements.extend(flat_sub.security_requirements)
  177. return flat_dependant
  178. def get_flat_params(dependant: Dependant) -> List[ModelField]:
  179. flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
  180. return (
  181. flat_dependant.path_params
  182. + flat_dependant.query_params
  183. + flat_dependant.header_params
  184. + flat_dependant.cookie_params
  185. )
  186. def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
  187. signature = inspect.signature(call)
  188. globalns = getattr(call, "__globals__", {})
  189. typed_params = [
  190. inspect.Parameter(
  191. name=param.name,
  192. kind=param.kind,
  193. default=param.default,
  194. annotation=get_typed_annotation(param.annotation, globalns),
  195. )
  196. for param in signature.parameters.values()
  197. ]
  198. typed_signature = inspect.Signature(typed_params)
  199. return typed_signature
  200. def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:
  201. if isinstance(annotation, str):
  202. annotation = ForwardRef(annotation)
  203. annotation = evaluate_forwardref(annotation, globalns, globalns)
  204. return annotation
  205. def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
  206. signature = inspect.signature(call)
  207. annotation = signature.return_annotation
  208. if annotation is inspect.Signature.empty:
  209. return None
  210. globalns = getattr(call, "__globals__", {})
  211. return get_typed_annotation(annotation, globalns)
  212. def get_dependant(
  213. *,
  214. path: str,
  215. call: Callable[..., Any],
  216. name: Optional[str] = None,
  217. security_scopes: Optional[List[str]] = None,
  218. use_cache: bool = True,
  219. ) -> Dependant:
  220. path_param_names = get_path_param_names(path)
  221. endpoint_signature = get_typed_signature(call)
  222. signature_params = endpoint_signature.parameters
  223. dependant = Dependant(
  224. call=call,
  225. name=name,
  226. path=path,
  227. security_scopes=security_scopes,
  228. use_cache=use_cache,
  229. )
  230. for param_name, param in signature_params.items():
  231. is_path_param = param_name in path_param_names
  232. type_annotation, depends, param_field = analyze_param(
  233. param_name=param_name,
  234. annotation=param.annotation,
  235. value=param.default,
  236. is_path_param=is_path_param,
  237. )
  238. if depends is not None:
  239. sub_dependant = get_param_sub_dependant(
  240. param_name=param_name,
  241. depends=depends,
  242. path=path,
  243. security_scopes=security_scopes,
  244. )
  245. dependant.dependencies.append(sub_dependant)
  246. continue
  247. if add_non_field_param_to_dependency(
  248. param_name=param_name,
  249. type_annotation=type_annotation,
  250. dependant=dependant,
  251. ):
  252. assert (
  253. param_field is None
  254. ), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
  255. continue
  256. assert param_field is not None
  257. if is_body_param(param_field=param_field, is_path_param=is_path_param):
  258. dependant.body_params.append(param_field)
  259. else:
  260. add_param_to_fields(field=param_field, dependant=dependant)
  261. return dependant
  262. def add_non_field_param_to_dependency(
  263. *, param_name: str, type_annotation: Any, dependant: Dependant
  264. ) -> Optional[bool]:
  265. if lenient_issubclass(type_annotation, Request):
  266. dependant.request_param_name = param_name
  267. return True
  268. elif lenient_issubclass(type_annotation, WebSocket):
  269. dependant.websocket_param_name = param_name
  270. return True
  271. elif lenient_issubclass(type_annotation, HTTPConnection):
  272. dependant.http_connection_param_name = param_name
  273. return True
  274. elif lenient_issubclass(type_annotation, Response):
  275. dependant.response_param_name = param_name
  276. return True
  277. elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):
  278. dependant.background_tasks_param_name = param_name
  279. return True
  280. elif lenient_issubclass(type_annotation, SecurityScopes):
  281. dependant.security_scopes_param_name = param_name
  282. return True
  283. return None
  284. def analyze_param(
  285. *,
  286. param_name: str,
  287. annotation: Any,
  288. value: Any,
  289. is_path_param: bool,
  290. ) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]:
  291. field_info = None
  292. depends = None
  293. type_annotation: Any = Any
  294. use_annotation: Any = Any
  295. if annotation is not inspect.Signature.empty:
  296. use_annotation = annotation
  297. type_annotation = annotation
  298. if get_origin(use_annotation) is Annotated:
  299. annotated_args = get_args(annotation)
  300. type_annotation = annotated_args[0]
  301. fastapi_annotations = [
  302. arg
  303. for arg in annotated_args[1:]
  304. if isinstance(arg, (FieldInfo, params.Depends))
  305. ]
  306. fastapi_specific_annotations = [
  307. arg
  308. for arg in fastapi_annotations
  309. if isinstance(arg, (params.Param, params.Body, params.Depends))
  310. ]
  311. if fastapi_specific_annotations:
  312. fastapi_annotation: Union[
  313. FieldInfo, params.Depends, None
  314. ] = fastapi_specific_annotations[-1]
  315. else:
  316. fastapi_annotation = None
  317. if isinstance(fastapi_annotation, FieldInfo):
  318. # Copy `field_info` because we mutate `field_info.default` below.
  319. field_info = copy_field_info(
  320. field_info=fastapi_annotation, annotation=use_annotation
  321. )
  322. assert field_info.default is Undefined or field_info.default is Required, (
  323. f"`{field_info.__class__.__name__}` default value cannot be set in"
  324. f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
  325. )
  326. if value is not inspect.Signature.empty:
  327. assert not is_path_param, "Path parameters cannot have default values"
  328. field_info.default = value
  329. else:
  330. field_info.default = Required
  331. elif isinstance(fastapi_annotation, params.Depends):
  332. depends = fastapi_annotation
  333. if isinstance(value, params.Depends):
  334. assert depends is None, (
  335. "Cannot specify `Depends` in `Annotated` and default value"
  336. f" together for {param_name!r}"
  337. )
  338. assert field_info is None, (
  339. "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a"
  340. f" default value together for {param_name!r}"
  341. )
  342. depends = value
  343. elif isinstance(value, FieldInfo):
  344. assert field_info is None, (
  345. "Cannot specify FastAPI annotations in `Annotated` and default value"
  346. f" together for {param_name!r}"
  347. )
  348. field_info = value
  349. if PYDANTIC_V2:
  350. field_info.annotation = type_annotation
  351. if depends is not None and depends.dependency is None:
  352. depends.dependency = type_annotation
  353. if lenient_issubclass(
  354. type_annotation,
  355. (
  356. Request,
  357. WebSocket,
  358. HTTPConnection,
  359. Response,
  360. StarletteBackgroundTasks,
  361. SecurityScopes,
  362. ),
  363. ):
  364. assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}"
  365. assert (
  366. field_info is None
  367. ), f"Cannot specify FastAPI annotation for type {type_annotation!r}"
  368. elif field_info is None and depends is None:
  369. default_value = value if value is not inspect.Signature.empty else Required
  370. if is_path_param:
  371. # We might check here that `default_value is Required`, but the fact is that the same
  372. # parameter might sometimes be a path parameter and sometimes not. See
  373. # `tests/test_infer_param_optionality.py` for an example.
  374. field_info = params.Path(annotation=use_annotation)
  375. elif is_uploadfile_or_nonable_uploadfile_annotation(
  376. type_annotation
  377. ) or is_uploadfile_sequence_annotation(type_annotation):
  378. field_info = params.File(annotation=use_annotation, default=default_value)
  379. elif not field_annotation_is_scalar(annotation=type_annotation):
  380. field_info = params.Body(annotation=use_annotation, default=default_value)
  381. else:
  382. field_info = params.Query(annotation=use_annotation, default=default_value)
  383. field = None
  384. if field_info is not None:
  385. if is_path_param:
  386. assert isinstance(field_info, params.Path), (
  387. f"Cannot use `{field_info.__class__.__name__}` for path param"
  388. f" {param_name!r}"
  389. )
  390. elif (
  391. isinstance(field_info, params.Param)
  392. and getattr(field_info, "in_", None) is None
  393. ):
  394. field_info.in_ = params.ParamTypes.query
  395. use_annotation_from_field_info = get_annotation_from_field_info(
  396. use_annotation,
  397. field_info,
  398. param_name,
  399. )
  400. if not field_info.alias and getattr(field_info, "convert_underscores", None):
  401. alias = param_name.replace("_", "-")
  402. else:
  403. alias = field_info.alias or param_name
  404. field_info.alias = alias
  405. field = create_response_field(
  406. name=param_name,
  407. type_=use_annotation_from_field_info,
  408. default=field_info.default,
  409. alias=alias,
  410. required=field_info.default in (Required, Undefined),
  411. field_info=field_info,
  412. )
  413. return type_annotation, depends, field
  414. def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
  415. if is_path_param:
  416. assert is_scalar_field(
  417. field=param_field
  418. ), "Path params must be of one of the supported types"
  419. return False
  420. elif is_scalar_field(field=param_field):
  421. return False
  422. elif isinstance(
  423. param_field.field_info, (params.Query, params.Header)
  424. ) and is_scalar_sequence_field(param_field):
  425. return False
  426. else:
  427. assert isinstance(
  428. param_field.field_info, params.Body
  429. ), f"Param: {param_field.name} can only be a request body, using Body()"
  430. return True
  431. def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
  432. field_info = field.field_info
  433. field_info_in = getattr(field_info, "in_", None)
  434. if field_info_in == params.ParamTypes.path:
  435. dependant.path_params.append(field)
  436. elif field_info_in == params.ParamTypes.query:
  437. dependant.query_params.append(field)
  438. elif field_info_in == params.ParamTypes.header:
  439. dependant.header_params.append(field)
  440. else:
  441. assert (
  442. field_info_in == params.ParamTypes.cookie
  443. ), f"non-body parameters must be in path, query, header or cookie: {field.name}"
  444. dependant.cookie_params.append(field)
  445. def is_coroutine_callable(call: Callable[..., Any]) -> bool:
  446. if inspect.isroutine(call):
  447. return inspect.iscoroutinefunction(call)
  448. if inspect.isclass(call):
  449. return False
  450. dunder_call = getattr(call, "__call__", None) # noqa: B004
  451. return inspect.iscoroutinefunction(dunder_call)
  452. def is_async_gen_callable(call: Callable[..., Any]) -> bool:
  453. if inspect.isasyncgenfunction(call):
  454. return True
  455. dunder_call = getattr(call, "__call__", None) # noqa: B004
  456. return inspect.isasyncgenfunction(dunder_call)
  457. def is_gen_callable(call: Callable[..., Any]) -> bool:
  458. if inspect.isgeneratorfunction(call):
  459. return True
  460. dunder_call = getattr(call, "__call__", None) # noqa: B004
  461. return inspect.isgeneratorfunction(dunder_call)
  462. async def solve_generator(
  463. *, call: Callable[..., Any], stack: AsyncExitStack, sub_values: Dict[str, Any]
  464. ) -> Any:
  465. if is_gen_callable(call):
  466. cm = contextmanager_in_threadpool(contextmanager(call)(**sub_values))
  467. elif is_async_gen_callable(call):
  468. cm = asynccontextmanager(call)(**sub_values)
  469. return await stack.enter_async_context(cm)
  470. async def solve_dependencies(
  471. *,
  472. request: Union[Request, WebSocket],
  473. dependant: Dependant,
  474. body: Optional[Union[Dict[str, Any], FormData]] = None,
  475. background_tasks: Optional[StarletteBackgroundTasks] = None,
  476. response: Optional[Response] = None,
  477. dependency_overrides_provider: Optional[Any] = None,
  478. dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
  479. async_exit_stack: AsyncExitStack,
  480. ) -> Tuple[
  481. Dict[str, Any],
  482. List[Any],
  483. Optional[StarletteBackgroundTasks],
  484. Response,
  485. Dict[Tuple[Callable[..., Any], Tuple[str]], Any],
  486. ]:
  487. values: Dict[str, Any] = {}
  488. errors: List[Any] = []
  489. if response is None:
  490. response = Response()
  491. del response.headers["content-length"]
  492. response.status_code = None # type: ignore
  493. dependency_cache = dependency_cache or {}
  494. sub_dependant: Dependant
  495. for sub_dependant in dependant.dependencies:
  496. sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
  497. sub_dependant.cache_key = cast(
  498. Tuple[Callable[..., Any], Tuple[str]], sub_dependant.cache_key
  499. )
  500. call = sub_dependant.call
  501. use_sub_dependant = sub_dependant
  502. if (
  503. dependency_overrides_provider
  504. and dependency_overrides_provider.dependency_overrides
  505. ):
  506. original_call = sub_dependant.call
  507. call = getattr(
  508. dependency_overrides_provider, "dependency_overrides", {}
  509. ).get(original_call, original_call)
  510. use_path: str = sub_dependant.path # type: ignore
  511. use_sub_dependant = get_dependant(
  512. path=use_path,
  513. call=call,
  514. name=sub_dependant.name,
  515. security_scopes=sub_dependant.security_scopes,
  516. )
  517. solved_result = await solve_dependencies(
  518. request=request,
  519. dependant=use_sub_dependant,
  520. body=body,
  521. background_tasks=background_tasks,
  522. response=response,
  523. dependency_overrides_provider=dependency_overrides_provider,
  524. dependency_cache=dependency_cache,
  525. async_exit_stack=async_exit_stack,
  526. )
  527. (
  528. sub_values,
  529. sub_errors,
  530. background_tasks,
  531. _, # the subdependency returns the same response we have
  532. sub_dependency_cache,
  533. ) = solved_result
  534. dependency_cache.update(sub_dependency_cache)
  535. if sub_errors:
  536. errors.extend(sub_errors)
  537. continue
  538. if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
  539. solved = dependency_cache[sub_dependant.cache_key]
  540. elif is_gen_callable(call) or is_async_gen_callable(call):
  541. solved = await solve_generator(
  542. call=call, stack=async_exit_stack, sub_values=sub_values
  543. )
  544. elif is_coroutine_callable(call):
  545. solved = await call(**sub_values)
  546. else:
  547. solved = await run_in_threadpool(call, **sub_values)
  548. if sub_dependant.name is not None:
  549. values[sub_dependant.name] = solved
  550. if sub_dependant.cache_key not in dependency_cache:
  551. dependency_cache[sub_dependant.cache_key] = solved
  552. path_values, path_errors = request_params_to_args(
  553. dependant.path_params, request.path_params
  554. )
  555. query_values, query_errors = request_params_to_args(
  556. dependant.query_params, request.query_params
  557. )
  558. header_values, header_errors = request_params_to_args(
  559. dependant.header_params, request.headers
  560. )
  561. cookie_values, cookie_errors = request_params_to_args(
  562. dependant.cookie_params, request.cookies
  563. )
  564. values.update(path_values)
  565. values.update(query_values)
  566. values.update(header_values)
  567. values.update(cookie_values)
  568. errors += path_errors + query_errors + header_errors + cookie_errors
  569. if dependant.body_params:
  570. (
  571. body_values,
  572. body_errors,
  573. ) = await request_body_to_args( # body_params checked above
  574. required_params=dependant.body_params, received_body=body
  575. )
  576. values.update(body_values)
  577. errors.extend(body_errors)
  578. if dependant.http_connection_param_name:
  579. values[dependant.http_connection_param_name] = request
  580. if dependant.request_param_name and isinstance(request, Request):
  581. values[dependant.request_param_name] = request
  582. elif dependant.websocket_param_name and isinstance(request, WebSocket):
  583. values[dependant.websocket_param_name] = request
  584. if dependant.background_tasks_param_name:
  585. if background_tasks is None:
  586. background_tasks = BackgroundTasks()
  587. values[dependant.background_tasks_param_name] = background_tasks
  588. if dependant.response_param_name:
  589. values[dependant.response_param_name] = response
  590. if dependant.security_scopes_param_name:
  591. values[dependant.security_scopes_param_name] = SecurityScopes(
  592. scopes=dependant.security_scopes
  593. )
  594. return values, errors, background_tasks, response, dependency_cache
  595. def request_params_to_args(
  596. required_params: Sequence[ModelField],
  597. received_params: Union[Mapping[str, Any], QueryParams, Headers],
  598. ) -> Tuple[Dict[str, Any], List[Any]]:
  599. values = {}
  600. errors = []
  601. for field in required_params:
  602. if is_scalar_sequence_field(field) and isinstance(
  603. received_params, (QueryParams, Headers)
  604. ):
  605. value = received_params.getlist(field.alias) or field.default
  606. else:
  607. value = received_params.get(field.alias)
  608. field_info = field.field_info
  609. assert isinstance(
  610. field_info, params.Param
  611. ), "Params must be subclasses of Param"
  612. loc = (field_info.in_.value, field.alias)
  613. if value is None:
  614. if field.required:
  615. errors.append(get_missing_field_error(loc=loc))
  616. else:
  617. values[field.name] = deepcopy(field.default)
  618. continue
  619. v_, errors_ = field.validate(value, values, loc=loc)
  620. if isinstance(errors_, ErrorWrapper):
  621. errors.append(errors_)
  622. elif isinstance(errors_, list):
  623. new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
  624. errors.extend(new_errors)
  625. else:
  626. values[field.name] = v_
  627. return values, errors
  628. async def request_body_to_args(
  629. required_params: List[ModelField],
  630. received_body: Optional[Union[Dict[str, Any], FormData]],
  631. ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
  632. values = {}
  633. errors: List[Dict[str, Any]] = []
  634. if required_params:
  635. field = required_params[0]
  636. field_info = field.field_info
  637. embed = getattr(field_info, "embed", None)
  638. field_alias_omitted = len(required_params) == 1 and not embed
  639. if field_alias_omitted:
  640. received_body = {field.alias: received_body}
  641. for field in required_params:
  642. loc: Tuple[str, ...]
  643. if field_alias_omitted:
  644. loc = ("body",)
  645. else:
  646. loc = ("body", field.alias)
  647. value: Optional[Any] = None
  648. if received_body is not None:
  649. if (is_sequence_field(field)) and isinstance(received_body, FormData):
  650. value = received_body.getlist(field.alias)
  651. else:
  652. try:
  653. value = received_body.get(field.alias)
  654. except AttributeError:
  655. errors.append(get_missing_field_error(loc))
  656. continue
  657. if (
  658. value is None
  659. or (isinstance(field_info, params.Form) and value == "")
  660. or (
  661. isinstance(field_info, params.Form)
  662. and is_sequence_field(field)
  663. and len(value) == 0
  664. )
  665. ):
  666. if field.required:
  667. errors.append(get_missing_field_error(loc))
  668. else:
  669. values[field.name] = deepcopy(field.default)
  670. continue
  671. if (
  672. isinstance(field_info, params.File)
  673. and is_bytes_field(field)
  674. and isinstance(value, UploadFile)
  675. ):
  676. value = await value.read()
  677. elif (
  678. is_bytes_sequence_field(field)
  679. and isinstance(field_info, params.File)
  680. and value_is_sequence(value)
  681. ):
  682. # For types
  683. assert isinstance(value, sequence_types) # type: ignore[arg-type]
  684. results: List[Union[bytes, str]] = []
  685. async def process_fn(
  686. fn: Callable[[], Coroutine[Any, Any, Any]]
  687. ) -> None:
  688. result = await fn()
  689. results.append(result) # noqa: B023
  690. async with anyio.create_task_group() as tg:
  691. for sub_value in value:
  692. tg.start_soon(process_fn, sub_value.read)
  693. value = serialize_sequence_value(field=field, value=results)
  694. v_, errors_ = field.validate(value, values, loc=loc)
  695. if isinstance(errors_, list):
  696. errors.extend(errors_)
  697. elif errors_:
  698. errors.append(errors_)
  699. else:
  700. values[field.name] = v_
  701. return values, errors
  702. def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
  703. flat_dependant = get_flat_dependant(dependant)
  704. if not flat_dependant.body_params:
  705. return None
  706. first_param = flat_dependant.body_params[0]
  707. field_info = first_param.field_info
  708. embed = getattr(field_info, "embed", None)
  709. body_param_names_set = {param.name for param in flat_dependant.body_params}
  710. if len(body_param_names_set) == 1 and not embed:
  711. check_file_field(first_param)
  712. return first_param
  713. # If one field requires to embed, all have to be embedded
  714. # in case a sub-dependency is evaluated with a single unique body field
  715. # That is combined (embedded) with other body fields
  716. for param in flat_dependant.body_params:
  717. setattr(param.field_info, "embed", True) # noqa: B010
  718. model_name = "Body_" + name
  719. BodyModel = create_body_model(
  720. fields=flat_dependant.body_params, model_name=model_name
  721. )
  722. required = any(True for f in flat_dependant.body_params if f.required)
  723. BodyFieldInfo_kwargs: Dict[str, Any] = {
  724. "annotation": BodyModel,
  725. "alias": "body",
  726. }
  727. if not required:
  728. BodyFieldInfo_kwargs["default"] = None
  729. if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
  730. BodyFieldInfo: Type[params.Body] = params.File
  731. elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
  732. BodyFieldInfo = params.Form
  733. else:
  734. BodyFieldInfo = params.Body
  735. body_param_media_types = [
  736. f.field_info.media_type
  737. for f in flat_dependant.body_params
  738. if isinstance(f.field_info, params.Body)
  739. ]
  740. if len(set(body_param_media_types)) == 1:
  741. BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
  742. final_field = create_response_field(
  743. name="body",
  744. type_=BodyModel,
  745. required=required,
  746. alias="body",
  747. field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
  748. )
  749. check_file_field(final_field)
  750. return final_field