fastapi 0.105.0__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.105.0"
3
+ __version__ = "0.106.0"
4
4
 
5
5
  from starlette import status as status
6
6
 
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
  )
@@ -529,6 +528,7 @@ async def solve_dependencies(
529
528
  response: Optional[Response] = None,
530
529
  dependency_overrides_provider: Optional[Any] = None,
531
530
  dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
531
+ async_exit_stack: AsyncExitStack,
532
532
  ) -> Tuple[
533
533
  Dict[str, Any],
534
534
  List[Any],
@@ -575,6 +575,7 @@ async def solve_dependencies(
575
575
  response=response,
576
576
  dependency_overrides_provider=dependency_overrides_provider,
577
577
  dependency_cache=dependency_cache,
578
+ async_exit_stack=async_exit_stack,
578
579
  )
579
580
  (
580
581
  sub_values,
@@ -590,10 +591,8 @@ async def solve_dependencies(
590
591
  if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
591
592
  solved = dependency_cache[sub_dependant.cache_key]
592
593
  elif is_gen_callable(call) or is_async_gen_callable(call):
593
- stack = request.scope.get("fastapi_astack")
594
- assert isinstance(stack, AsyncExitStack)
595
594
  solved = await solve_generator(
596
- call=call, stack=stack, sub_values=sub_values
595
+ call=call, stack=async_exit_stack, sub_values=sub_values
597
596
  )
598
597
  elif is_coroutine_callable(call):
599
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi
3
- Version: 0.105.0
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/
@@ -1,8 +1,8 @@
1
- fastapi/__init__.py,sha256=0MXz3K7abb_1VOoAC12BCaaqS0mKbKHJNPyhyLHj1n4,1081
1
+ fastapi/__init__.py,sha256=7JbocWwEkmJC10EiyLujBbEG9V3fhU-JKWgGzp3lmwk,1081
2
2
  fastapi/_compat.py,sha256=i_LwkpFVbNCuraFRbc1UmNNocX63BAwjjBXgF59JJVQ,23027
3
- fastapi/applications.py,sha256=C7mT6eZh0XUO2HmLM43_gJMyqjoyy_SdgypDHRrLu34,179073
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,7 +13,7 @@ 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
@@ -22,9 +22,8 @@ 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=kcy4qBgJJ1GhcyqB0kjbpR7WFwWSpPqKaWoQc_gczQo,30175
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.105.0.dist-info/METADATA,sha256=7ymLL1jjQ9NyFmAZD4cLbVJwo5wjAED0no5irmmGPKI,24944
46
- fastapi-0.105.0.dist-info/WHEEL,sha256=Gw8jIuTWkPakxikG-6o91zOxTjpZNZ00Qw8KZLqAprY,87
47
- fastapi-0.105.0.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
48
- fastapi-0.105.0.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.19.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