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 +1 -1
- fastapi/_compat.py +6 -1
- fastapi/applications.py +0 -52
- fastapi/concurrency.py +0 -1
- fastapi/dependencies/utils.py +34 -28
- fastapi/routing.py +133 -98
- fastapi/types.py +0 -1
- {fastapi-0.104.1.dist-info → fastapi-0.106.0.dist-info}/METADATA +5 -2
- {fastapi-0.104.1.dist-info → fastapi-0.106.0.dist-info}/RECORD +11 -12
- {fastapi-0.104.1.dist-info → fastapi-0.106.0.dist-info}/WHEEL +1 -1
- fastapi/middleware/asyncexitstack.py +0 -25
- {fastapi-0.104.1.dist-info → fastapi-0.106.0.dist-info}/licenses/LICENSE +0 -0
fastapi/__init__.py
CHANGED
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
|
-
|
|
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
fastapi/dependencies/utils.py
CHANGED
|
@@ -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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
416
|
+
field_info = params.Body(annotation=use_annotation, default=default_value)
|
|
412
417
|
else:
|
|
413
|
-
field_info = params.Query(annotation=
|
|
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
|
-
|
|
428
|
-
|
|
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_=
|
|
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 =
|
|
470
|
-
|
|
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
|
|
478
|
+
elif field_info_in == params.ParamTypes.query:
|
|
473
479
|
dependant.query_params.append(field)
|
|
474
|
-
elif
|
|
480
|
+
elif field_info_in == params.ParamTypes.header:
|
|
475
481
|
dependant.header_params.append(field)
|
|
476
482
|
else:
|
|
477
483
|
assert (
|
|
478
|
-
|
|
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=
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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.
|
|
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=
|
|
2
|
-
fastapi/_compat.py,sha256=
|
|
3
|
-
fastapi/applications.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
46
|
-
fastapi-0.
|
|
47
|
-
fastapi-0.
|
|
48
|
-
fastapi-0.
|
|
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,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
|
|
File without changes
|