utils.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. import dataclasses
  2. import inspect
  3. import sys
  4. from collections.abc import (
  5. AsyncGenerator,
  6. AsyncIterable,
  7. AsyncIterator,
  8. Callable,
  9. Generator,
  10. Iterable,
  11. Iterator,
  12. Mapping,
  13. Sequence,
  14. )
  15. from contextlib import AsyncExitStack, contextmanager
  16. from copy import copy, deepcopy
  17. from dataclasses import dataclass
  18. from typing import (
  19. Annotated,
  20. Any,
  21. ForwardRef,
  22. Literal,
  23. Union,
  24. cast,
  25. get_args,
  26. get_origin,
  27. )
  28. from fastapi import params
  29. from fastapi._compat import (
  30. ModelField,
  31. RequiredParam,
  32. Undefined,
  33. copy_field_info,
  34. create_body_model,
  35. evaluate_forwardref, # ty: ignore[deprecated]
  36. field_annotation_is_scalar,
  37. field_annotation_is_scalar_sequence,
  38. field_annotation_is_sequence,
  39. get_cached_model_fields,
  40. get_missing_field_error,
  41. is_bytes_or_nonable_bytes_annotation,
  42. is_bytes_sequence_annotation,
  43. is_scalar_field,
  44. is_uploadfile_or_nonable_uploadfile_annotation,
  45. is_uploadfile_sequence_annotation,
  46. lenient_issubclass,
  47. sequence_types,
  48. serialize_sequence_value,
  49. value_is_sequence,
  50. )
  51. from fastapi.background import BackgroundTasks
  52. from fastapi.concurrency import (
  53. asynccontextmanager,
  54. contextmanager_in_threadpool,
  55. )
  56. from fastapi.dependencies.models import Dependant
  57. from fastapi.exceptions import DependencyScopeError
  58. from fastapi.logger import logger
  59. from fastapi.security.oauth2 import SecurityScopes
  60. from fastapi.types import DependencyCacheKey
  61. from fastapi.utils import create_model_field, get_path_param_names
  62. from pydantic import BaseModel, Json
  63. from pydantic.fields import FieldInfo
  64. from starlette.background import BackgroundTasks as StarletteBackgroundTasks
  65. from starlette.concurrency import run_in_threadpool
  66. from starlette.datastructures import (
  67. FormData,
  68. Headers,
  69. ImmutableMultiDict,
  70. QueryParams,
  71. UploadFile,
  72. )
  73. from starlette.requests import HTTPConnection, Request
  74. from starlette.responses import Response
  75. from starlette.websockets import WebSocket
  76. from typing_inspection.typing_objects import is_typealiastype
  77. multipart_not_installed_error = (
  78. 'Form data requires "python-multipart" to be installed. \n'
  79. 'You can install "python-multipart" with: \n\n'
  80. "pip install python-multipart\n"
  81. )
  82. multipart_incorrect_install_error = (
  83. 'Form data requires "python-multipart" to be installed. '
  84. 'It seems you installed "multipart" instead. \n'
  85. 'You can remove "multipart" with: \n\n'
  86. "pip uninstall multipart\n\n"
  87. 'And then install "python-multipart" with: \n\n'
  88. "pip install python-multipart\n"
  89. )
  90. def ensure_multipart_is_installed() -> None:
  91. try:
  92. from python_multipart import __version__
  93. # Import an attribute that can be mocked/deleted in testing
  94. assert __version__ > "0.0.12"
  95. except (ImportError, AssertionError):
  96. try:
  97. # __version__ is available in both multiparts, and can be mocked
  98. from multipart import ( # type: ignore[no-redef,import-untyped] # ty: ignore[unused-ignore-comment]
  99. __version__,
  100. )
  101. assert __version__
  102. try:
  103. # parse_options_header is only available in the right multipart
  104. from multipart.multipart import ( # type: ignore[import-untyped] # ty: ignore[unused-ignore-comment]
  105. parse_options_header,
  106. )
  107. assert parse_options_header
  108. except ImportError:
  109. logger.error(multipart_incorrect_install_error)
  110. raise RuntimeError(multipart_incorrect_install_error) from None
  111. except ImportError:
  112. logger.error(multipart_not_installed_error)
  113. raise RuntimeError(multipart_not_installed_error) from None
  114. def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
  115. assert callable(depends.dependency), (
  116. "A parameter-less dependency must have a callable dependency"
  117. )
  118. own_oauth_scopes: list[str] = []
  119. if isinstance(depends, params.Security) and depends.scopes:
  120. own_oauth_scopes.extend(depends.scopes)
  121. return get_dependant(
  122. path=path,
  123. call=depends.dependency,
  124. scope=depends.scope,
  125. own_oauth_scopes=own_oauth_scopes,
  126. )
  127. def get_flat_dependant(
  128. dependant: Dependant,
  129. *,
  130. skip_repeats: bool = False,
  131. visited: list[DependencyCacheKey] | None = None,
  132. parent_oauth_scopes: list[str] | None = None,
  133. ) -> Dependant:
  134. if visited is None:
  135. visited = []
  136. visited.append(dependant.cache_key)
  137. use_parent_oauth_scopes = (parent_oauth_scopes or []) + (
  138. dependant.oauth_scopes or []
  139. )
  140. flat_dependant = Dependant(
  141. path_params=dependant.path_params.copy(),
  142. query_params=dependant.query_params.copy(),
  143. header_params=dependant.header_params.copy(),
  144. cookie_params=dependant.cookie_params.copy(),
  145. body_params=dependant.body_params.copy(),
  146. name=dependant.name,
  147. call=dependant.call,
  148. request_param_name=dependant.request_param_name,
  149. websocket_param_name=dependant.websocket_param_name,
  150. http_connection_param_name=dependant.http_connection_param_name,
  151. response_param_name=dependant.response_param_name,
  152. background_tasks_param_name=dependant.background_tasks_param_name,
  153. security_scopes_param_name=dependant.security_scopes_param_name,
  154. own_oauth_scopes=dependant.own_oauth_scopes,
  155. parent_oauth_scopes=use_parent_oauth_scopes,
  156. use_cache=dependant.use_cache,
  157. path=dependant.path,
  158. scope=dependant.scope,
  159. )
  160. for sub_dependant in dependant.dependencies:
  161. if skip_repeats and sub_dependant.cache_key in visited:
  162. continue
  163. flat_sub = get_flat_dependant(
  164. sub_dependant,
  165. skip_repeats=skip_repeats,
  166. visited=visited,
  167. parent_oauth_scopes=flat_dependant.oauth_scopes,
  168. )
  169. flat_dependant.dependencies.append(flat_sub)
  170. flat_dependant.path_params.extend(flat_sub.path_params)
  171. flat_dependant.query_params.extend(flat_sub.query_params)
  172. flat_dependant.header_params.extend(flat_sub.header_params)
  173. flat_dependant.cookie_params.extend(flat_sub.cookie_params)
  174. flat_dependant.body_params.extend(flat_sub.body_params)
  175. flat_dependant.dependencies.extend(flat_sub.dependencies)
  176. return flat_dependant
  177. def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]:
  178. if not fields:
  179. return fields
  180. first_field = fields[0]
  181. if len(fields) == 1 and lenient_issubclass(
  182. first_field.field_info.annotation, BaseModel
  183. ):
  184. fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
  185. return fields_to_extract
  186. return fields
  187. def get_flat_params(dependant: Dependant) -> list[ModelField]:
  188. flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
  189. path_params = _get_flat_fields_from_params(flat_dependant.path_params)
  190. query_params = _get_flat_fields_from_params(flat_dependant.query_params)
  191. header_params = _get_flat_fields_from_params(flat_dependant.header_params)
  192. cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)
  193. return path_params + query_params + header_params + cookie_params
  194. def _get_signature(call: Callable[..., Any]) -> inspect.Signature:
  195. try:
  196. signature = inspect.signature(call, eval_str=True)
  197. except NameError:
  198. # Handle type annotations with if TYPE_CHECKING, not used by FastAPI
  199. # e.g. dependency return types
  200. if sys.version_info >= (3, 14):
  201. from annotationlib import Format
  202. signature = inspect.signature(call, annotation_format=Format.FORWARDREF)
  203. else:
  204. signature = inspect.signature(call)
  205. return signature
  206. def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
  207. signature = _get_signature(call)
  208. unwrapped = inspect.unwrap(call)
  209. globalns = getattr(unwrapped, "__globals__", {})
  210. typed_params = [
  211. inspect.Parameter(
  212. name=param.name,
  213. kind=param.kind,
  214. default=param.default,
  215. annotation=get_typed_annotation(param.annotation, globalns),
  216. )
  217. for param in signature.parameters.values()
  218. ]
  219. typed_signature = inspect.Signature(typed_params)
  220. return typed_signature
  221. def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
  222. if isinstance(annotation, str):
  223. annotation = ForwardRef(annotation)
  224. annotation = evaluate_forwardref(annotation, globalns, globalns) # ty: ignore[deprecated]
  225. if annotation is type(None):
  226. return None
  227. return annotation
  228. def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
  229. signature = _get_signature(call)
  230. unwrapped = inspect.unwrap(call)
  231. annotation = signature.return_annotation
  232. if annotation is inspect.Signature.empty:
  233. return None
  234. globalns = getattr(unwrapped, "__globals__", {})
  235. return get_typed_annotation(annotation, globalns)
  236. _STREAM_ORIGINS = {
  237. AsyncIterable,
  238. AsyncIterator,
  239. AsyncGenerator,
  240. Iterable,
  241. Iterator,
  242. Generator,
  243. }
  244. def get_stream_item_type(annotation: Any) -> Any | None:
  245. origin = get_origin(annotation)
  246. if origin is not None and origin in _STREAM_ORIGINS:
  247. type_args = get_args(annotation)
  248. if type_args:
  249. return type_args[0]
  250. return Any
  251. return None
  252. def get_dependant(
  253. *,
  254. path: str,
  255. call: Callable[..., Any],
  256. name: str | None = None,
  257. own_oauth_scopes: list[str] | None = None,
  258. parent_oauth_scopes: list[str] | None = None,
  259. use_cache: bool = True,
  260. scope: Literal["function", "request"] | None = None,
  261. ) -> Dependant:
  262. dependant = Dependant(
  263. call=call,
  264. name=name,
  265. path=path,
  266. use_cache=use_cache,
  267. scope=scope,
  268. own_oauth_scopes=own_oauth_scopes,
  269. parent_oauth_scopes=parent_oauth_scopes,
  270. )
  271. current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])
  272. path_param_names = get_path_param_names(path)
  273. endpoint_signature = get_typed_signature(call)
  274. signature_params = endpoint_signature.parameters
  275. for param_name, param in signature_params.items():
  276. is_path_param = param_name in path_param_names
  277. param_details = analyze_param(
  278. param_name=param_name,
  279. annotation=param.annotation,
  280. value=param.default,
  281. is_path_param=is_path_param,
  282. )
  283. if param_details.depends is not None:
  284. assert param_details.depends.dependency
  285. if (
  286. (dependant.is_gen_callable or dependant.is_async_gen_callable)
  287. and dependant.computed_scope == "request"
  288. and param_details.depends.scope == "function"
  289. ):
  290. assert dependant.call
  291. call_name = getattr(dependant.call, "__name__", "<unnamed_callable>")
  292. raise DependencyScopeError(
  293. f'The dependency "{call_name}" has a scope of '
  294. '"request", it cannot depend on dependencies with scope "function".'
  295. )
  296. sub_own_oauth_scopes: list[str] = []
  297. if isinstance(param_details.depends, params.Security):
  298. if param_details.depends.scopes:
  299. sub_own_oauth_scopes = list(param_details.depends.scopes)
  300. sub_dependant = get_dependant(
  301. path=path,
  302. call=param_details.depends.dependency,
  303. name=param_name,
  304. own_oauth_scopes=sub_own_oauth_scopes,
  305. parent_oauth_scopes=current_scopes,
  306. use_cache=param_details.depends.use_cache,
  307. scope=param_details.depends.scope,
  308. )
  309. dependant.dependencies.append(sub_dependant)
  310. continue
  311. if add_non_field_param_to_dependency(
  312. param_name=param_name,
  313. type_annotation=param_details.type_annotation,
  314. dependant=dependant,
  315. ):
  316. assert param_details.field is None, (
  317. f"Cannot specify multiple FastAPI annotations for {param_name!r}"
  318. )
  319. continue
  320. assert param_details.field is not None
  321. if isinstance(param_details.field.field_info, params.Body):
  322. dependant.body_params.append(param_details.field)
  323. else:
  324. add_param_to_fields(field=param_details.field, dependant=dependant)
  325. return dependant
  326. def add_non_field_param_to_dependency(
  327. *, param_name: str, type_annotation: Any, dependant: Dependant
  328. ) -> bool | None:
  329. if lenient_issubclass(type_annotation, Request):
  330. dependant.request_param_name = param_name
  331. return True
  332. elif lenient_issubclass(type_annotation, WebSocket):
  333. dependant.websocket_param_name = param_name
  334. return True
  335. elif lenient_issubclass(type_annotation, HTTPConnection):
  336. dependant.http_connection_param_name = param_name
  337. return True
  338. elif lenient_issubclass(type_annotation, Response):
  339. dependant.response_param_name = param_name
  340. return True
  341. elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):
  342. dependant.background_tasks_param_name = param_name
  343. return True
  344. elif lenient_issubclass(type_annotation, SecurityScopes):
  345. dependant.security_scopes_param_name = param_name
  346. return True
  347. return None
  348. @dataclass
  349. class ParamDetails:
  350. type_annotation: Any
  351. depends: params.Depends | None
  352. field: ModelField | None
  353. def analyze_param(
  354. *,
  355. param_name: str,
  356. annotation: Any,
  357. value: Any,
  358. is_path_param: bool,
  359. ) -> ParamDetails:
  360. field_info = None
  361. depends = None
  362. type_annotation: Any = Any
  363. use_annotation: Any = Any
  364. if is_typealiastype(annotation):
  365. # unpack in case PEP 695 type syntax is used
  366. annotation = annotation.__value__
  367. if annotation is not inspect.Signature.empty:
  368. use_annotation = annotation
  369. type_annotation = annotation
  370. # Extract Annotated info
  371. if get_origin(use_annotation) is Annotated:
  372. annotated_args = get_args(annotation)
  373. type_annotation = annotated_args[0]
  374. fastapi_annotations = [
  375. arg
  376. for arg in annotated_args[1:]
  377. if isinstance(arg, (FieldInfo, params.Depends))
  378. ]
  379. fastapi_specific_annotations = [
  380. arg
  381. for arg in fastapi_annotations
  382. if isinstance(
  383. arg,
  384. (
  385. params.Param,
  386. params.Body,
  387. params.Depends,
  388. ),
  389. )
  390. ]
  391. if fastapi_specific_annotations:
  392. fastapi_annotation: FieldInfo | params.Depends | None = (
  393. fastapi_specific_annotations[-1]
  394. )
  395. else:
  396. fastapi_annotation = None
  397. # Set default for Annotated FieldInfo
  398. if isinstance(fastapi_annotation, FieldInfo):
  399. # Copy `field_info` because we mutate `field_info.default` below.
  400. field_info = copy_field_info(
  401. field_info=fastapi_annotation,
  402. annotation=use_annotation,
  403. )
  404. assert (
  405. field_info.default == Undefined or field_info.default == RequiredParam
  406. ), (
  407. f"`{field_info.__class__.__name__}` default value cannot be set in"
  408. f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
  409. )
  410. if value is not inspect.Signature.empty:
  411. assert not is_path_param, "Path parameters cannot have default values"
  412. field_info.default = value
  413. else:
  414. field_info.default = RequiredParam
  415. # Get Annotated Depends
  416. elif isinstance(fastapi_annotation, params.Depends):
  417. depends = fastapi_annotation
  418. # Get Depends from default value
  419. if isinstance(value, params.Depends):
  420. assert depends is None, (
  421. "Cannot specify `Depends` in `Annotated` and default value"
  422. f" together for {param_name!r}"
  423. )
  424. assert field_info is None, (
  425. "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a"
  426. f" default value together for {param_name!r}"
  427. )
  428. depends = value
  429. # Get FieldInfo from default value
  430. elif isinstance(value, FieldInfo):
  431. assert field_info is None, (
  432. "Cannot specify FastAPI annotations in `Annotated` and default value"
  433. f" together for {param_name!r}"
  434. )
  435. field_info = value
  436. if isinstance(field_info, FieldInfo):
  437. field_info.annotation = type_annotation
  438. # Get Depends from type annotation
  439. if depends is not None and depends.dependency is None:
  440. # Copy `depends` before mutating it
  441. depends = copy(depends)
  442. depends = dataclasses.replace(depends, dependency=type_annotation)
  443. # Handle non-param type annotations like Request
  444. # Only apply special handling when there's no explicit Depends - if there's a Depends,
  445. # the dependency will be called and its return value used instead of the special injection
  446. if depends is None and lenient_issubclass(
  447. type_annotation,
  448. (
  449. Request,
  450. WebSocket,
  451. HTTPConnection,
  452. Response,
  453. StarletteBackgroundTasks,
  454. SecurityScopes,
  455. ),
  456. ):
  457. assert field_info is None, (
  458. f"Cannot specify FastAPI annotation for type {type_annotation!r}"
  459. )
  460. # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
  461. elif field_info is None and depends is None:
  462. default_value = value if value is not inspect.Signature.empty else RequiredParam
  463. if is_path_param:
  464. # We might check here that `default_value is RequiredParam`, but the fact is that the same
  465. # parameter might sometimes be a path parameter and sometimes not. See
  466. # `tests/test_infer_param_optionality.py` for an example.
  467. field_info = params.Path(annotation=use_annotation)
  468. elif is_uploadfile_or_nonable_uploadfile_annotation(
  469. type_annotation
  470. ) or is_uploadfile_sequence_annotation(type_annotation):
  471. field_info = params.File(annotation=use_annotation, default=default_value)
  472. elif not field_annotation_is_scalar(annotation=type_annotation):
  473. field_info = params.Body(annotation=use_annotation, default=default_value)
  474. else:
  475. field_info = params.Query(annotation=use_annotation, default=default_value)
  476. field = None
  477. # It's a field_info, not a dependency
  478. if field_info is not None:
  479. # Handle field_info.in_
  480. if is_path_param:
  481. assert isinstance(field_info, params.Path), (
  482. f"Cannot use `{field_info.__class__.__name__}` for path param"
  483. f" {param_name!r}"
  484. )
  485. elif (
  486. isinstance(field_info, params.Param)
  487. and getattr(field_info, "in_", None) is None
  488. ):
  489. field_info.in_ = params.ParamTypes.query
  490. use_annotation_from_field_info = use_annotation
  491. if isinstance(field_info, params.Form):
  492. ensure_multipart_is_installed()
  493. if not field_info.alias and getattr(field_info, "convert_underscores", None):
  494. alias = param_name.replace("_", "-")
  495. else:
  496. alias = field_info.alias or param_name
  497. field_info.alias = alias
  498. field = create_model_field(
  499. name=param_name,
  500. type_=use_annotation_from_field_info,
  501. default=field_info.default,
  502. alias=alias,
  503. field_info=field_info,
  504. )
  505. if is_path_param:
  506. assert is_scalar_field(field=field), (
  507. "Path params must be of one of the supported types"
  508. )
  509. elif isinstance(field_info, params.Query):
  510. assert (
  511. is_scalar_field(field)
  512. or field_annotation_is_scalar_sequence(field.field_info.annotation)
  513. or lenient_issubclass(field.field_info.annotation, BaseModel)
  514. ), f"Query parameter {param_name!r} must be one of the supported types"
  515. return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
  516. def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
  517. field_info = field.field_info
  518. field_info_in = getattr(field_info, "in_", None)
  519. if field_info_in == params.ParamTypes.path:
  520. dependant.path_params.append(field)
  521. elif field_info_in == params.ParamTypes.query:
  522. dependant.query_params.append(field)
  523. elif field_info_in == params.ParamTypes.header:
  524. dependant.header_params.append(field)
  525. else:
  526. assert field_info_in == params.ParamTypes.cookie, (
  527. f"non-body parameters must be in path, query, header or cookie: {field.name}"
  528. )
  529. dependant.cookie_params.append(field)
  530. async def _solve_generator(
  531. *, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]
  532. ) -> Any:
  533. assert dependant.call
  534. if dependant.is_async_gen_callable:
  535. cm = asynccontextmanager(dependant.call)(**sub_values)
  536. elif dependant.is_gen_callable:
  537. cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))
  538. return await stack.enter_async_context(cm)
  539. @dataclass
  540. class SolvedDependency:
  541. values: dict[str, Any]
  542. errors: list[Any]
  543. background_tasks: StarletteBackgroundTasks | None
  544. response: Response
  545. dependency_cache: dict[DependencyCacheKey, Any]
  546. async def solve_dependencies(
  547. *,
  548. request: Request | WebSocket,
  549. dependant: Dependant,
  550. body: dict[str, Any] | FormData | bytes | None = None,
  551. background_tasks: StarletteBackgroundTasks | None = None,
  552. response: Response | None = None,
  553. dependency_overrides_provider: Any | None = None,
  554. dependency_cache: dict[DependencyCacheKey, Any] | None = None,
  555. # TODO: remove this parameter later, no longer used, not removing it yet as some
  556. # people might be monkey patching this function (although that's not supported)
  557. async_exit_stack: AsyncExitStack,
  558. embed_body_fields: bool,
  559. ) -> SolvedDependency:
  560. request_astack = request.scope.get("fastapi_inner_astack")
  561. assert isinstance(request_astack, AsyncExitStack), (
  562. "fastapi_inner_astack not found in request scope"
  563. )
  564. function_astack = request.scope.get("fastapi_function_astack")
  565. assert isinstance(function_astack, AsyncExitStack), (
  566. "fastapi_function_astack not found in request scope"
  567. )
  568. values: dict[str, Any] = {}
  569. errors: list[Any] = []
  570. if response is None:
  571. response = Response()
  572. del response.headers["content-length"]
  573. response.status_code = None # type: ignore # ty: ignore[unused-ignore-comment]
  574. if dependency_cache is None:
  575. dependency_cache = {}
  576. for sub_dependant in dependant.dependencies:
  577. sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
  578. call = sub_dependant.call
  579. use_sub_dependant = sub_dependant
  580. if (
  581. dependency_overrides_provider
  582. and dependency_overrides_provider.dependency_overrides
  583. ):
  584. original_call = sub_dependant.call
  585. call = getattr(
  586. dependency_overrides_provider, "dependency_overrides", {}
  587. ).get(original_call, original_call)
  588. use_path: str = sub_dependant.path # type: ignore
  589. use_sub_dependant = get_dependant(
  590. path=use_path,
  591. call=call,
  592. name=sub_dependant.name,
  593. parent_oauth_scopes=sub_dependant.oauth_scopes,
  594. scope=sub_dependant.scope,
  595. )
  596. solved_result = await solve_dependencies(
  597. request=request,
  598. dependant=use_sub_dependant,
  599. body=body,
  600. background_tasks=background_tasks,
  601. response=response,
  602. dependency_overrides_provider=dependency_overrides_provider,
  603. dependency_cache=dependency_cache,
  604. async_exit_stack=async_exit_stack,
  605. embed_body_fields=embed_body_fields,
  606. )
  607. background_tasks = solved_result.background_tasks
  608. if solved_result.errors:
  609. errors.extend(solved_result.errors)
  610. continue
  611. if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
  612. solved = dependency_cache[sub_dependant.cache_key]
  613. elif (
  614. use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable
  615. ):
  616. use_astack = request_astack
  617. if sub_dependant.scope == "function":
  618. use_astack = function_astack
  619. solved = await _solve_generator(
  620. dependant=use_sub_dependant,
  621. stack=use_astack,
  622. sub_values=solved_result.values,
  623. )
  624. elif use_sub_dependant.is_coroutine_callable:
  625. solved = await call(**solved_result.values)
  626. else:
  627. solved = await run_in_threadpool(call, **solved_result.values)
  628. if sub_dependant.name is not None:
  629. values[sub_dependant.name] = solved
  630. if sub_dependant.cache_key not in dependency_cache:
  631. dependency_cache[sub_dependant.cache_key] = solved
  632. path_values, path_errors = request_params_to_args(
  633. dependant.path_params, request.path_params
  634. )
  635. query_values, query_errors = request_params_to_args(
  636. dependant.query_params, request.query_params
  637. )
  638. header_values, header_errors = request_params_to_args(
  639. dependant.header_params, request.headers
  640. )
  641. cookie_values, cookie_errors = request_params_to_args(
  642. dependant.cookie_params, request.cookies
  643. )
  644. values.update(path_values)
  645. values.update(query_values)
  646. values.update(header_values)
  647. values.update(cookie_values)
  648. errors += path_errors + query_errors + header_errors + cookie_errors
  649. if dependant.body_params:
  650. (
  651. body_values,
  652. body_errors,
  653. ) = await request_body_to_args( # body_params checked above
  654. body_fields=dependant.body_params,
  655. received_body=body,
  656. embed_body_fields=embed_body_fields,
  657. )
  658. values.update(body_values)
  659. errors.extend(body_errors)
  660. if dependant.http_connection_param_name:
  661. values[dependant.http_connection_param_name] = request
  662. if dependant.request_param_name and isinstance(request, Request):
  663. values[dependant.request_param_name] = request
  664. elif dependant.websocket_param_name and isinstance(request, WebSocket):
  665. values[dependant.websocket_param_name] = request
  666. if dependant.background_tasks_param_name:
  667. if background_tasks is None:
  668. background_tasks = BackgroundTasks()
  669. values[dependant.background_tasks_param_name] = background_tasks
  670. if dependant.response_param_name:
  671. values[dependant.response_param_name] = response
  672. if dependant.security_scopes_param_name:
  673. values[dependant.security_scopes_param_name] = SecurityScopes(
  674. scopes=dependant.oauth_scopes
  675. )
  676. return SolvedDependency(
  677. values=values,
  678. errors=errors,
  679. background_tasks=background_tasks,
  680. response=response,
  681. dependency_cache=dependency_cache,
  682. )
  683. def _validate_value_with_model_field(
  684. *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...]
  685. ) -> tuple[Any, list[Any]]:
  686. if value is None:
  687. if field.field_info.is_required():
  688. return None, [get_missing_field_error(loc=loc)]
  689. else:
  690. return deepcopy(field.default), []
  691. return field.validate(value, values, loc=loc)
  692. def _is_json_field(field: ModelField) -> bool:
  693. return any(type(item) is Json for item in field.field_info.metadata)
  694. def _get_multidict_value(
  695. field: ModelField, values: Mapping[str, Any], alias: str | None = None
  696. ) -> Any:
  697. alias = alias or get_validation_alias(field)
  698. if (
  699. (not _is_json_field(field))
  700. and field_annotation_is_sequence(field.field_info.annotation)
  701. and isinstance(values, (ImmutableMultiDict, Headers))
  702. ):
  703. value = values.getlist(alias)
  704. else:
  705. value = values.get(alias, None)
  706. if (
  707. value is None
  708. or (
  709. isinstance(field.field_info, params.Form)
  710. and isinstance(value, str) # For type checks
  711. and value == ""
  712. )
  713. or (
  714. field_annotation_is_sequence(field.field_info.annotation)
  715. and len(value) == 0
  716. )
  717. ):
  718. if field.field_info.is_required():
  719. return
  720. else:
  721. return deepcopy(field.default)
  722. return value
  723. def request_params_to_args(
  724. fields: Sequence[ModelField],
  725. received_params: Mapping[str, Any] | QueryParams | Headers,
  726. ) -> tuple[dict[str, Any], list[Any]]:
  727. values: dict[str, Any] = {}
  728. errors: list[dict[str, Any]] = []
  729. if not fields:
  730. return values, errors
  731. first_field = fields[0]
  732. fields_to_extract = fields
  733. single_not_embedded_field = False
  734. default_convert_underscores = True
  735. if len(fields) == 1 and lenient_issubclass(
  736. first_field.field_info.annotation, BaseModel
  737. ):
  738. fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
  739. single_not_embedded_field = True
  740. # If headers are in a Pydantic model, the way to disable convert_underscores
  741. # would be with Header(convert_underscores=False) at the Pydantic model level
  742. default_convert_underscores = getattr(
  743. first_field.field_info, "convert_underscores", True
  744. )
  745. params_to_process: dict[str, Any] = {}
  746. processed_keys = set()
  747. for field in fields_to_extract:
  748. alias = None
  749. if isinstance(received_params, Headers):
  750. # Handle fields extracted from a Pydantic Model for a header, each field
  751. # doesn't have a FieldInfo of type Header with the default convert_underscores=True
  752. convert_underscores = getattr(
  753. field.field_info, "convert_underscores", default_convert_underscores
  754. )
  755. if convert_underscores:
  756. alias = get_validation_alias(field)
  757. if alias == field.name:
  758. alias = alias.replace("_", "-")
  759. value = _get_multidict_value(field, received_params, alias=alias)
  760. if value is not None:
  761. params_to_process[get_validation_alias(field)] = value
  762. processed_keys.add(alias or get_validation_alias(field))
  763. for key in received_params.keys():
  764. if key not in processed_keys:
  765. if isinstance(received_params, (ImmutableMultiDict, Headers)):
  766. value = received_params.getlist(key)
  767. if isinstance(value, list) and (len(value) == 1):
  768. params_to_process[key] = value[0]
  769. else:
  770. params_to_process[key] = value
  771. else:
  772. params_to_process[key] = received_params.get(key)
  773. if single_not_embedded_field:
  774. field_info = first_field.field_info
  775. assert isinstance(field_info, params.Param), (
  776. "Params must be subclasses of Param"
  777. )
  778. loc: tuple[str, ...] = (field_info.in_.value,)
  779. v_, errors_ = _validate_value_with_model_field(
  780. field=first_field, value=params_to_process, values=values, loc=loc
  781. )
  782. return {first_field.name: v_}, errors_
  783. for field in fields:
  784. value = _get_multidict_value(field, received_params)
  785. field_info = field.field_info
  786. assert isinstance(field_info, params.Param), (
  787. "Params must be subclasses of Param"
  788. )
  789. loc = (field_info.in_.value, get_validation_alias(field))
  790. v_, errors_ = _validate_value_with_model_field(
  791. field=field, value=value, values=values, loc=loc
  792. )
  793. if errors_:
  794. errors.extend(errors_)
  795. else:
  796. values[field.name] = v_
  797. return values, errors
  798. def is_union_of_base_models(field_type: Any) -> bool:
  799. """Check if field type is a Union where all members are BaseModel subclasses."""
  800. from fastapi.types import UnionType
  801. origin = get_origin(field_type)
  802. # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)
  803. if origin is not Union and origin is not UnionType:
  804. return False
  805. union_args = get_args(field_type)
  806. for arg in union_args:
  807. if not lenient_issubclass(arg, BaseModel):
  808. return False
  809. return True
  810. def _should_embed_body_fields(fields: list[ModelField]) -> bool:
  811. if not fields:
  812. return False
  813. # More than one dependency could have the same field, it would show up as multiple
  814. # fields but it's the same one, so count them by name
  815. body_param_names_set = {field.name for field in fields}
  816. # A top level field has to be a single field, not multiple
  817. if len(body_param_names_set) > 1:
  818. return True
  819. first_field = fields[0]
  820. # If it explicitly specifies it is embedded, it has to be embedded
  821. if getattr(first_field.field_info, "embed", None):
  822. return True
  823. # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level
  824. # otherwise it has to be embedded, so that the key value pair can be extracted
  825. if (
  826. isinstance(first_field.field_info, params.Form)
  827. and not lenient_issubclass(first_field.field_info.annotation, BaseModel)
  828. and not is_union_of_base_models(first_field.field_info.annotation)
  829. ):
  830. return True
  831. return False
  832. async def _extract_form_body(
  833. body_fields: list[ModelField],
  834. received_body: FormData,
  835. ) -> dict[str, Any]:
  836. values = {}
  837. for field in body_fields:
  838. value = _get_multidict_value(field, received_body)
  839. field_info = field.field_info
  840. if (
  841. isinstance(field_info, params.File)
  842. and is_bytes_or_nonable_bytes_annotation(field.field_info.annotation)
  843. and isinstance(value, UploadFile)
  844. ):
  845. value = await value.read()
  846. elif (
  847. is_bytes_sequence_annotation(field.field_info.annotation)
  848. and isinstance(field_info, params.File)
  849. and value_is_sequence(value)
  850. ):
  851. # For types
  852. assert isinstance(value, sequence_types)
  853. results: list[bytes | str] = []
  854. for sub_value in value:
  855. results.append(await sub_value.read())
  856. value = serialize_sequence_value(field=field, value=results)
  857. if value is not None:
  858. values[get_validation_alias(field)] = value
  859. field_aliases = {get_validation_alias(field) for field in body_fields}
  860. for key in received_body.keys():
  861. if key not in field_aliases:
  862. param_values = received_body.getlist(key)
  863. if len(param_values) == 1:
  864. values[key] = param_values[0]
  865. else:
  866. values[key] = param_values
  867. return values
  868. async def request_body_to_args(
  869. body_fields: list[ModelField],
  870. received_body: dict[str, Any] | FormData | bytes | None,
  871. embed_body_fields: bool,
  872. ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
  873. values: dict[str, Any] = {}
  874. errors: list[dict[str, Any]] = []
  875. assert body_fields, "request_body_to_args() should be called with fields"
  876. single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields
  877. first_field = body_fields[0]
  878. body_to_process = received_body
  879. fields_to_extract: list[ModelField] = body_fields
  880. if (
  881. single_not_embedded_field
  882. and lenient_issubclass(first_field.field_info.annotation, BaseModel)
  883. and isinstance(received_body, FormData)
  884. ):
  885. fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
  886. if isinstance(received_body, FormData):
  887. body_to_process = await _extract_form_body(fields_to_extract, received_body)
  888. if single_not_embedded_field:
  889. loc: tuple[str, ...] = ("body",)
  890. v_, errors_ = _validate_value_with_model_field(
  891. field=first_field, value=body_to_process, values=values, loc=loc
  892. )
  893. return {first_field.name: v_}, errors_
  894. for field in body_fields:
  895. loc = ("body", get_validation_alias(field))
  896. value: Any | None = None
  897. if body_to_process is not None and not isinstance(body_to_process, bytes):
  898. try:
  899. value = body_to_process.get(get_validation_alias(field))
  900. # If the received body is a list, not a dict
  901. except AttributeError:
  902. errors.append(get_missing_field_error(loc))
  903. continue
  904. v_, errors_ = _validate_value_with_model_field(
  905. field=field, value=value, values=values, loc=loc
  906. )
  907. if errors_:
  908. errors.extend(errors_)
  909. else:
  910. values[field.name] = v_
  911. return values, errors
  912. def get_body_field(
  913. *, flat_dependant: Dependant, name: str, embed_body_fields: bool
  914. ) -> ModelField | None:
  915. """
  916. Get a ModelField representing the request body for a path operation, combining
  917. all body parameters into a single field if necessary.
  918. Used to check if it's form data (with `isinstance(body_field, params.Form)`)
  919. or JSON and to generate the JSON Schema for a request body.
  920. This is **not** used to validate/parse the request body, that's done with each
  921. individual body parameter.
  922. """
  923. if not flat_dependant.body_params:
  924. return None
  925. first_param = flat_dependant.body_params[0]
  926. if not embed_body_fields:
  927. return first_param
  928. model_name = "Body_" + name
  929. BodyModel = create_body_model(
  930. fields=flat_dependant.body_params, model_name=model_name
  931. )
  932. required = any(
  933. True for f in flat_dependant.body_params if f.field_info.is_required()
  934. )
  935. BodyFieldInfo_kwargs: dict[str, Any] = {
  936. "annotation": BodyModel,
  937. "alias": "body",
  938. }
  939. if not required:
  940. BodyFieldInfo_kwargs["default"] = None
  941. if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
  942. BodyFieldInfo: type[params.Body] = params.File
  943. elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
  944. BodyFieldInfo = params.Form
  945. else:
  946. BodyFieldInfo = params.Body
  947. body_param_media_types = [
  948. f.field_info.media_type
  949. for f in flat_dependant.body_params
  950. if isinstance(f.field_info, params.Body)
  951. ]
  952. if len(set(body_param_media_types)) == 1:
  953. BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
  954. final_field = create_model_field(
  955. name="body",
  956. type_=BodyModel,
  957. alias="body",
  958. field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
  959. )
  960. return final_field
  961. def get_validation_alias(field: ModelField) -> str:
  962. va = getattr(field, "validation_alias", None)
  963. return va or field.alias