fastapi 0.104.1__py3-none-any.whl → 0.106.0__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.104.1"
3
+ __version__ = "0.106.0"
4
4
 
5
5
  from starlette import status as status
6
6
 
fastapi/_compat.py CHANGED
@@ -249,7 +249,12 @@ if PYDANTIC_V2:
249
249
  return is_bytes_sequence_annotation(field.type_)
250
250
 
251
251
  def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
252
- return type(field_info).from_annotation(annotation)
252
+ cls = type(field_info)
253
+ merged_field_info = cls.from_annotation(annotation)
254
+ new_field_info = copy(field_info)
255
+ new_field_info.metadata = merged_field_info.metadata
256
+ new_field_info.annotation = merged_field_info.annotation
257
+ return new_field_info
253
258
 
254
259
  def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
255
260
  origin_type = (
fastapi/applications.py CHANGED
@@ -22,7 +22,6 @@ from fastapi.exception_handlers import (
22
22
  )
23
23
  from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
24
24
  from fastapi.logger import logger
25
- from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware
26
25
  from fastapi.openapi.docs import (
27
26
  get_redoc_html,
28
27
  get_swagger_ui_html,
@@ -37,8 +36,6 @@ from starlette.datastructures import State
37
36
  from starlette.exceptions import HTTPException
38
37
  from starlette.middleware import Middleware
39
38
  from starlette.middleware.base import BaseHTTPMiddleware
40
- from starlette.middleware.errors import ServerErrorMiddleware
41
- from starlette.middleware.exceptions import ExceptionMiddleware
42
39
  from starlette.requests import Request
43
40
  from starlette.responses import HTMLResponse, JSONResponse, Response
44
41
  from starlette.routing import BaseRoute
@@ -966,55 +963,6 @@ class FastAPI(Starlette):
966
963
  self.middleware_stack: Union[ASGIApp, None] = None
967
964
  self.setup()
968
965
 
969
- def build_middleware_stack(self) -> ASGIApp:
970
- # Duplicate/override from Starlette to add AsyncExitStackMiddleware
971
- # inside of ExceptionMiddleware, inside of custom user middlewares
972
- debug = self.debug
973
- error_handler = None
974
- exception_handlers = {}
975
-
976
- for key, value in self.exception_handlers.items():
977
- if key in (500, Exception):
978
- error_handler = value
979
- else:
980
- exception_handlers[key] = value
981
-
982
- middleware = (
983
- [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
984
- + self.user_middleware
985
- + [
986
- Middleware(
987
- ExceptionMiddleware, handlers=exception_handlers, debug=debug
988
- ),
989
- # Add FastAPI-specific AsyncExitStackMiddleware for dependencies with
990
- # contextvars.
991
- # This needs to happen after user middlewares because those create a
992
- # new contextvars context copy by using a new AnyIO task group.
993
- # The initial part of dependencies with 'yield' is executed in the
994
- # FastAPI code, inside all the middlewares. However, the teardown part
995
- # (after 'yield') is executed in the AsyncExitStack in this middleware.
996
- # If the AsyncExitStack lived outside of the custom middlewares and
997
- # contextvars were set in a dependency with 'yield' in that internal
998
- # contextvars context, the values would not be available in the
999
- # outer context of the AsyncExitStack.
1000
- # By placing the middleware and the AsyncExitStack here, inside all
1001
- # user middlewares, the code before and after 'yield' in dependencies
1002
- # with 'yield' is executed in the same contextvars context. Thus, all values
1003
- # set in contextvars before 'yield' are still available after 'yield,' as
1004
- # expected.
1005
- # Additionally, by having this AsyncExitStack here, after the
1006
- # ExceptionMiddleware, dependencies can now catch handled exceptions,
1007
- # e.g. HTTPException, to customize the teardown code (e.g. DB session
1008
- # rollback).
1009
- Middleware(AsyncExitStackMiddleware),
1010
- ]
1011
- )
1012
-
1013
- app = self.router
1014
- for cls, options in reversed(middleware):
1015
- app = cls(app=app, **options)
1016
- return app
1017
-
1018
966
  def openapi(self) -> Dict[str, Any]:
1019
967
  """
1020
968
  Generate the OpenAPI schema of the application. This is called by FastAPI
fastapi/concurrency.py CHANGED
@@ -1,4 +1,3 @@
1
- from contextlib import AsyncExitStack as AsyncExitStack # noqa
2
1
  from contextlib import asynccontextmanager as asynccontextmanager
3
2
  from typing import AsyncGenerator, ContextManager, TypeVar
4
3
 
@@ -1,5 +1,5 @@
1
1
  import inspect
2
- from contextlib import contextmanager
2
+ from contextlib import AsyncExitStack, contextmanager
3
3
  from copy import deepcopy
4
4
  from typing import (
5
5
  Any,
@@ -46,7 +46,6 @@ from fastapi._compat import (
46
46
  )
47
47
  from fastapi.background import BackgroundTasks
48
48
  from fastapi.concurrency import (
49
- AsyncExitStack,
50
49
  asynccontextmanager,
51
50
  contextmanager_in_threadpool,
52
51
  )
@@ -325,10 +324,11 @@ def analyze_param(
325
324
  field_info = None
326
325
  depends = None
327
326
  type_annotation: Any = Any
328
- if (
329
- annotation is not inspect.Signature.empty
330
- and get_origin(annotation) is Annotated
331
- ):
327
+ use_annotation: Any = Any
328
+ if annotation is not inspect.Signature.empty:
329
+ use_annotation = annotation
330
+ type_annotation = annotation
331
+ if get_origin(use_annotation) is Annotated:
332
332
  annotated_args = get_args(annotation)
333
333
  type_annotation = annotated_args[0]
334
334
  fastapi_annotations = [
@@ -336,14 +336,21 @@ def analyze_param(
336
336
  for arg in annotated_args[1:]
337
337
  if isinstance(arg, (FieldInfo, params.Depends))
338
338
  ]
339
- assert (
340
- len(fastapi_annotations) <= 1
341
- ), f"Cannot specify multiple `Annotated` FastAPI arguments for {param_name!r}"
342
- fastapi_annotation = next(iter(fastapi_annotations), None)
339
+ fastapi_specific_annotations = [
340
+ arg
341
+ for arg in fastapi_annotations
342
+ if isinstance(arg, (params.Param, params.Body, params.Depends))
343
+ ]
344
+ if fastapi_specific_annotations:
345
+ fastapi_annotation: Union[
346
+ FieldInfo, params.Depends, None
347
+ ] = fastapi_specific_annotations[-1]
348
+ else:
349
+ fastapi_annotation = None
343
350
  if isinstance(fastapi_annotation, FieldInfo):
344
351
  # Copy `field_info` because we mutate `field_info.default` below.
345
352
  field_info = copy_field_info(
346
- field_info=fastapi_annotation, annotation=annotation
353
+ field_info=fastapi_annotation, annotation=use_annotation
347
354
  )
348
355
  assert field_info.default is Undefined or field_info.default is Required, (
349
356
  f"`{field_info.__class__.__name__}` default value cannot be set in"
@@ -356,8 +363,6 @@ def analyze_param(
356
363
  field_info.default = Required
357
364
  elif isinstance(fastapi_annotation, params.Depends):
358
365
  depends = fastapi_annotation
359
- elif annotation is not inspect.Signature.empty:
360
- type_annotation = annotation
361
366
 
362
367
  if isinstance(value, params.Depends):
363
368
  assert depends is None, (
@@ -402,15 +407,15 @@ def analyze_param(
402
407
  # We might check here that `default_value is Required`, but the fact is that the same
403
408
  # parameter might sometimes be a path parameter and sometimes not. See
404
409
  # `tests/test_infer_param_optionality.py` for an example.
405
- field_info = params.Path(annotation=type_annotation)
410
+ field_info = params.Path(annotation=use_annotation)
406
411
  elif is_uploadfile_or_nonable_uploadfile_annotation(
407
412
  type_annotation
408
413
  ) or is_uploadfile_sequence_annotation(type_annotation):
409
- field_info = params.File(annotation=type_annotation, default=default_value)
414
+ field_info = params.File(annotation=use_annotation, default=default_value)
410
415
  elif not field_annotation_is_scalar(annotation=type_annotation):
411
- field_info = params.Body(annotation=type_annotation, default=default_value)
416
+ field_info = params.Body(annotation=use_annotation, default=default_value)
412
417
  else:
413
- field_info = params.Query(annotation=type_annotation, default=default_value)
418
+ field_info = params.Query(annotation=use_annotation, default=default_value)
414
419
 
415
420
  field = None
416
421
  if field_info is not None:
@@ -424,8 +429,8 @@ def analyze_param(
424
429
  and getattr(field_info, "in_", None) is None
425
430
  ):
426
431
  field_info.in_ = params.ParamTypes.query
427
- use_annotation = get_annotation_from_field_info(
428
- type_annotation,
432
+ use_annotation_from_field_info = get_annotation_from_field_info(
433
+ use_annotation,
429
434
  field_info,
430
435
  param_name,
431
436
  )
@@ -436,7 +441,7 @@ def analyze_param(
436
441
  field_info.alias = alias
437
442
  field = create_response_field(
438
443
  name=param_name,
439
- type_=use_annotation,
444
+ type_=use_annotation_from_field_info,
440
445
  default=field_info.default,
441
446
  alias=alias,
442
447
  required=field_info.default in (Required, Undefined),
@@ -466,16 +471,17 @@ def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
466
471
 
467
472
 
468
473
  def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
469
- field_info = cast(params.Param, field.field_info)
470
- if field_info.in_ == params.ParamTypes.path:
474
+ field_info = field.field_info
475
+ field_info_in = getattr(field_info, "in_", None)
476
+ if field_info_in == params.ParamTypes.path:
471
477
  dependant.path_params.append(field)
472
- elif field_info.in_ == params.ParamTypes.query:
478
+ elif field_info_in == params.ParamTypes.query:
473
479
  dependant.query_params.append(field)
474
- elif field_info.in_ == params.ParamTypes.header:
480
+ elif field_info_in == params.ParamTypes.header:
475
481
  dependant.header_params.append(field)
476
482
  else:
477
483
  assert (
478
- field_info.in_ == params.ParamTypes.cookie
484
+ field_info_in == params.ParamTypes.cookie
479
485
  ), f"non-body parameters must be in path, query, header or cookie: {field.name}"
480
486
  dependant.cookie_params.append(field)
481
487
 
@@ -522,6 +528,7 @@ async def solve_dependencies(
522
528
  response: Optional[Response] = None,
523
529
  dependency_overrides_provider: Optional[Any] = None,
524
530
  dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
531
+ async_exit_stack: AsyncExitStack,
525
532
  ) -> Tuple[
526
533
  Dict[str, Any],
527
534
  List[Any],
@@ -568,6 +575,7 @@ async def solve_dependencies(
568
575
  response=response,
569
576
  dependency_overrides_provider=dependency_overrides_provider,
570
577
  dependency_cache=dependency_cache,
578
+ async_exit_stack=async_exit_stack,
571
579
  )
572
580
  (
573
581
  sub_values,
@@ -583,10 +591,8 @@ async def solve_dependencies(
583
591
  if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
584
592
  solved = dependency_cache[sub_dependant.cache_key]
585
593
  elif is_gen_callable(call) or is_async_gen_callable(call):
586
- stack = request.scope.get("fastapi_astack")
587
- assert isinstance(stack, AsyncExitStack)
588
594
  solved = await solve_generator(
589
- call=call, stack=stack, sub_values=sub_values
595
+ call=call, stack=async_exit_stack, sub_values=sub_values
590
596
  )
591
597
  elif is_coroutine_callable(call):
592
598
  solved = await call(**sub_values)
fastapi/routing.py CHANGED
@@ -216,95 +216,124 @@ def get_request_handler(
216
216
  actual_response_class = response_class
217
217
 
218
218
  async def app(request: Request) -> Response:
219
- try:
220
- body: Any = None
221
- if body_field:
222
- if is_body_form:
223
- body = await request.form()
224
- stack = request.scope.get("fastapi_astack")
225
- assert isinstance(stack, AsyncExitStack)
226
- stack.push_async_callback(body.close)
219
+ exception_to_reraise: Optional[Exception] = None
220
+ response: Union[Response, None] = None
221
+ async with AsyncExitStack() as async_exit_stack:
222
+ # TODO: remove this scope later, after a few releases
223
+ # This scope fastapi_astack is no longer used by FastAPI, kept for
224
+ # compatibility, just in case
225
+ request.scope["fastapi_astack"] = async_exit_stack
226
+ try:
227
+ body: Any = None
228
+ if body_field:
229
+ if is_body_form:
230
+ body = await request.form()
231
+ async_exit_stack.push_async_callback(body.close)
232
+ else:
233
+ body_bytes = await request.body()
234
+ if body_bytes:
235
+ json_body: Any = Undefined
236
+ content_type_value = request.headers.get("content-type")
237
+ if not content_type_value:
238
+ json_body = await request.json()
239
+ else:
240
+ message = email.message.Message()
241
+ message["content-type"] = content_type_value
242
+ if message.get_content_maintype() == "application":
243
+ subtype = message.get_content_subtype()
244
+ if subtype == "json" or subtype.endswith("+json"):
245
+ json_body = await request.json()
246
+ if json_body != Undefined:
247
+ body = json_body
248
+ else:
249
+ body = body_bytes
250
+ except json.JSONDecodeError as e:
251
+ validation_error = RequestValidationError(
252
+ [
253
+ {
254
+ "type": "json_invalid",
255
+ "loc": ("body", e.pos),
256
+ "msg": "JSON decode error",
257
+ "input": {},
258
+ "ctx": {"error": e.msg},
259
+ }
260
+ ],
261
+ body=e.doc,
262
+ )
263
+ exception_to_reraise = validation_error
264
+ raise validation_error from e
265
+ except HTTPException as e:
266
+ exception_to_reraise = e
267
+ raise
268
+ except Exception as e:
269
+ http_error = HTTPException(
270
+ status_code=400, detail="There was an error parsing the body"
271
+ )
272
+ exception_to_reraise = http_error
273
+ raise http_error from e
274
+ try:
275
+ solved_result = await solve_dependencies(
276
+ request=request,
277
+ dependant=dependant,
278
+ body=body,
279
+ dependency_overrides_provider=dependency_overrides_provider,
280
+ async_exit_stack=async_exit_stack,
281
+ )
282
+ values, errors, background_tasks, sub_response, _ = solved_result
283
+ except Exception as e:
284
+ exception_to_reraise = e
285
+ raise e
286
+ if errors:
287
+ validation_error = RequestValidationError(
288
+ _normalize_errors(errors), body=body
289
+ )
290
+ exception_to_reraise = validation_error
291
+ raise validation_error
292
+ else:
293
+ try:
294
+ raw_response = await run_endpoint_function(
295
+ dependant=dependant, values=values, is_coroutine=is_coroutine
296
+ )
297
+ except Exception as e:
298
+ exception_to_reraise = e
299
+ raise e
300
+ if isinstance(raw_response, Response):
301
+ if raw_response.background is None:
302
+ raw_response.background = background_tasks
303
+ response = raw_response
227
304
  else:
228
- body_bytes = await request.body()
229
- if body_bytes:
230
- json_body: Any = Undefined
231
- content_type_value = request.headers.get("content-type")
232
- if not content_type_value:
233
- json_body = await request.json()
234
- else:
235
- message = email.message.Message()
236
- message["content-type"] = content_type_value
237
- if message.get_content_maintype() == "application":
238
- subtype = message.get_content_subtype()
239
- if subtype == "json" or subtype.endswith("+json"):
240
- json_body = await request.json()
241
- if json_body != Undefined:
242
- body = json_body
243
- else:
244
- body = body_bytes
245
- except json.JSONDecodeError as e:
246
- raise RequestValidationError(
247
- [
248
- {
249
- "type": "json_invalid",
250
- "loc": ("body", e.pos),
251
- "msg": "JSON decode error",
252
- "input": {},
253
- "ctx": {"error": e.msg},
254
- }
255
- ],
256
- body=e.doc,
257
- ) from e
258
- except HTTPException:
259
- raise
260
- except Exception as e:
261
- raise HTTPException(
262
- status_code=400, detail="There was an error parsing the body"
263
- ) from e
264
- solved_result = await solve_dependencies(
265
- request=request,
266
- dependant=dependant,
267
- body=body,
268
- dependency_overrides_provider=dependency_overrides_provider,
269
- )
270
- values, errors, background_tasks, sub_response, _ = solved_result
271
- if errors:
272
- raise RequestValidationError(_normalize_errors(errors), body=body)
273
- else:
274
- raw_response = await run_endpoint_function(
275
- dependant=dependant, values=values, is_coroutine=is_coroutine
276
- )
277
-
278
- if isinstance(raw_response, Response):
279
- if raw_response.background is None:
280
- raw_response.background = background_tasks
281
- return raw_response
282
- response_args: Dict[str, Any] = {"background": background_tasks}
283
- # If status_code was set, use it, otherwise use the default from the
284
- # response class, in the case of redirect it's 307
285
- current_status_code = (
286
- status_code if status_code else sub_response.status_code
287
- )
288
- if current_status_code is not None:
289
- response_args["status_code"] = current_status_code
290
- if sub_response.status_code:
291
- response_args["status_code"] = sub_response.status_code
292
- content = await serialize_response(
293
- field=response_field,
294
- response_content=raw_response,
295
- include=response_model_include,
296
- exclude=response_model_exclude,
297
- by_alias=response_model_by_alias,
298
- exclude_unset=response_model_exclude_unset,
299
- exclude_defaults=response_model_exclude_defaults,
300
- exclude_none=response_model_exclude_none,
301
- is_coroutine=is_coroutine,
302
- )
303
- response = actual_response_class(content, **response_args)
304
- if not is_body_allowed_for_status_code(response.status_code):
305
- response.body = b""
306
- response.headers.raw.extend(sub_response.headers.raw)
307
- return response
305
+ response_args: Dict[str, Any] = {"background": background_tasks}
306
+ # If status_code was set, use it, otherwise use the default from the
307
+ # response class, in the case of redirect it's 307
308
+ current_status_code = (
309
+ status_code if status_code else sub_response.status_code
310
+ )
311
+ if current_status_code is not None:
312
+ response_args["status_code"] = current_status_code
313
+ if sub_response.status_code:
314
+ response_args["status_code"] = sub_response.status_code
315
+ content = await serialize_response(
316
+ field=response_field,
317
+ response_content=raw_response,
318
+ include=response_model_include,
319
+ exclude=response_model_exclude,
320
+ by_alias=response_model_by_alias,
321
+ exclude_unset=response_model_exclude_unset,
322
+ exclude_defaults=response_model_exclude_defaults,
323
+ exclude_none=response_model_exclude_none,
324
+ is_coroutine=is_coroutine,
325
+ )
326
+ response = actual_response_class(content, **response_args)
327
+ if not is_body_allowed_for_status_code(response.status_code):
328
+ response.body = b""
329
+ response.headers.raw.extend(sub_response.headers.raw)
330
+ # This exception was possibly handled by the dependency but it should
331
+ # still bubble up so that the ServerErrorMiddleware can return a 500
332
+ # or the ExceptionMiddleware can catch and handle any other exceptions
333
+ if exception_to_reraise:
334
+ raise exception_to_reraise
335
+ assert response is not None, "An error occurred while generating the request"
336
+ return response
308
337
 
309
338
  return app
310
339
 
@@ -313,16 +342,22 @@ def get_websocket_app(
313
342
  dependant: Dependant, dependency_overrides_provider: Optional[Any] = None
314
343
  ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:
315
344
  async def app(websocket: WebSocket) -> None:
316
- solved_result = await solve_dependencies(
317
- request=websocket,
318
- dependant=dependant,
319
- dependency_overrides_provider=dependency_overrides_provider,
320
- )
321
- values, errors, _, _2, _3 = solved_result
322
- if errors:
323
- raise WebSocketRequestValidationError(_normalize_errors(errors))
324
- assert dependant.call is not None, "dependant.call must be a function"
325
- await dependant.call(**values)
345
+ async with AsyncExitStack() as async_exit_stack:
346
+ # TODO: remove this scope later, after a few releases
347
+ # This scope fastapi_astack is no longer used by FastAPI, kept for
348
+ # compatibility, just in case
349
+ websocket.scope["fastapi_astack"] = async_exit_stack
350
+ solved_result = await solve_dependencies(
351
+ request=websocket,
352
+ dependant=dependant,
353
+ dependency_overrides_provider=dependency_overrides_provider,
354
+ async_exit_stack=async_exit_stack,
355
+ )
356
+ values, errors, _, _2, _3 = solved_result
357
+ if errors:
358
+ raise WebSocketRequestValidationError(_normalize_errors(errors))
359
+ assert dependant.call is not None, "dependant.call must be a function"
360
+ await dependant.call(**values)
326
361
 
327
362
  return app
328
363
 
fastapi/types.py CHANGED
@@ -6,6 +6,5 @@ from pydantic import BaseModel
6
6
 
7
7
  DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])
8
8
  UnionType = getattr(types, "UnionType", Union)
9
- NoneType = getattr(types, "UnionType", None)
10
9
  ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str]
11
10
  IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi
3
- Version: 0.104.1
3
+ Version: 0.106.0
4
4
  Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production
5
5
  Project-URL: Homepage, https://github.com/tiangolo/fastapi
6
6
  Project-URL: Documentation, https://fastapi.tiangolo.com/
@@ -103,9 +103,11 @@ The key features are:
103
103
 
104
104
  <a href="https://cryptapi.io/" target="_blank" title="CryptAPI: Your easy to use, secure and privacy oriented payment gateway."><img src="https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg"></a>
105
105
  <a href="https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" target="_blank" title="Build, run and scale your apps on a modern, reliable, and secure PaaS."><img src="https://fastapi.tiangolo.com/img/sponsors/platform-sh.png"></a>
106
- <a href="https://www.buildwithfern.com/?utm_source=tiangolo&utm_medium=website&utm_campaign=main-badge" target="_blank" title="Fern | SDKs and API docs"><img src="https://fastapi.tiangolo.com/img/sponsors/fern.svg"></a>
107
106
  <a href="https://www.porter.run" target="_blank" title="Deploy FastAPI on AWS with a few clicks"><img src="https://fastapi.tiangolo.com/img/sponsors/porter.png"></a>
108
107
  <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>
108
+ <a href="https://reflex.dev" target="_blank" title="Reflex"><img src="https://fastapi.tiangolo.com/img/sponsors/reflex.png"></a>
109
+ <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>
110
+ <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>
109
111
  <a href="https://www.deta.sh/?ref=fastapi" target="_blank" title="The launchpad for all your (team's) ideas"><img src="https://fastapi.tiangolo.com/img/sponsors/deta.svg"></a>
110
112
  <a href="https://training.talkpython.fm/fastapi-courses" target="_blank" title="FastAPI video courses on demand from people you trust"><img src="https://fastapi.tiangolo.com/img/sponsors/talkpython.png"></a>
111
113
  <a href="https://testdriven.io/courses/tdd-fastapi/" target="_blank" title="Learn to build high-quality web apps with best practices"><img src="https://fastapi.tiangolo.com/img/sponsors/testdriven.svg"></a>
@@ -114,6 +116,7 @@ The key features are:
114
116
  <a href="https://databento.com/" target="_blank" title="Pay as you go for market data"><img src="https://fastapi.tiangolo.com/img/sponsors/databento.svg"></a>
115
117
  <a href="https://speakeasyapi.dev?utm_source=fastapi+repo&utm_medium=github+sponsorship" target="_blank" title="SDKs for your API | Speakeasy"><img src="https://fastapi.tiangolo.com/img/sponsors/speakeasy.png"></a>
116
118
  <a href="https://www.svix.com/" target="_blank" title="Svix - Webhooks as a service"><img src="https://fastapi.tiangolo.com/img/sponsors/svix.svg"></a>
119
+ <a href="https://www.codacy.com/?utm_source=github&utm_medium=sponsors&utm_id=pioneers" target="_blank" title="Take code reviews from hours to minutes"><img src="https://fastapi.tiangolo.com/img/sponsors/codacy.png"></a>
117
120
 
118
121
  <!-- /sponsors -->
119
122
 
@@ -1,8 +1,8 @@
1
- fastapi/__init__.py,sha256=n8125d7_qIsNGVM_1QL7_LpYtGH8GYrkJjgSMjP31cE,1081
2
- fastapi/_compat.py,sha256=BlQp8ec0cFM6FLAEASdpYd7Ip9TY1FZr8PGiGRO4QLg,22798
3
- fastapi/applications.py,sha256=C7mT6eZh0XUO2HmLM43_gJMyqjoyy_SdgypDHRrLu34,179073
1
+ fastapi/__init__.py,sha256=7JbocWwEkmJC10EiyLujBbEG9V3fhU-JKWgGzp3lmwk,1081
2
+ fastapi/_compat.py,sha256=i_LwkpFVbNCuraFRbc1UmNNocX63BAwjjBXgF59JJVQ,23027
3
+ fastapi/applications.py,sha256=nUoSz0Lhif39vAw9KATzFH2Xjz73kftOMCiwq6osNfg,176375
4
4
  fastapi/background.py,sha256=F1tsrJKfDZaRchNgF9ykB2PcRaPBJTbL4htN45TJAIc,1799
5
- fastapi/concurrency.py,sha256=NAK9SMlTCOALLjTAR6KzWUDEkVj7_EyNRz0-lDVW_W8,1467
5
+ fastapi/concurrency.py,sha256=AYLnS4judDUmXsNRICtoKSP0prfYDcS8ehBtYW9JhQQ,1403
6
6
  fastapi/datastructures.py,sha256=FF1s2g6cAQ5XxlNToB3scgV94Zf3DjdzcaI7ToaTrmg,5797
7
7
  fastapi/encoders.py,sha256=90lbmIW8NZjpPVzbgKhpY49B7TFqa7hrdQDQa70SM9U,11024
8
8
  fastapi/exception_handlers.py,sha256=MBrIOA-ugjJDivIi4rSsUJBdTsjuzN76q4yh0q1COKw,1332
@@ -13,18 +13,17 @@ fastapi/params.py,sha256=LzjihAvODd3w7-GddraUyVtH1xfwR9smIoQn-Z_g4mg,27807
13
13
  fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142
15
15
  fastapi/responses.py,sha256=QNQQlwpKhQoIPZTTWkpc9d_QGeGZ_aVQPaDV3nQ8m7c,1761
16
- fastapi/routing.py,sha256=VADa3-b52ahpweFCcmAKXkVKldMrfF60N5gZWobI42M,172198
16
+ fastapi/routing.py,sha256=YTWD3Cfwq1RxHN0-9WJSD9J4W3SSh52oKxYUud2XtcY,174483
17
17
  fastapi/staticfiles.py,sha256=iirGIt3sdY2QZXd36ijs3Cj-T0FuGFda3cd90kM9Ikw,69
18
18
  fastapi/templating.py,sha256=4zsuTWgcjcEainMJFAlW6-gnslm6AgOS1SiiDWfmQxk,76
19
19
  fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66
20
- fastapi/types.py,sha256=WZJ1jvm1MCwIrxxRYxKwtXS9HqcGk0RnCbLzrMZh-lI,428
20
+ fastapi/types.py,sha256=nFb36sK3DSoqoyo7Miwy3meKK5UdFBgkAgLSzQlUVyI,383
21
21
  fastapi/utils.py,sha256=rpSasHpgooPIfe67yU3HzOMDv7PtxiG9x6K-bhu6Z18,8193
22
22
  fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
23
23
  fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  fastapi/dependencies/models.py,sha256=-n-YCxzgVBkurQi49qOTooT71v_oeAhHJ-qQFonxh5o,2494
25
- fastapi/dependencies/utils.py,sha256=DjRdd_NVdXh_jDYKTRjUIXkwkLD0WE4oFXQC4peMr2c,29915
25
+ fastapi/dependencies/utils.py,sha256=IUY-uZmp56BUZuEiHtTQT0iGNF5ASIst7AzFMzA_As8,30158
26
26
  fastapi/middleware/__init__.py,sha256=oQDxiFVcc1fYJUOIFvphnK7pTT5kktmfL32QXpBFvvo,58
27
- fastapi/middleware/asyncexitstack.py,sha256=LvMyVI1QdmWNWYPZqx295VFavssUfVpUsonPOsMWz1E,1035
28
27
  fastapi/middleware/cors.py,sha256=ynwjWQZoc_vbhzZ3_ZXceoaSrslHFHPdoM52rXr0WUU,79
29
28
  fastapi/middleware/gzip.py,sha256=xM5PcsH8QlAimZw4VDvcmTnqQamslThsfe3CVN2voa0,79
30
29
  fastapi/middleware/httpsredirect.py,sha256=rL8eXMnmLijwVkH7_400zHri1AekfeBd6D6qs8ix950,115
@@ -42,7 +41,7 @@ fastapi/security/http.py,sha256=_YdhSRRUCGydVDUILygWg0VlkPA28t_gjcy_axD3eOk,1353
42
41
  fastapi/security/oauth2.py,sha256=QAUOE2f6KXbXjkrJIIYCOugI6-R0g9EECZ5t8eN9nA4,21612
43
42
  fastapi/security/open_id_connect_url.py,sha256=Mb8wFxrRh4CrsFW0RcjBEQLASPHGDtZRP6c2dCrspAg,2753
44
43
  fastapi/security/utils.py,sha256=bd8T0YM7UQD5ATKucr1bNtAvz_Y3__dVNAv5UebiPvc,293
45
- fastapi-0.104.1.dist-info/METADATA,sha256=Zgj7yzBMm50KgBZsq5R9A29zVk7LMUvkUC6oTWuR8J0,24298
46
- fastapi-0.104.1.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
47
- fastapi-0.104.1.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
48
- fastapi-0.104.1.dist-info/RECORD,,
44
+ fastapi-0.106.0.dist-info/METADATA,sha256=m8-HdxWfRji5ynzlkoOQaAAxR1k1mkpt7UQXcxpUZmk,24944
45
+ fastapi-0.106.0.dist-info/WHEEL,sha256=mRYSEL3Ih6g5a_CVMIcwiF__0Ae4_gLYh01YFNwiq1k,87
46
+ fastapi-0.106.0.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
47
+ fastapi-0.106.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.18.0
2
+ Generator: hatchling 1.21.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,25 +0,0 @@
1
- from typing import Optional
2
-
3
- from fastapi.concurrency import AsyncExitStack
4
- from starlette.types import ASGIApp, Receive, Scope, Send
5
-
6
-
7
- class AsyncExitStackMiddleware:
8
- def __init__(self, app: ASGIApp, context_name: str = "fastapi_astack") -> None:
9
- self.app = app
10
- self.context_name = context_name
11
-
12
- async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
13
- dependency_exception: Optional[Exception] = None
14
- async with AsyncExitStack() as stack:
15
- scope[self.context_name] = stack
16
- try:
17
- await self.app(scope, receive, send)
18
- except Exception as e:
19
- dependency_exception = e
20
- raise e
21
- if dependency_exception:
22
- # This exception was possibly handled by the dependency but it should
23
- # still bubble up so that the ServerErrorMiddleware can return a 500
24
- # or the ExceptionMiddleware can catch and handle any other exceptions
25
- raise dependency_exception