fastapi 0.112.1__py3-none-any.whl → 0.112.3__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.1"
3
+ __version__ = "0.112.3"
4
4
 
5
5
  from starlette import status as status
6
6
 
fastapi/applications.py CHANGED
@@ -1056,7 +1056,7 @@ class FastAPI(Starlette):
1056
1056
  def add_api_route(
1057
1057
  self,
1058
1058
  path: str,
1059
- endpoint: Callable[..., Coroutine[Any, Any, Response]],
1059
+ endpoint: Callable[..., Any],
1060
1060
  *,
1061
1061
  response_model: Any = Default(None),
1062
1062
  status_code: Optional[int] = None,
@@ -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,7 +55,7 @@ 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
@@ -79,25 +80,23 @@ multipart_incorrect_install_error = (
79
80
  )
80
81
 
81
82
 
82
- def check_file_field(field: ModelField) -> None:
83
- field_info = field.field_info
84
- if isinstance(field_info, params.Form):
83
+ def ensure_multipart_is_installed() -> None:
84
+ try:
85
+ # __version__ is available in both multiparts, and can be mocked
86
+ from multipart import __version__ # type: ignore
87
+
88
+ assert __version__
85
89
  try:
86
- # __version__ is available in both multiparts, and can be mocked
87
- from multipart import __version__ # type: ignore
88
-
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
93
-
94
- assert parse_options_header
95
- except ImportError:
96
- logger.error(multipart_incorrect_install_error)
97
- raise RuntimeError(multipart_incorrect_install_error) from None
90
+ # parse_options_header is only available in the right multipart
91
+ from multipart.multipart import parse_options_header # type: ignore
92
+
93
+ assert parse_options_header
98
94
  except ImportError:
99
- logger.error(multipart_not_installed_error)
100
- raise RuntimeError(multipart_not_installed_error) from None
95
+ logger.error(multipart_incorrect_install_error)
96
+ raise RuntimeError(multipart_incorrect_install_error) from None
97
+ except ImportError:
98
+ logger.error(multipart_not_installed_error)
99
+ raise RuntimeError(multipart_not_installed_error) from None
101
100
 
102
101
 
103
102
  def get_param_sub_dependant(
@@ -175,7 +174,7 @@ def get_flat_dependant(
175
174
  header_params=dependant.header_params.copy(),
176
175
  cookie_params=dependant.cookie_params.copy(),
177
176
  body_params=dependant.body_params.copy(),
178
- security_schemes=dependant.security_requirements.copy(),
177
+ security_requirements=dependant.security_requirements.copy(),
179
178
  use_cache=dependant.use_cache,
180
179
  path=dependant.path,
181
180
  )
@@ -258,16 +257,16 @@ def get_dependant(
258
257
  )
259
258
  for param_name, param in signature_params.items():
260
259
  is_path_param = param_name in path_param_names
261
- type_annotation, depends, param_field = analyze_param(
260
+ param_details = analyze_param(
262
261
  param_name=param_name,
263
262
  annotation=param.annotation,
264
263
  value=param.default,
265
264
  is_path_param=is_path_param,
266
265
  )
267
- if depends is not None:
266
+ if param_details.depends is not None:
268
267
  sub_dependant = get_param_sub_dependant(
269
268
  param_name=param_name,
270
- depends=depends,
269
+ depends=param_details.depends,
271
270
  path=path,
272
271
  security_scopes=security_scopes,
273
272
  )
@@ -275,18 +274,18 @@ def get_dependant(
275
274
  continue
276
275
  if add_non_field_param_to_dependency(
277
276
  param_name=param_name,
278
- type_annotation=type_annotation,
277
+ type_annotation=param_details.type_annotation,
279
278
  dependant=dependant,
280
279
  ):
281
280
  assert (
282
- param_field is None
281
+ param_details.field is None
283
282
  ), f"Cannot specify multiple FastAPI annotations for {param_name!r}"
284
283
  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)
284
+ assert param_details.field is not None
285
+ if is_body_param(param_field=param_details.field, is_path_param=is_path_param):
286
+ dependant.body_params.append(param_details.field)
288
287
  else:
289
- add_param_to_fields(field=param_field, dependant=dependant)
288
+ add_param_to_fields(field=param_details.field, dependant=dependant)
290
289
  return dependant
291
290
 
292
291
 
@@ -314,13 +313,20 @@ def add_non_field_param_to_dependency(
314
313
  return None
315
314
 
316
315
 
316
+ @dataclass
317
+ class ParamDetails:
318
+ type_annotation: Any
319
+ depends: Optional[params.Depends]
320
+ field: Optional[ModelField]
321
+
322
+
317
323
  def analyze_param(
318
324
  *,
319
325
  param_name: str,
320
326
  annotation: Any,
321
327
  value: Any,
322
328
  is_path_param: bool,
323
- ) -> Tuple[Any, Optional[params.Depends], Optional[ModelField]]:
329
+ ) -> ParamDetails:
324
330
  field_info = None
325
331
  depends = None
326
332
  type_annotation: Any = Any
@@ -328,6 +334,7 @@ def analyze_param(
328
334
  if annotation is not inspect.Signature.empty:
329
335
  use_annotation = annotation
330
336
  type_annotation = annotation
337
+ # Extract Annotated info
331
338
  if get_origin(use_annotation) is Annotated:
332
339
  annotated_args = get_args(annotation)
333
340
  type_annotation = annotated_args[0]
@@ -342,11 +349,12 @@ def analyze_param(
342
349
  if isinstance(arg, (params.Param, params.Body, params.Depends))
343
350
  ]
344
351
  if fastapi_specific_annotations:
345
- fastapi_annotation: Union[
346
- FieldInfo, params.Depends, None
347
- ] = fastapi_specific_annotations[-1]
352
+ fastapi_annotation: Union[FieldInfo, params.Depends, None] = (
353
+ fastapi_specific_annotations[-1]
354
+ )
348
355
  else:
349
356
  fastapi_annotation = None
357
+ # Set default for Annotated FieldInfo
350
358
  if isinstance(fastapi_annotation, FieldInfo):
351
359
  # Copy `field_info` because we mutate `field_info.default` below.
352
360
  field_info = copy_field_info(
@@ -361,9 +369,10 @@ def analyze_param(
361
369
  field_info.default = value
362
370
  else:
363
371
  field_info.default = Required
372
+ # Get Annotated Depends
364
373
  elif isinstance(fastapi_annotation, params.Depends):
365
374
  depends = fastapi_annotation
366
-
375
+ # Get Depends from default value
367
376
  if isinstance(value, params.Depends):
368
377
  assert depends is None, (
369
378
  "Cannot specify `Depends` in `Annotated` and default value"
@@ -374,6 +383,7 @@ def analyze_param(
374
383
  f" default value together for {param_name!r}"
375
384
  )
376
385
  depends = value
386
+ # Get FieldInfo from default value
377
387
  elif isinstance(value, FieldInfo):
378
388
  assert field_info is None, (
379
389
  "Cannot specify FastAPI annotations in `Annotated` and default value"
@@ -383,11 +393,13 @@ def analyze_param(
383
393
  if PYDANTIC_V2:
384
394
  field_info.annotation = type_annotation
385
395
 
396
+ # Get Depends from type annotation
386
397
  if depends is not None and depends.dependency is None:
387
398
  # Copy `depends` before mutating it
388
399
  depends = copy(depends)
389
400
  depends.dependency = type_annotation
390
401
 
402
+ # Handle non-param type annotations like Request
391
403
  if lenient_issubclass(
392
404
  type_annotation,
393
405
  (
@@ -403,6 +415,7 @@ def analyze_param(
403
415
  assert (
404
416
  field_info is None
405
417
  ), f"Cannot specify FastAPI annotation for type {type_annotation!r}"
418
+ # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value
406
419
  elif field_info is None and depends is None:
407
420
  default_value = value if value is not inspect.Signature.empty else Required
408
421
  if is_path_param:
@@ -420,7 +433,9 @@ def analyze_param(
420
433
  field_info = params.Query(annotation=use_annotation, default=default_value)
421
434
 
422
435
  field = None
436
+ # It's a field_info, not a dependency
423
437
  if field_info is not None:
438
+ # Handle field_info.in_
424
439
  if is_path_param:
425
440
  assert isinstance(field_info, params.Path), (
426
441
  f"Cannot use `{field_info.__class__.__name__}` for path param"
@@ -436,12 +451,14 @@ def analyze_param(
436
451
  field_info,
437
452
  param_name,
438
453
  )
454
+ if isinstance(field_info, params.Form):
455
+ ensure_multipart_is_installed()
439
456
  if not field_info.alias and getattr(field_info, "convert_underscores", None):
440
457
  alias = param_name.replace("_", "-")
441
458
  else:
442
459
  alias = field_info.alias or param_name
443
460
  field_info.alias = alias
444
- field = create_response_field(
461
+ field = create_model_field(
445
462
  name=param_name,
446
463
  type_=use_annotation_from_field_info,
447
464
  default=field_info.default,
@@ -450,7 +467,7 @@ def analyze_param(
450
467
  field_info=field_info,
451
468
  )
452
469
 
453
- return type_annotation, depends, field
470
+ return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
454
471
 
455
472
 
456
473
  def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
@@ -521,6 +538,15 @@ async def solve_generator(
521
538
  return await stack.enter_async_context(cm)
522
539
 
523
540
 
541
+ @dataclass
542
+ class SolvedDependency:
543
+ values: Dict[str, Any]
544
+ errors: List[Any]
545
+ background_tasks: Optional[StarletteBackgroundTasks]
546
+ response: Response
547
+ dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any]
548
+
549
+
524
550
  async def solve_dependencies(
525
551
  *,
526
552
  request: Union[Request, WebSocket],
@@ -531,13 +557,7 @@ async def solve_dependencies(
531
557
  dependency_overrides_provider: Optional[Any] = None,
532
558
  dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
533
559
  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
- ]:
560
+ ) -> SolvedDependency:
541
561
  values: Dict[str, Any] = {}
542
562
  errors: List[Any] = []
543
563
  if response is None:
@@ -579,27 +599,21 @@ async def solve_dependencies(
579
599
  dependency_cache=dependency_cache,
580
600
  async_exit_stack=async_exit_stack,
581
601
  )
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)
602
+ background_tasks = solved_result.background_tasks
603
+ dependency_cache.update(solved_result.dependency_cache)
604
+ if solved_result.errors:
605
+ errors.extend(solved_result.errors)
592
606
  continue
593
607
  if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
594
608
  solved = dependency_cache[sub_dependant.cache_key]
595
609
  elif is_gen_callable(call) or is_async_gen_callable(call):
596
610
  solved = await solve_generator(
597
- call=call, stack=async_exit_stack, sub_values=sub_values
611
+ call=call, stack=async_exit_stack, sub_values=solved_result.values
598
612
  )
599
613
  elif is_coroutine_callable(call):
600
- solved = await call(**sub_values)
614
+ solved = await call(**solved_result.values)
601
615
  else:
602
- solved = await run_in_threadpool(call, **sub_values)
616
+ solved = await run_in_threadpool(call, **solved_result.values)
603
617
  if sub_dependant.name is not None:
604
618
  values[sub_dependant.name] = solved
605
619
  if sub_dependant.cache_key not in dependency_cache:
@@ -646,7 +660,13 @@ async def solve_dependencies(
646
660
  values[dependant.security_scopes_param_name] = SecurityScopes(
647
661
  scopes=dependant.security_scopes
648
662
  )
649
- return values, errors, background_tasks, response, dependency_cache
663
+ return SolvedDependency(
664
+ values=values,
665
+ errors=errors,
666
+ background_tasks=background_tasks,
667
+ response=response,
668
+ dependency_cache=dependency_cache,
669
+ )
650
670
 
651
671
 
652
672
  def request_params_to_args(
@@ -775,7 +795,6 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
775
795
  embed = getattr(field_info, "embed", None)
776
796
  body_param_names_set = {param.name for param in flat_dependant.body_params}
777
797
  if len(body_param_names_set) == 1 and not embed:
778
- check_file_field(first_param)
779
798
  return first_param
780
799
  # If one field requires to embed, all have to be embedded
781
800
  # in case a sub-dependency is evaluated with a single unique body field
@@ -807,12 +826,11 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
807
826
  ]
808
827
  if len(set(body_param_media_types)) == 1:
809
828
  BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
810
- final_field = create_response_field(
829
+ final_field = create_model_field(
811
830
  name="body",
812
831
  type_=BodyModel,
813
832
  required=required,
814
833
  alias="body",
815
834
  field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
816
835
  )
817
- check_file_field(final_field)
818
836
  return final_field
@@ -2343,7 +2343,7 @@ def Security( # noqa: N802
2343
2343
  ```python
2344
2344
  from typing import Annotated
2345
2345
 
2346
- from fastapi import Depends, FastAPI
2346
+ from fastapi import Security, FastAPI
2347
2347
 
2348
2348
  from .db import User
2349
2349
  from .security import get_current_active_user
fastapi/params.py CHANGED
@@ -91,7 +91,7 @@ class Param(FieldInfo):
91
91
  max_length=max_length,
92
92
  discriminator=discriminator,
93
93
  multiple_of=multiple_of,
94
- allow_nan=allow_inf_nan,
94
+ allow_inf_nan=allow_inf_nan,
95
95
  max_digits=max_digits,
96
96
  decimal_places=decimal_places,
97
97
  **extra,
@@ -547,7 +547,7 @@ class Body(FieldInfo):
547
547
  max_length=max_length,
548
548
  discriminator=discriminator,
549
549
  multiple_of=multiple_of,
550
- allow_nan=allow_inf_nan,
550
+ allow_inf_nan=allow_inf_nan,
551
551
  max_digits=max_digits,
552
552
  decimal_places=decimal_places,
553
553
  **extra,
fastapi/routing.py CHANGED
@@ -3,14 +3,16 @@ import dataclasses
3
3
  import email.message
4
4
  import inspect
5
5
  import json
6
- from contextlib import AsyncExitStack
6
+ from contextlib import AsyncExitStack, asynccontextmanager
7
7
  from enum import Enum, IntEnum
8
8
  from typing import (
9
9
  Any,
10
+ AsyncIterator,
10
11
  Callable,
11
12
  Coroutine,
12
13
  Dict,
13
14
  List,
15
+ Mapping,
14
16
  Optional,
15
17
  Sequence,
16
18
  Set,
@@ -47,7 +49,7 @@ from fastapi.exceptions import (
47
49
  from fastapi.types import DecoratedCallable, IncEx
48
50
  from fastapi.utils import (
49
51
  create_cloned_field,
50
- create_response_field,
52
+ create_model_field,
51
53
  generate_unique_id,
52
54
  get_value_or_default,
53
55
  is_body_allowed_for_status_code,
@@ -67,7 +69,7 @@ from starlette.routing import (
67
69
  websocket_session,
68
70
  )
69
71
  from starlette.routing import Mount as Mount # noqa
70
- from starlette.types import ASGIApp, Lifespan, Scope
72
+ from starlette.types import AppType, ASGIApp, Lifespan, Scope
71
73
  from starlette.websockets import WebSocket
72
74
  from typing_extensions import Annotated, Doc, deprecated
73
75
 
@@ -119,6 +121,23 @@ def _prepare_response_content(
119
121
  return res
120
122
 
121
123
 
124
+ def _merge_lifespan_context(
125
+ original_context: Lifespan[Any], nested_context: Lifespan[Any]
126
+ ) -> Lifespan[Any]:
127
+ @asynccontextmanager
128
+ async def merged_lifespan(
129
+ app: AppType,
130
+ ) -> AsyncIterator[Optional[Mapping[str, Any]]]:
131
+ async with original_context(app) as maybe_original_state:
132
+ async with nested_context(app) as maybe_nested_state:
133
+ if maybe_nested_state is None and maybe_original_state is None:
134
+ yield None # old ASGI compatibility
135
+ else:
136
+ yield {**(maybe_nested_state or {}), **(maybe_original_state or {})}
137
+
138
+ return merged_lifespan # type: ignore[return-value]
139
+
140
+
122
141
  async def serialize_response(
123
142
  *,
124
143
  field: Optional[ModelField] = None,
@@ -273,26 +292,34 @@ def get_request_handler(
273
292
  dependency_overrides_provider=dependency_overrides_provider,
274
293
  async_exit_stack=async_exit_stack,
275
294
  )
276
- values, errors, background_tasks, sub_response, _ = solved_result
295
+ errors = solved_result.errors
277
296
  if not errors:
278
297
  raw_response = await run_endpoint_function(
279
- dependant=dependant, values=values, is_coroutine=is_coroutine
298
+ dependant=dependant,
299
+ values=solved_result.values,
300
+ is_coroutine=is_coroutine,
280
301
  )
281
302
  if isinstance(raw_response, Response):
282
303
  if raw_response.background is None:
283
- raw_response.background = background_tasks
304
+ raw_response.background = solved_result.background_tasks
284
305
  response = raw_response
285
306
  else:
286
- response_args: Dict[str, Any] = {"background": background_tasks}
307
+ response_args: Dict[str, Any] = {
308
+ "background": solved_result.background_tasks
309
+ }
287
310
  # If status_code was set, use it, otherwise use the default from the
288
311
  # response class, in the case of redirect it's 307
289
312
  current_status_code = (
290
- status_code if status_code else sub_response.status_code
313
+ status_code
314
+ if status_code
315
+ else solved_result.response.status_code
291
316
  )
292
317
  if current_status_code is not None:
293
318
  response_args["status_code"] = current_status_code
294
- if sub_response.status_code:
295
- response_args["status_code"] = sub_response.status_code
319
+ if solved_result.response.status_code:
320
+ response_args["status_code"] = (
321
+ solved_result.response.status_code
322
+ )
296
323
  content = await serialize_response(
297
324
  field=response_field,
298
325
  response_content=raw_response,
@@ -307,7 +334,7 @@ def get_request_handler(
307
334
  response = actual_response_class(content, **response_args)
308
335
  if not is_body_allowed_for_status_code(response.status_code):
309
336
  response.body = b""
310
- response.headers.raw.extend(sub_response.headers.raw)
337
+ response.headers.raw.extend(solved_result.response.headers.raw)
311
338
  if errors:
312
339
  validation_error = RequestValidationError(
313
340
  _normalize_errors(errors), body=body
@@ -341,11 +368,12 @@ def get_websocket_app(
341
368
  dependency_overrides_provider=dependency_overrides_provider,
342
369
  async_exit_stack=async_exit_stack,
343
370
  )
344
- values, errors, _, _2, _3 = solved_result
345
- if errors:
346
- raise WebSocketRequestValidationError(_normalize_errors(errors))
371
+ if solved_result.errors:
372
+ raise WebSocketRequestValidationError(
373
+ _normalize_errors(solved_result.errors)
374
+ )
347
375
  assert dependant.call is not None, "dependant.call must be a function"
348
- await dependant.call(**values)
376
+ await dependant.call(**solved_result.values)
349
377
 
350
378
  return app
351
379
 
@@ -454,9 +482,9 @@ class APIRoute(routing.Route):
454
482
  methods = ["GET"]
455
483
  self.methods: Set[str] = {method.upper() for method in methods}
456
484
  if isinstance(generate_unique_id_function, DefaultPlaceholder):
457
- current_generate_unique_id: Callable[
458
- ["APIRoute"], str
459
- ] = generate_unique_id_function.value
485
+ current_generate_unique_id: Callable[[APIRoute], str] = (
486
+ generate_unique_id_function.value
487
+ )
460
488
  else:
461
489
  current_generate_unique_id = generate_unique_id_function
462
490
  self.unique_id = self.operation_id or current_generate_unique_id(self)
@@ -469,7 +497,7 @@ class APIRoute(routing.Route):
469
497
  status_code
470
498
  ), f"Status code {status_code} must not have a response body"
471
499
  response_name = "Response_" + self.unique_id
472
- self.response_field = create_response_field(
500
+ self.response_field = create_model_field(
473
501
  name=response_name,
474
502
  type_=self.response_model,
475
503
  mode="serialization",
@@ -482,9 +510,9 @@ class APIRoute(routing.Route):
482
510
  # By being a new field, no inheritance will be passed as is. A new model
483
511
  # will always be created.
484
512
  # TODO: remove when deprecating Pydantic v1
485
- self.secure_cloned_response_field: Optional[
486
- ModelField
487
- ] = create_cloned_field(self.response_field)
513
+ self.secure_cloned_response_field: Optional[ModelField] = (
514
+ create_cloned_field(self.response_field)
515
+ )
488
516
  else:
489
517
  self.response_field = None # type: ignore
490
518
  self.secure_cloned_response_field = None
@@ -502,7 +530,7 @@ class APIRoute(routing.Route):
502
530
  additional_status_code
503
531
  ), f"Status code {additional_status_code} must not have a response body"
504
532
  response_name = f"Response_{additional_status_code}_{self.unique_id}"
505
- response_field = create_response_field(name=response_name, type_=model)
533
+ response_field = create_model_field(name=response_name, type_=model)
506
534
  response_fields[additional_status_code] = response_field
507
535
  if response_fields:
508
536
  self.response_fields: Dict[Union[int, str], ModelField] = response_fields
@@ -1308,6 +1336,10 @@ class APIRouter(routing.Router):
1308
1336
  self.add_event_handler("startup", handler)
1309
1337
  for handler in router.on_shutdown:
1310
1338
  self.add_event_handler("shutdown", handler)
1339
+ self.lifespan_context = _merge_lifespan_context(
1340
+ self.lifespan_context,
1341
+ router.lifespan_context,
1342
+ )
1311
1343
 
1312
1344
  def get(
1313
1345
  self,
fastapi/utils.py CHANGED
@@ -34,9 +34,9 @@ if TYPE_CHECKING: # pragma: nocover
34
34
  from .routing import APIRoute
35
35
 
36
36
  # Cache for `create_cloned_field`
37
- _CLONED_TYPES_CACHE: MutableMapping[
38
- Type[BaseModel], Type[BaseModel]
39
- ] = WeakKeyDictionary()
37
+ _CLONED_TYPES_CACHE: MutableMapping[Type[BaseModel], Type[BaseModel]] = (
38
+ WeakKeyDictionary()
39
+ )
40
40
 
41
41
 
42
42
  def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool:
@@ -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.1
3
+ Version: 0.112.3
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
@@ -43,7 +43,7 @@ Requires-Dist: fastapi-cli[standard]>=0.0.5; extra == "standard"
43
43
  Requires-Dist: httpx>=0.23.0; extra == "standard"
44
44
  Requires-Dist: jinja2>=2.11.2; extra == "standard"
45
45
  Requires-Dist: python-multipart>=0.0.7; extra == "standard"
46
- Requires-Dist: email_validator>=2.0.0; extra == "standard"
46
+ Requires-Dist: email-validator>=2.0.0; extra == "standard"
47
47
  Requires-Dist: uvicorn[standard]>=0.12.0; extra == "standard"
48
48
  Requires-Dist: fastapi-cli[standard]>=0.0.5; extra == "all"
49
49
  Requires-Dist: httpx>=0.23.0; extra == "all"
@@ -53,7 +53,7 @@ Requires-Dist: itsdangerous>=1.1.0; extra == "all"
53
53
  Requires-Dist: pyyaml>=5.3.1; extra == "all"
54
54
  Requires-Dist: ujson!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,>=4.0.1; extra == "all"
55
55
  Requires-Dist: orjson>=3.2.1; extra == "all"
56
- Requires-Dist: email_validator>=2.0.0; extra == "all"
56
+ Requires-Dist: email-validator>=2.0.0; extra == "all"
57
57
  Requires-Dist: uvicorn[standard]>=0.12.0; extra == "all"
58
58
  Requires-Dist: pydantic-settings>=2.0.0; extra == "all"
59
59
  Requires-Dist: pydantic-extra-types>=2.0.0; extra == "all"
@@ -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>
@@ -195,6 +194,8 @@ FastAPI stands on the shoulders of giants:
195
194
 
196
195
  ## Installation
197
196
 
197
+ Create and activate a <a href="https://fastapi.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a> and then install FastAPI:
198
+
198
199
  <div class="termy">
199
200
 
200
201
  ```console
@@ -455,7 +456,7 @@ Coming back to the previous code example, **FastAPI** will:
455
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.
456
457
  * As the `q` parameter is declared with `= None`, it is optional.
457
458
  * Without the `None` it would be required (as is the body in the case with `PUT`).
458
- * 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:
459
460
  * Check that it has a required attribute `name` that should be a `str`.
460
461
  * Check that it has a required attribute `price` that has to be a `float`.
461
462
  * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present.
@@ -525,7 +526,7 @@ When you install FastAPI with `pip install "fastapi[standard]"` it comes the `st
525
526
 
526
527
  Used by Pydantic:
527
528
 
528
- * <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email_validator</code></a> - for email validation.
529
+ * <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - for email validation.
529
530
 
530
531
  Used by Starlette:
531
532
 
@@ -1,18 +1,18 @@
1
- fastapi-0.112.1.dist-info/METADATA,sha256=DXZ6wUq8_97Y7baE_tdgj78VG-88WZ0CquDFrNDELO0,27356
2
- fastapi-0.112.1.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
- fastapi-0.112.1.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
4
- fastapi-0.112.1.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
- fastapi/__init__.py,sha256=mJPNeiw1woqECVo-LHqyg3k8MTR2Rf9MWiPCLJpgBJQ,1081
1
+ fastapi-0.112.3.dist-info/METADATA,sha256=w1QfelBarqYSCtz6ly70TBP_VdihTpwEujW_K2sRg9E,27273
2
+ fastapi-0.112.3.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
+ fastapi-0.112.3.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
4
+ fastapi-0.112.3.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
+ fastapi/__init__.py,sha256=o7tP2gKcsHMiiJNCKrN-GA3wqWjGR54B379fYdZ3_TA,1081
6
6
  fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37
7
7
  fastapi/_compat.py,sha256=OjE3FUZ0IPXqIJWKhoWKDNCHv4so-FQ-rfN8ngQZeFE,23134
8
- fastapi/applications.py,sha256=h0hVWT7fXS05N3ZAC72mtf1pu5uJmTdJVEw62uUMf7k,176342
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=rynOrwW6BKSv0bTVDCY1ag4kRsOzJwmmzO31sRKsTzA,30241
14
+ fastapi/dependencies/models.py,sha256=Pjl6vx-4nZ5Tta9kJa3-RfQKkXtCpS09-FhMgs9eWNs,1507
15
+ fastapi/dependencies/utils.py,sha256=2J36LBZMJTZ6jadjANaG-voTfzgs8JpROJCRtoLoSFA,30922
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=LcVyxFoK-W1gYGaH7H1dGvth1alwwxXouReg4zKSk88,64005
32
- fastapi/params.py,sha256=GB7aNcyBt8xFUVLnLzt8AGJfZAncQJvwd4N8nhjcXHk,28191
31
+ fastapi/param_functions.py,sha256=Xp1h5RBdb-zlatMJ8_USkut2G9HJAgkMx7HBky9wHbM,64006
32
+ fastapi/params.py,sha256=CWumi-CkfxWSg4I2KpPxIvQyKdeW_Lnct9reHnrTwW8,28199
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=dcQBjxUsO9KT0pmp1ewJzM8qD3wjcZi1BrnyyqYELoE,174150
36
+ fastapi/routing.py,sha256=e_H0k8q4HQl88mgi-Jdf-9hBfn2gTvDjixXrufDN3U0,175326
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=lHKngr-TmOx9QzSyA6PXYSvEgxPYUIk5t3u-kZtskEM,8035
48
+ fastapi/utils.py,sha256=y8Bj5ttMaI9tS4D60OUgXqKnktBr99NdYUnHHV9LgoY,7948
49
49
  fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
50
- fastapi-0.112.1.dist-info/RECORD,,
50
+ fastapi-0.112.3.dist-info/RECORD,,