fastapi 0.112.2__py3-none-any.whl → 0.112.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fastapi might be problematic. Click here for more details.

fastapi/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
2
2
 
3
- __version__ = "0.112.2"
3
+ __version__ = "0.112.4"
4
4
 
5
5
  from starlette import status as status
6
6
 
fastapi/_compat.py CHANGED
@@ -279,6 +279,12 @@ if PYDANTIC_V2:
279
279
  BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload]
280
280
  return BodyModel
281
281
 
282
+ def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
283
+ return [
284
+ ModelField(field_info=field_info, name=name)
285
+ for name, field_info in model.model_fields.items()
286
+ ]
287
+
282
288
  else:
283
289
  from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX
284
290
  from pydantic import AnyUrl as Url # noqa: F401
@@ -513,6 +519,9 @@ else:
513
519
  BodyModel.__fields__[f.name] = f # type: ignore[index]
514
520
  return BodyModel
515
521
 
522
+ def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
523
+ return list(model.__fields__.values()) # type: ignore[attr-defined]
524
+
516
525
 
517
526
  def _regenerate_error_with_loc(
518
527
  *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...]
@@ -532,6 +541,12 @@ def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
532
541
 
533
542
 
534
543
  def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:
544
+ origin = get_origin(annotation)
545
+ if origin is Union or origin is UnionType:
546
+ for arg in get_args(annotation):
547
+ if field_annotation_is_sequence(arg):
548
+ return True
549
+ return False
535
550
  return _annotation_is_sequence(annotation) or _annotation_is_sequence(
536
551
  get_origin(annotation)
537
552
  )
@@ -1,58 +1,37 @@
1
- from typing import Any, Callable, List, Optional, Sequence
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Callable, List, Optional, Sequence, Tuple
2
3
 
3
4
  from fastapi._compat import ModelField
4
5
  from fastapi.security.base import SecurityBase
5
6
 
6
7
 
8
+ @dataclass
7
9
  class SecurityRequirement:
8
- def __init__(
9
- self, security_scheme: SecurityBase, scopes: Optional[Sequence[str]] = None
10
- ):
11
- self.security_scheme = security_scheme
12
- self.scopes = scopes
10
+ security_scheme: SecurityBase
11
+ scopes: Optional[Sequence[str]] = None
13
12
 
14
13
 
14
+ @dataclass
15
15
  class Dependant:
16
- def __init__(
17
- self,
18
- *,
19
- path_params: Optional[List[ModelField]] = None,
20
- query_params: Optional[List[ModelField]] = None,
21
- header_params: Optional[List[ModelField]] = None,
22
- cookie_params: Optional[List[ModelField]] = None,
23
- body_params: Optional[List[ModelField]] = None,
24
- dependencies: Optional[List["Dependant"]] = None,
25
- security_schemes: Optional[List[SecurityRequirement]] = None,
26
- name: Optional[str] = None,
27
- call: Optional[Callable[..., Any]] = None,
28
- request_param_name: Optional[str] = None,
29
- websocket_param_name: Optional[str] = None,
30
- http_connection_param_name: Optional[str] = None,
31
- response_param_name: Optional[str] = None,
32
- background_tasks_param_name: Optional[str] = None,
33
- security_scopes_param_name: Optional[str] = None,
34
- security_scopes: Optional[List[str]] = None,
35
- use_cache: bool = True,
36
- path: Optional[str] = None,
37
- ) -> None:
38
- self.path_params = path_params or []
39
- self.query_params = query_params or []
40
- self.header_params = header_params or []
41
- self.cookie_params = cookie_params or []
42
- self.body_params = body_params or []
43
- self.dependencies = dependencies or []
44
- self.security_requirements = security_schemes or []
45
- self.request_param_name = request_param_name
46
- self.websocket_param_name = websocket_param_name
47
- self.http_connection_param_name = http_connection_param_name
48
- self.response_param_name = response_param_name
49
- self.background_tasks_param_name = background_tasks_param_name
50
- self.security_scopes = security_scopes
51
- self.security_scopes_param_name = security_scopes_param_name
52
- self.name = name
53
- self.call = call
54
- self.use_cache = use_cache
55
- # Store the path to be able to re-generate a dependable from it in overrides
56
- self.path = path
57
- # Save the cache key at creation to optimize performance
16
+ path_params: List[ModelField] = field(default_factory=list)
17
+ query_params: List[ModelField] = field(default_factory=list)
18
+ header_params: List[ModelField] = field(default_factory=list)
19
+ cookie_params: List[ModelField] = field(default_factory=list)
20
+ body_params: List[ModelField] = field(default_factory=list)
21
+ dependencies: List["Dependant"] = field(default_factory=list)
22
+ security_requirements: List[SecurityRequirement] = field(default_factory=list)
23
+ name: Optional[str] = None
24
+ call: Optional[Callable[..., Any]] = None
25
+ request_param_name: Optional[str] = None
26
+ websocket_param_name: Optional[str] = None
27
+ http_connection_param_name: Optional[str] = None
28
+ response_param_name: Optional[str] = None
29
+ background_tasks_param_name: Optional[str] = None
30
+ security_scopes_param_name: Optional[str] = None
31
+ security_scopes: Optional[List[str]] = None
32
+ use_cache: bool = True
33
+ path: Optional[str] = None
34
+ cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] = field(init=False)
35
+
36
+ def __post_init__(self) -> None:
58
37
  self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or []))))
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  from contextlib import AsyncExitStack, contextmanager
3
3
  from copy import copy, deepcopy
4
+ from dataclasses import dataclass
4
5
  from typing import (
5
6
  Any,
6
7
  Callable,
@@ -54,11 +55,17 @@ from fastapi.logger import logger
54
55
  from fastapi.security.base import SecurityBase
55
56
  from fastapi.security.oauth2 import OAuth2, SecurityScopes
56
57
  from fastapi.security.open_id_connect_url import OpenIdConnect
57
- from fastapi.utils import create_response_field, get_path_param_names
58
+ from fastapi.utils import create_model_field, get_path_param_names
58
59
  from pydantic.fields import FieldInfo
59
60
  from starlette.background import BackgroundTasks as StarletteBackgroundTasks
60
61
  from starlette.concurrency import run_in_threadpool
61
- from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
62
+ from starlette.datastructures import (
63
+ FormData,
64
+ Headers,
65
+ ImmutableMultiDict,
66
+ QueryParams,
67
+ UploadFile,
68
+ )
62
69
  from starlette.requests import HTTPConnection, Request
63
70
  from starlette.responses import Response
64
71
  from starlette.websockets import WebSocket
@@ -79,25 +86,23 @@ multipart_incorrect_install_error = (
79
86
  )
80
87
 
81
88
 
82
- def check_file_field(field: ModelField) -> None:
83
- field_info = field.field_info
84
- if isinstance(field_info, params.Form):
85
- try:
86
- # __version__ is available in both multiparts, and can be mocked
87
- from multipart import __version__ # type: ignore
89
+ def ensure_multipart_is_installed() -> None:
90
+ try:
91
+ # __version__ is available in both multiparts, and can be mocked
92
+ from multipart import __version__ # type: ignore
88
93
 
89
- assert __version__
90
- try:
91
- # parse_options_header is only available in the right multipart
92
- from multipart.multipart import parse_options_header # type: ignore
94
+ assert __version__
95
+ try:
96
+ # parse_options_header is only available in the right multipart
97
+ from multipart.multipart import parse_options_header # type: ignore
93
98
 
94
- assert parse_options_header
95
- except ImportError:
96
- logger.error(multipart_incorrect_install_error)
97
- raise RuntimeError(multipart_incorrect_install_error) from None
99
+ assert parse_options_header
98
100
  except ImportError:
99
- logger.error(multipart_not_installed_error)
100
- raise RuntimeError(multipart_not_installed_error) from None
101
+ logger.error(multipart_incorrect_install_error)
102
+ raise RuntimeError(multipart_incorrect_install_error) from None
103
+ except ImportError:
104
+ logger.error(multipart_not_installed_error)
105
+ raise RuntimeError(multipart_not_installed_error) from None
101
106
 
102
107
 
103
108
  def get_param_sub_dependant(
@@ -175,7 +180,7 @@ def get_flat_dependant(
175
180
  header_params=dependant.header_params.copy(),
176
181
  cookie_params=dependant.cookie_params.copy(),
177
182
  body_params=dependant.body_params.copy(),
178
- security_schemes=dependant.security_requirements.copy(),
183
+ security_requirements=dependant.security_requirements.copy(),
179
184
  use_cache=dependant.use_cache,
180
185
  path=dependant.path,
181
186
  )
@@ -258,16 +263,16 @@ def get_dependant(
258
263
  )
259
264
  for param_name, param in signature_params.items():
260
265
  is_path_param = param_name in path_param_names
261
- type_annotation, depends, param_field = analyze_param(
266
+ param_details = analyze_param(
262
267
  param_name=param_name,
263
268
  annotation=param.annotation,
264
269
  value=param.default,
265
270
  is_path_param=is_path_param,
266
271
  )
267
- if depends is not None:
272
+ if param_details.depends is not None:
268
273
  sub_dependant = get_param_sub_dependant(
269
274
  param_name=param_name,
270
- depends=depends,
275
+ depends=param_details.depends,
271
276
  path=path,
272
277
  security_scopes=security_scopes,
273
278
  )
@@ -275,18 +280,18 @@ def get_dependant(
275
280
  continue
276
281
  if add_non_field_param_to_dependency(
277
282
  param_name=param_name,
278
- type_annotation=type_annotation,
283
+ type_annotation=param_details.type_annotation,
279
284
  dependant=dependant,
280
285
  ):
281
286
  assert (
282
- param_field is None
287
+ param_details.field is None
283
288
  ), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
284
289
  continue
285
- assert param_field is not None
286
- if is_body_param(param_field=param_field, is_path_param=is_path_param):
287
- dependant.body_params.append(param_field)
290
+ assert param_details.field is not None
291
+ if isinstance(param_details.field.field_info, params.Body):
292
+ dependant.body_params.append(param_details.field)
288
293
  else:
289
- add_param_to_fields(field=param_field, dependant=dependant)
294
+ add_param_to_fields(field=param_details.field, dependant=dependant)
290
295
  return dependant
291
296
 
292
297
 
@@ -314,13 +319,20 @@ def add_non_field_param_to_dependency(
314
319
  return None
315
320
 
316
321
 
322
+ @dataclass
323
+ class ParamDetails:
324
+ type_annotation: Any
325
+ depends: Optional[params.Depends]
326
+ field: Optional[ModelField]
327
+
328
+
317
329
  def analyze_param(
318
330
  *,
319
331
  param_name: str,
320
332
  annotation: Any,
321
333
  value: Any,
322
334
  is_path_param: bool,
323
- ) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]:
335
+ ) -> ParamDetails:
324
336
  field_info = None
325
337
  depends = None
326
338
  type_annotation: Any = Any
@@ -328,6 +340,7 @@ def analyze_param(
328
340
  if annotation is not inspect.Signature.empty:
329
341
  use_annotation = annotation
330
342
  type_annotation = annotation
343
+ # Extract Annotated info
331
344
  if get_origin(use_annotation) is Annotated:
332
345
  annotated_args = get_args(annotation)
333
346
  type_annotation = annotated_args[0]
@@ -347,6 +360,7 @@ def analyze_param(
347
360
  )
348
361
  else:
349
362
  fastapi_annotation = None
363
+ # Set default for Annotated FieldInfo
350
364
  if isinstance(fastapi_annotation, FieldInfo):
351
365
  # Copy `field_info` because we mutate `field_info.default` below.
352
366
  field_info = copy_field_info(
@@ -361,9 +375,10 @@ def analyze_param(
361
375
  field_info.default = value
362
376
  else:
363
377
  field_info.default = Required
378
+ # Get Annotated Depends
364
379
  elif isinstance(fastapi_annotation, params.Depends):
365
380
  depends = fastapi_annotation
366
-
381
+ # Get Depends from default value
367
382
  if isinstance(value, params.Depends):
368
383
  assert depends is None, (
369
384
  "Cannot specify `Depends` in `Annotated` and default value"
@@ -374,6 +389,7 @@ def analyze_param(
374
389
  f" default value together for {param_name!r}"
375
390
  )
376
391
  depends = value
392
+ # Get FieldInfo from default value
377
393
  elif isinstance(value, FieldInfo):
378
394
  assert field_info is None, (
379
395
  "Cannot specify FastAPI annotations in `Annotated` and default value"
@@ -383,11 +399,13 @@ def analyze_param(
383
399
  if PYDANTIC_V2:
384
400
  field_info.annotation = type_annotation
385
401
 
402
+ # Get Depends from type annotation
386
403
  if depends is not None and depends.dependency is None:
387
404
  # Copy `depends` before mutating it
388
405
  depends = copy(depends)
389
406
  depends.dependency = type_annotation
390
407
 
408
+ # Handle non-param type annotations like Request
391
409
  if lenient_issubclass(
392
410
  type_annotation,
393
411
  (
@@ -403,6 +421,7 @@ def analyze_param(
403
421
  assert (
404
422
  field_info is None
405
423
  ), f"Cannot specify FastAPI annotation for type {type_annotation!r}"
424
+ # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
406
425
  elif field_info is None and depends is None:
407
426
  default_value = value if value is not inspect.Signature.empty else Required
408
427
  if is_path_param:
@@ -420,7 +439,9 @@ def analyze_param(
420
439
  field_info = params.Query(annotation=use_annotation, default=default_value)
421
440
 
422
441
  field = None
442
+ # It's a field_info, not a dependency
423
443
  if field_info is not None:
444
+ # Handle field_info.in_
424
445
  if is_path_param:
425
446
  assert isinstance(field_info, params.Path), (
426
447
  f"Cannot use `{field_info.__class__.__name__}` for path param"
@@ -436,12 +457,14 @@ def analyze_param(
436
457
  field_info,
437
458
  param_name,
438
459
  )
460
+ if isinstance(field_info, params.Form):
461
+ ensure_multipart_is_installed()
439
462
  if not field_info.alias and getattr(field_info, "convert_underscores", None):
440
463
  alias = param_name.replace("_", "-")
441
464
  else:
442
465
  alias = field_info.alias or param_name
443
466
  field_info.alias = alias
444
- field = create_response_field(
467
+ field = create_model_field(
445
468
  name=param_name,
446
469
  type_=use_annotation_from_field_info,
447
470
  default=field_info.default,
@@ -449,27 +472,14 @@ def analyze_param(
449
472
  required=field_info.default in (Required, Undefined),
450
473
  field_info=field_info,
451
474
  )
475
+ if is_path_param:
476
+ assert is_scalar_field(
477
+ field=field
478
+ ), "Path params must be of one of the supported types"
479
+ elif isinstance(field_info, params.Query):
480
+ assert is_scalar_field(field) or is_scalar_sequence_field(field)
452
481
 
453
- return type_annotation, depends, field
454
-
455
-
456
- def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
457
- if is_path_param:
458
- assert is_scalar_field(
459
- field=param_field
460
- ), "Path params must be of one of the supported types"
461
- return False
462
- elif is_scalar_field(field=param_field):
463
- return False
464
- elif isinstance(
465
- param_field.field_info, (params.Query, params.Header)
466
- ) and is_scalar_sequence_field(param_field):
467
- return False
468
- else:
469
- assert isinstance(
470
- param_field.field_info, params.Body
471
- ), f"Param: {param_field.name} can only be a request body, using Body()"
472
- return True
482
+ return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
473
483
 
474
484
 
475
485
  def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
@@ -521,6 +531,15 @@ async def solve_generator(
521
531
  return await stack.enter_async_context(cm)
522
532
 
523
533
 
534
+ @dataclass
535
+ class SolvedDependency:
536
+ values: Dict[str, Any]
537
+ errors: List[Any]
538
+ background_tasks: Optional[StarletteBackgroundTasks]
539
+ response: Response
540
+ dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any]
541
+
542
+
524
543
  async def solve_dependencies(
525
544
  *,
526
545
  request: Union[Request, WebSocket],
@@ -531,13 +550,8 @@ async def solve_dependencies(
531
550
  dependency_overrides_provider: Optional[Any] = None,
532
551
  dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
533
552
  async_exit_stack: AsyncExitStack,
534
- ) -> Tuple[
535
- Dict[str, Any],
536
- List[Any],
537
- Optional[StarletteBackgroundTasks],
538
- Response,
539
- Dict[Tuple[Callable[..., Any], Tuple[str]], Any],
540
- ]:
553
+ embed_body_fields: bool,
554
+ ) -> SolvedDependency:
541
555
  values: Dict[str, Any] = {}
542
556
  errors: List[Any] = []
543
557
  if response is None:
@@ -578,28 +592,23 @@ async def solve_dependencies(
578
592
  dependency_overrides_provider=dependency_overrides_provider,
579
593
  dependency_cache=dependency_cache,
580
594
  async_exit_stack=async_exit_stack,
595
+ embed_body_fields=embed_body_fields,
581
596
  )
582
- (
583
- sub_values,
584
- sub_errors,
585
- background_tasks,
586
- _, # the subdependency returns the same response we have
587
- sub_dependency_cache,
588
- ) = solved_result
589
- dependency_cache.update(sub_dependency_cache)
590
- if sub_errors:
591
- errors.extend(sub_errors)
597
+ background_tasks = solved_result.background_tasks
598
+ dependency_cache.update(solved_result.dependency_cache)
599
+ if solved_result.errors:
600
+ errors.extend(solved_result.errors)
592
601
  continue
593
602
  if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
594
603
  solved = dependency_cache[sub_dependant.cache_key]
595
604
  elif is_gen_callable(call) or is_async_gen_callable(call):
596
605
  solved = await solve_generator(
597
- call=call, stack=async_exit_stack, sub_values=sub_values
606
+ call=call, stack=async_exit_stack, sub_values=solved_result.values
598
607
  )
599
608
  elif is_coroutine_callable(call):
600
- solved = await call(**sub_values)
609
+ solved = await call(**solved_result.values)
601
610
  else:
602
- solved = await run_in_threadpool(call, **sub_values)
611
+ solved = await run_in_threadpool(call, **solved_result.values)
603
612
  if sub_dependant.name is not None:
604
613
  values[sub_dependant.name] = solved
605
614
  if sub_dependant.cache_key not in dependency_cache:
@@ -626,7 +635,9 @@ async def solve_dependencies(
626
635
  body_values,
627
636
  body_errors,
628
637
  ) = await request_body_to_args( # body_params checked above
629
- required_params=dependant.body_params, received_body=body
638
+ body_fields=dependant.body_params,
639
+ received_body=body,
640
+ embed_body_fields=embed_body_fields,
630
641
  )
631
642
  values.update(body_values)
632
643
  errors.extend(body_errors)
@@ -646,142 +657,194 @@ async def solve_dependencies(
646
657
  values[dependant.security_scopes_param_name] = SecurityScopes(
647
658
  scopes=dependant.security_scopes
648
659
  )
649
- return values, errors, background_tasks, response, dependency_cache
660
+ return SolvedDependency(
661
+ values=values,
662
+ errors=errors,
663
+ background_tasks=background_tasks,
664
+ response=response,
665
+ dependency_cache=dependency_cache,
666
+ )
667
+
668
+
669
+ def _validate_value_with_model_field(
670
+ *, field: ModelField, value: Any, values: Dict[str, Any], loc: Tuple[str, ...]
671
+ ) -> Tuple[Any, List[Any]]:
672
+ if value is None:
673
+ if field.required:
674
+ return None, [get_missing_field_error(loc=loc)]
675
+ else:
676
+ return deepcopy(field.default), []
677
+ v_, errors_ = field.validate(value, values, loc=loc)
678
+ if isinstance(errors_, ErrorWrapper):
679
+ return None, [errors_]
680
+ elif isinstance(errors_, list):
681
+ new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
682
+ return None, new_errors
683
+ else:
684
+ return v_, []
685
+
686
+
687
+ def _get_multidict_value(field: ModelField, values: Mapping[str, Any]) -> Any:
688
+ if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
689
+ value = values.getlist(field.alias)
690
+ else:
691
+ value = values.get(field.alias, None)
692
+ if (
693
+ value is None
694
+ or (
695
+ isinstance(field.field_info, params.Form)
696
+ and isinstance(value, str) # For type checks
697
+ and value == ""
698
+ )
699
+ or (is_sequence_field(field) and len(value) == 0)
700
+ ):
701
+ if field.required:
702
+ return
703
+ else:
704
+ return deepcopy(field.default)
705
+ return value
650
706
 
651
707
 
652
708
  def request_params_to_args(
653
- required_params: Sequence[ModelField],
709
+ fields: Sequence[ModelField],
654
710
  received_params: Union[Mapping[str, Any], QueryParams, Headers],
655
711
  ) -> Tuple[Dict[str, Any], List[Any]]:
656
- values = {}
712
+ values: Dict[str, Any] = {}
657
713
  errors = []
658
- for field in required_params:
659
- if is_scalar_sequence_field(field) and isinstance(
660
- received_params, (QueryParams, Headers)
661
- ):
662
- value = received_params.getlist(field.alias) or field.default
663
- else:
664
- value = received_params.get(field.alias)
714
+ for field in fields:
715
+ value = _get_multidict_value(field, received_params)
665
716
  field_info = field.field_info
666
717
  assert isinstance(
667
718
  field_info, params.Param
668
719
  ), "Params must be subclasses of Param"
669
720
  loc = (field_info.in_.value, field.alias)
670
- if value is None:
671
- if field.required:
672
- errors.append(get_missing_field_error(loc=loc))
673
- else:
674
- values[field.name] = deepcopy(field.default)
675
- continue
676
- v_, errors_ = field.validate(value, values, loc=loc)
677
- if isinstance(errors_, ErrorWrapper):
678
- errors.append(errors_)
679
- elif isinstance(errors_, list):
680
- new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=())
681
- errors.extend(new_errors)
721
+ v_, errors_ = _validate_value_with_model_field(
722
+ field=field, value=value, values=values, loc=loc
723
+ )
724
+ if errors_:
725
+ errors.extend(errors_)
682
726
  else:
683
727
  values[field.name] = v_
684
728
  return values, errors
685
729
 
686
730
 
731
+ def _should_embed_body_fields(fields: List[ModelField]) -> bool:
732
+ if not fields:
733
+ return False
734
+ # More than one dependency could have the same field, it would show up as multiple
735
+ # fields but it's the same one, so count them by name
736
+ body_param_names_set = {field.name for field in fields}
737
+ # A top level field has to be a single field, not multiple
738
+ if len(body_param_names_set) > 1:
739
+ return True
740
+ first_field = fields[0]
741
+ # If it explicitly specifies it is embedded, it has to be embedded
742
+ if getattr(first_field.field_info, "embed", None):
743
+ return True
744
+ # If it's a Form (or File) field, it has to be a BaseModel to be top level
745
+ # otherwise it has to be embedded, so that the key value pair can be extracted
746
+ if isinstance(first_field.field_info, params.Form):
747
+ return True
748
+ return False
749
+
750
+
751
+ async def _extract_form_body(
752
+ body_fields: List[ModelField],
753
+ received_body: FormData,
754
+ ) -> Dict[str, Any]:
755
+ values = {}
756
+ first_field = body_fields[0]
757
+ first_field_info = first_field.field_info
758
+
759
+ for field in body_fields:
760
+ value = _get_multidict_value(field, received_body)
761
+ if (
762
+ isinstance(first_field_info, params.File)
763
+ and is_bytes_field(field)
764
+ and isinstance(value, UploadFile)
765
+ ):
766
+ value = await value.read()
767
+ elif (
768
+ is_bytes_sequence_field(field)
769
+ and isinstance(first_field_info, params.File)
770
+ and value_is_sequence(value)
771
+ ):
772
+ # For types
773
+ assert isinstance(value, sequence_types) # type: ignore[arg-type]
774
+ results: List[Union[bytes, str]] = []
775
+
776
+ async def process_fn(
777
+ fn: Callable[[], Coroutine[Any, Any, Any]],
778
+ ) -> None:
779
+ result = await fn()
780
+ results.append(result) # noqa: B023
781
+
782
+ async with anyio.create_task_group() as tg:
783
+ for sub_value in value:
784
+ tg.start_soon(process_fn, sub_value.read)
785
+ value = serialize_sequence_value(field=field, value=results)
786
+ values[field.name] = value
787
+ return values
788
+
789
+
687
790
  async def request_body_to_args(
688
- required_params: List[ModelField],
791
+ body_fields: List[ModelField],
689
792
  received_body: Optional[Union[Dict[str, Any], FormData]],
793
+ embed_body_fields: bool,
690
794
  ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
691
- values = {}
795
+ values: Dict[str, Any] = {}
692
796
  errors: List[Dict[str, Any]] = []
693
- if required_params:
694
- field = required_params[0]
695
- field_info = field.field_info
696
- embed = getattr(field_info, "embed", None)
697
- field_alias_omitted = len(required_params) == 1 and not embed
698
- if field_alias_omitted:
699
- received_body = {field.alias: received_body}
700
-
701
- for field in required_params:
702
- loc: Tuple[str, ...]
703
- if field_alias_omitted:
704
- loc = ("body",)
705
- else:
706
- loc = ("body", field.alias)
707
-
708
- value: Optional[Any] = None
709
- if received_body is not None:
710
- if (is_sequence_field(field)) and isinstance(received_body, FormData):
711
- value = received_body.getlist(field.alias)
712
- else:
713
- try:
714
- value = received_body.get(field.alias)
715
- except AttributeError:
716
- errors.append(get_missing_field_error(loc))
717
- continue
718
- if (
719
- value is None
720
- or (isinstance(field_info, params.Form) and value == "")
721
- or (
722
- isinstance(field_info, params.Form)
723
- and is_sequence_field(field)
724
- and len(value) == 0
725
- )
726
- ):
727
- if field.required:
728
- errors.append(get_missing_field_error(loc))
729
- else:
730
- values[field.name] = deepcopy(field.default)
797
+ assert body_fields, "request_body_to_args() should be called with fields"
798
+ single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields
799
+ first_field = body_fields[0]
800
+ body_to_process = received_body
801
+ if isinstance(received_body, FormData):
802
+ body_to_process = await _extract_form_body(body_fields, received_body)
803
+
804
+ if single_not_embedded_field:
805
+ loc: Tuple[str, ...] = ("body",)
806
+ v_, errors_ = _validate_value_with_model_field(
807
+ field=first_field, value=body_to_process, values=values, loc=loc
808
+ )
809
+ return {first_field.name: v_}, errors_
810
+ for field in body_fields:
811
+ loc = ("body", field.alias)
812
+ value: Optional[Any] = None
813
+ if body_to_process is not None:
814
+ try:
815
+ value = body_to_process.get(field.alias)
816
+ # If the received body is a list, not a dict
817
+ except AttributeError:
818
+ errors.append(get_missing_field_error(loc))
731
819
  continue
732
- if (
733
- isinstance(field_info, params.File)
734
- and is_bytes_field(field)
735
- and isinstance(value, UploadFile)
736
- ):
737
- value = await value.read()
738
- elif (
739
- is_bytes_sequence_field(field)
740
- and isinstance(field_info, params.File)
741
- and value_is_sequence(value)
742
- ):
743
- # For types
744
- assert isinstance(value, sequence_types) # type: ignore[arg-type]
745
- results: List[Union[bytes, str]] = []
746
-
747
- async def process_fn(
748
- fn: Callable[[], Coroutine[Any, Any, Any]],
749
- ) -> None:
750
- result = await fn()
751
- results.append(result) # noqa: B023
752
-
753
- async with anyio.create_task_group() as tg:
754
- for sub_value in value:
755
- tg.start_soon(process_fn, sub_value.read)
756
- value = serialize_sequence_value(field=field, value=results)
757
-
758
- v_, errors_ = field.validate(value, values, loc=loc)
759
-
760
- if isinstance(errors_, list):
761
- errors.extend(errors_)
762
- elif errors_:
763
- errors.append(errors_)
764
- else:
765
- values[field.name] = v_
820
+ v_, errors_ = _validate_value_with_model_field(
821
+ field=field, value=value, values=values, loc=loc
822
+ )
823
+ if errors_:
824
+ errors.extend(errors_)
825
+ else:
826
+ values[field.name] = v_
766
827
  return values, errors
767
828
 
768
829
 
769
- def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
770
- flat_dependant = get_flat_dependant(dependant)
830
+ def get_body_field(
831
+ *, flat_dependant: Dependant, name: str, embed_body_fields: bool
832
+ ) -> Optional[ModelField]:
833
+ """
834
+ Get a ModelField representing the request body for a path operation, combining
835
+ all body parameters into a single field if necessary.
836
+
837
+ Used to check if it's form data (with `isinstance(body_field, params.Form)`)
838
+ or JSON and to generate the JSON Schema for a request body.
839
+
840
+ This is **not** used to validate/parse the request body, that's done with each
841
+ individual body parameter.
842
+ """
771
843
  if not flat_dependant.body_params:
772
844
  return None
773
845
  first_param = flat_dependant.body_params[0]
774
- field_info = first_param.field_info
775
- embed = getattr(field_info, "embed", None)
776
- body_param_names_set = {param.name for param in flat_dependant.body_params}
777
- if len(body_param_names_set) == 1 and not embed:
778
- check_file_field(first_param)
846
+ if not embed_body_fields:
779
847
  return first_param
780
- # If one field requires to embed, all have to be embedded
781
- # in case a sub-dependency is evaluated with a single unique body field
782
- # That is combined (embedded) with other body fields
783
- for param in flat_dependant.body_params:
784
- setattr(param.field_info, "embed", True) # noqa: B010
785
848
  model_name = "Body_" + name
786
849
  BodyModel = create_body_model(
787
850
  fields=flat_dependant.body_params, model_name=model_name
@@ -807,12 +870,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
807
870
  ]
808
871
  if len(set(body_param_media_types)) == 1:
809
872
  BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
810
- final_field = create_response_field(
873
+ final_field = create_model_field(
811
874
  name="body",
812
875
  type_=BodyModel,
813
876
  required=required,
814
877
  alias="body",
815
878
  field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
816
879
  )
817
- check_file_field(final_field)
818
880
  return final_field
@@ -1282,7 +1282,7 @@ def Body( # noqa: N802
1282
1282
  ),
1283
1283
  ] = _Unset,
1284
1284
  embed: Annotated[
1285
- bool,
1285
+ Union[bool, None],
1286
1286
  Doc(
1287
1287
  """
1288
1288
  When `embed` is `True`, the parameter will be expected in a JSON body as a
@@ -1294,7 +1294,7 @@ def Body( # noqa: N802
1294
1294
  [FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter).
1295
1295
  """
1296
1296
  ),
1297
- ] = False,
1297
+ ] = None,
1298
1298
  media_type: Annotated[
1299
1299
  str,
1300
1300
  Doc(
fastapi/params.py CHANGED
@@ -479,7 +479,7 @@ class Body(FieldInfo):
479
479
  *,
480
480
  default_factory: Union[Callable[[], Any], None] = _Unset,
481
481
  annotation: Optional[Any] = None,
482
- embed: bool = False,
482
+ embed: Union[bool, None] = None,
483
483
  media_type: str = "application/json",
484
484
  alias: Optional[str] = None,
485
485
  alias_priority: Union[int, None] = _Unset,
@@ -642,7 +642,6 @@ class Form(Body):
642
642
  default=default,
643
643
  default_factory=default_factory,
644
644
  annotation=annotation,
645
- embed=True,
646
645
  media_type=media_type,
647
646
  alias=alias,
648
647
  alias_priority=alias_priority,
fastapi/routing.py CHANGED
@@ -33,8 +33,10 @@ from fastapi._compat import (
33
33
  from fastapi.datastructures import Default, DefaultPlaceholder
34
34
  from fastapi.dependencies.models import Dependant
35
35
  from fastapi.dependencies.utils import (
36
+ _should_embed_body_fields,
36
37
  get_body_field,
37
38
  get_dependant,
39
+ get_flat_dependant,
38
40
  get_parameterless_sub_dependant,
39
41
  get_typed_return_annotation,
40
42
  solve_dependencies,
@@ -49,7 +51,7 @@ from fastapi.exceptions import (
49
51
  from fastapi.types import DecoratedCallable, IncEx
50
52
  from fastapi.utils import (
51
53
  create_cloned_field,
52
- create_response_field,
54
+ create_model_field,
53
55
  generate_unique_id,
54
56
  get_value_or_default,
55
57
  is_body_allowed_for_status_code,
@@ -225,6 +227,7 @@ def get_request_handler(
225
227
  response_model_exclude_defaults: bool = False,
226
228
  response_model_exclude_none: bool = False,
227
229
  dependency_overrides_provider: Optional[Any] = None,
230
+ embed_body_fields: bool = False,
228
231
  ) -> Callable[[Request], Coroutine[Any, Any, Response]]:
229
232
  assert dependant.call is not None, "dependant.call must be a function"
230
233
  is_coroutine = asyncio.iscoroutinefunction(dependant.call)
@@ -291,27 +294,36 @@ def get_request_handler(
291
294
  body=body,
292
295
  dependency_overrides_provider=dependency_overrides_provider,
293
296
  async_exit_stack=async_exit_stack,
297
+ embed_body_fields=embed_body_fields,
294
298
  )
295
- values, errors, background_tasks, sub_response, _ = solved_result
299
+ errors = solved_result.errors
296
300
  if not errors:
297
301
  raw_response = await run_endpoint_function(
298
- dependant=dependant, values=values, is_coroutine=is_coroutine
302
+ dependant=dependant,
303
+ values=solved_result.values,
304
+ is_coroutine=is_coroutine,
299
305
  )
300
306
  if isinstance(raw_response, Response):
301
307
  if raw_response.background is None:
302
- raw_response.background = background_tasks
308
+ raw_response.background = solved_result.background_tasks
303
309
  response = raw_response
304
310
  else:
305
- response_args: Dict[str, Any] = {"background": background_tasks}
311
+ response_args: Dict[str, Any] = {
312
+ "background": solved_result.background_tasks
313
+ }
306
314
  # If status_code was set, use it, otherwise use the default from the
307
315
  # response class, in the case of redirect it's 307
308
316
  current_status_code = (
309
- status_code if status_code else sub_response.status_code
317
+ status_code
318
+ if status_code
319
+ else solved_result.response.status_code
310
320
  )
311
321
  if current_status_code is not None:
312
322
  response_args["status_code"] = current_status_code
313
- if sub_response.status_code:
314
- response_args["status_code"] = sub_response.status_code
323
+ if solved_result.response.status_code:
324
+ response_args["status_code"] = (
325
+ solved_result.response.status_code
326
+ )
315
327
  content = await serialize_response(
316
328
  field=response_field,
317
329
  response_content=raw_response,
@@ -326,7 +338,7 @@ def get_request_handler(
326
338
  response = actual_response_class(content, **response_args)
327
339
  if not is_body_allowed_for_status_code(response.status_code):
328
340
  response.body = b""
329
- response.headers.raw.extend(sub_response.headers.raw)
341
+ response.headers.raw.extend(solved_result.response.headers.raw)
330
342
  if errors:
331
343
  validation_error = RequestValidationError(
332
344
  _normalize_errors(errors), body=body
@@ -346,7 +358,9 @@ def get_request_handler(
346
358
 
347
359
 
348
360
  def get_websocket_app(
349
- dependant: Dependant, dependency_overrides_provider: Optional[Any] = None
361
+ dependant: Dependant,
362
+ dependency_overrides_provider: Optional[Any] = None,
363
+ embed_body_fields: bool = False,
350
364
  ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:
351
365
  async def app(websocket: WebSocket) -> None:
352
366
  async with AsyncExitStack() as async_exit_stack:
@@ -359,12 +373,14 @@ def get_websocket_app(
359
373
  dependant=dependant,
360
374
  dependency_overrides_provider=dependency_overrides_provider,
361
375
  async_exit_stack=async_exit_stack,
376
+ embed_body_fields=embed_body_fields,
362
377
  )
363
- values, errors, _, _2, _3 = solved_result
364
- if errors:
365
- raise WebSocketRequestValidationError(_normalize_errors(errors))
378
+ if solved_result.errors:
379
+ raise WebSocketRequestValidationError(
380
+ _normalize_errors(solved_result.errors)
381
+ )
366
382
  assert dependant.call is not None, "dependant.call must be a function"
367
- await dependant.call(**values)
383
+ await dependant.call(**solved_result.values)
368
384
 
369
385
  return app
370
386
 
@@ -390,11 +406,15 @@ class APIWebSocketRoute(routing.WebSocketRoute):
390
406
  0,
391
407
  get_parameterless_sub_dependant(depends=depends, path=self.path_format),
392
408
  )
393
-
409
+ self._flat_dependant = get_flat_dependant(self.dependant)
410
+ self._embed_body_fields = _should_embed_body_fields(
411
+ self._flat_dependant.body_params
412
+ )
394
413
  self.app = websocket_session(
395
414
  get_websocket_app(
396
415
  dependant=self.dependant,
397
416
  dependency_overrides_provider=dependency_overrides_provider,
417
+ embed_body_fields=self._embed_body_fields,
398
418
  )
399
419
  )
400
420
 
@@ -488,7 +508,7 @@ class APIRoute(routing.Route):
488
508
  status_code
489
509
  ), f"Status code {status_code} must not have a response body"
490
510
  response_name = "Response_" + self.unique_id
491
- self.response_field = create_response_field(
511
+ self.response_field = create_model_field(
492
512
  name=response_name,
493
513
  type_=self.response_model,
494
514
  mode="serialization",
@@ -521,7 +541,7 @@ class APIRoute(routing.Route):
521
541
  additional_status_code
522
542
  ), f"Status code {additional_status_code} must not have a response body"
523
543
  response_name = f"Response_{additional_status_code}_{self.unique_id}"
524
- response_field = create_response_field(name=response_name, type_=model)
544
+ response_field = create_model_field(name=response_name, type_=model)
525
545
  response_fields[additional_status_code] = response_field
526
546
  if response_fields:
527
547
  self.response_fields: Dict[Union[int, str], ModelField] = response_fields
@@ -535,7 +555,15 @@ class APIRoute(routing.Route):
535
555
  0,
536
556
  get_parameterless_sub_dependant(depends=depends, path=self.path_format),
537
557
  )
538
- self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id)
558
+ self._flat_dependant = get_flat_dependant(self.dependant)
559
+ self._embed_body_fields = _should_embed_body_fields(
560
+ self._flat_dependant.body_params
561
+ )
562
+ self.body_field = get_body_field(
563
+ flat_dependant=self._flat_dependant,
564
+ name=self.unique_id,
565
+ embed_body_fields=self._embed_body_fields,
566
+ )
539
567
  self.app = request_response(self.get_route_handler())
540
568
 
541
569
  def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
@@ -552,6 +580,7 @@ class APIRoute(routing.Route):
552
580
  response_model_exclude_defaults=self.response_model_exclude_defaults,
553
581
  response_model_exclude_none=self.response_model_exclude_none,
554
582
  dependency_overrides_provider=self.dependency_overrides_provider,
583
+ embed_body_fields=self._embed_body_fields,
555
584
  )
556
585
 
557
586
  def matches(self, scope: Scope) -> Tuple[Match, Scope]:
fastapi/utils.py CHANGED
@@ -60,9 +60,9 @@ def get_path_param_names(path: str) -> Set[str]:
60
60
  return set(re.findall("{(.*?)}", path))
61
61
 
62
62
 
63
- def create_response_field(
63
+ def create_model_field(
64
64
  name: str,
65
- type_: Type[Any],
65
+ type_: Any,
66
66
  class_validators: Optional[Dict[str, Validator]] = None,
67
67
  default: Optional[Any] = Undefined,
68
68
  required: Union[bool, UndefinedType] = Undefined,
@@ -71,9 +71,6 @@ def create_response_field(
71
71
  alias: Optional[str] = None,
72
72
  mode: Literal["validation", "serialization"] = "validation",
73
73
  ) -> ModelField:
74
- """
75
- Create a new response field. Raises if type_ is invalid.
76
- """
77
74
  class_validators = class_validators or {}
78
75
  if PYDANTIC_V2:
79
76
  field_info = field_info or FieldInfo(
@@ -135,7 +132,7 @@ def create_cloned_field(
135
132
  use_type.__fields__[f.name] = create_cloned_field(
136
133
  f, cloned_types=cloned_types
137
134
  )
138
- new_field = create_response_field(name=field.name, type_=use_type)
135
+ new_field = create_model_field(name=field.name, type_=use_type)
139
136
  new_field.has_alias = field.has_alias # type: ignore[attr-defined]
140
137
  new_field.alias = field.alias # type: ignore[misc]
141
138
  new_field.class_validators = field.class_validators # type: ignore[attr-defined]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi
3
- Version: 0.112.2
3
+ Version: 0.112.4
4
4
  Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production
5
5
  Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
6
6
  Classifier: Intended Audience :: Information Technology
@@ -115,9 +115,8 @@ The key features are:
115
115
  <a href="https://bump.sh/fastapi?utm_source=fastapi&utm_medium=referral&utm_campaign=sponsor" target="_blank" title="Automate FastAPI documentation generation with Bump.sh"><img src="https://fastapi.tiangolo.com/img/sponsors/bump-sh.svg"></a>
116
116
  <a href="https://github.com/scalar/scalar/?utm_source=fastapi&utm_medium=website&utm_campaign=main-badge" target="_blank" title="Scalar: Beautiful Open-Source API References from Swagger/OpenAPI files"><img src="https://fastapi.tiangolo.com/img/sponsors/scalar.svg"></a>
117
117
  <a href="https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=mainbadge" target="_blank" title="Auth, user management and more for your B2B product"><img src="https://fastapi.tiangolo.com/img/sponsors/propelauth.png"></a>
118
- <a href="https://docs.withcoherence.com/configuration/frameworks/?utm_medium=advertising&utm_source=fastapi&utm_campaign=docs#fastapi-example" target="_blank" title="Coherence"><img src="https://fastapi.tiangolo.com/img/sponsors/coherence.png"></a>
118
+ <a href="https://docs.withcoherence.com/coherence-templates/full-stack-template/#fastapi?utm_medium=advertising&utm_source=fastapi&utm_campaign=docs" target="_blank" title="Coherence"><img src="https://fastapi.tiangolo.com/img/sponsors/coherence.png"></a>
119
119
  <a href="https://www.mongodb.com/developer/languages/python/python-quickstart-fastapi/?utm_campaign=fastapi_framework&utm_source=fastapi_sponsorship&utm_medium=web_referral" target="_blank" title="Simplify Full Stack Development with FastAPI & MongoDB"><img src="https://fastapi.tiangolo.com/img/sponsors/mongodb.png"></a>
120
- <a href="https://konghq.com/products/kong-konnect?utm_medium=referral&utm_source=github&utm_campaign=platform&utm_content=fast-api" target="_blank" title="Kong Konnect - API management platform"><img src="https://fastapi.tiangolo.com/img/sponsors/kong.png"></a>
121
120
  <a href="https://zuplo.link/fastapi-gh" target="_blank" title="Zuplo: Scale, Protect, Document, and Monetize your FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/zuplo.png"></a>
122
121
  <a href="https://fine.dev?ref=fastapibadge" target="_blank" title="Fine's AI FastAPI Workflow: Effortlessly Deploy and Integrate FastAPI into Your Project"><img src="https://fastapi.tiangolo.com/img/sponsors/fine.png"></a>
123
122
  <a href="https://liblab.com?utm_source=fastapi" target="_blank" title="liblab - Generate SDKs from FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/liblab.png"></a>
@@ -457,7 +456,7 @@ Coming back to the previous code example, **FastAPI** will:
457
456
  * Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests.
458
457
  * As the `q` parameter is declared with `= None`, it is optional.
459
458
  * Without the `None` it would be required (as is the body in the case with `PUT`).
460
- * For `PUT` requests to `/items/{item_id}`, Read the body as JSON:
459
+ * For `PUT` requests to `/items/{item_id}`, read the body as JSON:
461
460
  * Check that it has a required attribute `name` that should be a `str`.
462
461
  * Check that it has a required attribute `price` that has to be a `float`.
463
462
  * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
@@ -1,18 +1,18 @@
1
- fastapi-0.112.2.dist-info/METADATA,sha256=bnN5StpCV0DjSg7dk8yiEBkSTr_hAjcMHoxYSv4LuIU,27528
2
- fastapi-0.112.2.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
- fastapi-0.112.2.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
4
- fastapi-0.112.2.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
- fastapi/__init__.py,sha256=61dw7I6Jsj1RGSw6HG8Q3fNgxEuX7Y_QoG39NNJ81C8,1081
1
+ fastapi-0.112.4.dist-info/METADATA,sha256=5HI7E6Ktp7E6DHqe-EAOrbdi9cOuiJqLfxqBCeJGmcQ,27273
2
+ fastapi-0.112.4.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
+ fastapi-0.112.4.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
4
+ fastapi-0.112.4.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
+ fastapi/__init__.py,sha256=6SxOneGIhP_aHxYYRO2T6iyZXYjC_rjss7xd3XsWl7s,1081
6
6
  fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37
7
- fastapi/_compat.py,sha256=OjE3FUZ0IPXqIJWKhoWKDNCHv4so-FQ-rfN8ngQZeFE,23134
7
+ fastapi/_compat.py,sha256=9AipVKxefipp4ymECk3VhrryBi-Hbh8A7zsljaWdGAk,23723
8
8
  fastapi/applications.py,sha256=Ix-o9pQAWhEDf9J0Q1hZ0nBB1uP72c-Y3oiYzvrwqiM,176316
9
9
  fastapi/background.py,sha256=rouLirxUANrcYC824MSMypXL_Qb2HYg2YZqaiEqbEKI,1768
10
10
  fastapi/cli.py,sha256=OYhZb0NR_deuT5ofyPF2NoNBzZDNOP8Salef2nk-HqA,418
11
11
  fastapi/concurrency.py,sha256=AYLnS4judDUmXsNRICtoKSP0prfYDcS8ehBtYW9JhQQ,1403
12
12
  fastapi/datastructures.py,sha256=b2PEz77XGq-u3Ur1Inwk0AGjOsQZO49yF9C7IPJ15cY,5766
13
13
  fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- fastapi/dependencies/models.py,sha256=-n-YCxzgVBkurQi49qOTooT71v_oeAhHJ-qQFonxh5o,2494
15
- fastapi/dependencies/utils.py,sha256=DBKj3z2oU1U3W89zTc3e6ej6V1Qy1tvx0V5ho_cYVIQ,30243
14
+ fastapi/dependencies/models.py,sha256=Pjl6vx-4nZ5Tta9kJa3-RfQKkXtCpS09-FhMgs9eWNs,1507
15
+ fastapi/dependencies/utils.py,sha256=k3bs2iNotFYcwO_RihkdJjGrBAr2SNOxf6j5u7vu9gM,31980
16
16
  fastapi/encoders.py,sha256=LvwYmFeOz4tVwvgBoC5rvZnbr7hZr73KGrU8O7zSptU,11068
17
17
  fastapi/exception_handlers.py,sha256=MBrIOA-ugjJDivIi4rSsUJBdTsjuzN76q4yh0q1COKw,1332
18
18
  fastapi/exceptions.py,sha256=taNixuFEXb67lI1bnX1ubq8y8TseJ4yoPlWjyP0fTzk,4969
@@ -28,12 +28,12 @@ fastapi/openapi/constants.py,sha256=adGzmis1L1HJRTE3kJ5fmHS_Noq6tIY6pWv_SFzoFDU,
28
28
  fastapi/openapi/docs.py,sha256=XcQq-ZbQdC5sI0gIGu5MoHK1q-OFaqws7-ORTo6sjY4,10348
29
29
  fastapi/openapi/models.py,sha256=PqkxQiqcEgjKuhfUIWPZPQcyTcubtUCB3vcObLsB7VE,15397
30
30
  fastapi/openapi/utils.py,sha256=asSbOKDuagDfpByNQvPy7OM0sqOBdUmqh64BH-n-5f0,22286
31
- fastapi/param_functions.py,sha256=Xp1h5RBdb-zlatMJ8_USkut2G9HJAgkMx7HBky9wHbM,64006
32
- fastapi/params.py,sha256=CWumi-CkfxWSg4I2KpPxIvQyKdeW_Lnct9reHnrTwW8,28199
31
+ fastapi/param_functions.py,sha256=uQzNlihlhM80u4Xbstz__D3L3yxpTqLmsC-Hra1WfqE,64018
32
+ fastapi/params.py,sha256=e_ZHoUohqWWbquiBL5AHqdsKJmTMLZzAPii6eCYR3tk,28187
33
33
  fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142
35
35
  fastapi/responses.py,sha256=QNQQlwpKhQoIPZTTWkpc9d_QGeGZ_aVQPaDV3nQ8m7c,1761
36
- fastapi/routing.py,sha256=sUxTddjKa4IokZAOzv6fHTF0MbltBtJv_QRmh4-L_eA,175040
36
+ fastapi/routing.py,sha256=LnYhRsafzHporfz4aDUmEk8qDU2tarLZNjHaKy3RB7w,176148
37
37
  fastapi/security/__init__.py,sha256=bO8pNmxqVRXUjfl2mOKiVZLn0FpBQ61VUYVjmppnbJw,881
38
38
  fastapi/security/api_key.py,sha256=_OqUUjEHG5_MT1IPAhXIGJRCPldTBdSww_DegFy_W8Y,9368
39
39
  fastapi/security/base.py,sha256=dl4pvbC-RxjfbWgPtCWd8MVU-7CB2SZ22rJDXVCXO6c,141
@@ -45,6 +45,6 @@ fastapi/staticfiles.py,sha256=iirGIt3sdY2QZXd36ijs3Cj-T0FuGFda3cd90kM9Ikw,69
45
45
  fastapi/templating.py,sha256=4zsuTWgcjcEainMJFAlW6-gnslm6AgOS1SiiDWfmQxk,76
46
46
  fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66
47
47
  fastapi/types.py,sha256=nFb36sK3DSoqoyo7Miwy3meKK5UdFBgkAgLSzQlUVyI,383
48
- fastapi/utils.py,sha256=T1OYlJBw80LMtKhvU18J6XeTX4_MMPzIGPYcJqI5QVM,8037
48
+ fastapi/utils.py,sha256=y8Bj5ttMaI9tS4D60OUgXqKnktBr99NdYUnHHV9LgoY,7948
49
49
  fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
50
- fastapi-0.112.2.dist-info/RECORD,,
50
+ fastapi-0.112.4.dist-info/RECORD,,