Browse Source

Update tests

pull/14099/head
Sebastián Ramírez 3 days ago
parent
commit
50fb38d20e
  1. 255
      fastapi/routing.py
  2. 2
      tests/test_tutorial/test_dependencies/test_tutorial008c.py

255
fastapi/routing.py

@ -1,5 +1,6 @@
import dataclasses import dataclasses
import email.message import email.message
import functools
import inspect import inspect
import json import json
import sys import sys
@ -8,6 +9,7 @@ from enum import Enum, IntEnum
from typing import ( from typing import (
Any, Any,
AsyncIterator, AsyncIterator,
Awaitable,
Callable, Callable,
Collection, Collection,
Coroutine, Coroutine,
@ -59,6 +61,8 @@ from fastapi.utils import (
) )
from pydantic import BaseModel from pydantic import BaseModel
from starlette import routing from starlette import routing
from starlette._exception_handler import wrap_app_handling_exceptions
from starlette._utils import is_async_callable
from starlette.concurrency import run_in_threadpool from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.requests import Request from starlette.requests import Request
@ -68,11 +72,9 @@ from starlette.routing import (
Match, Match,
compile_path, compile_path,
get_name, get_name,
request_response,
websocket_session,
) )
from starlette.routing import Mount as Mount # noqa from starlette.routing import Mount as Mount # noqa
from starlette.types import AppType, ASGIApp, Lifespan, Scope from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
from starlette.websockets import WebSocket from starlette.websockets import WebSocket
from typing_extensions import Annotated, Doc, deprecated from typing_extensions import Annotated, Doc, deprecated
@ -246,111 +248,119 @@ def get_request_handler(
async def app(request: Request) -> Response: async def app(request: Request) -> Response:
response: Union[Response, None] = None response: Union[Response, None] = None
async with AsyncExitStack() as file_stack: file_stack = request.scope.get("fastapi_middleware_astack")
try: assert isinstance(
body: Any = None file_stack, AsyncExitStack
if body_field: ), "fastapi_astack not found in request scope"
if is_body_form:
body = await request.form() # Read body and auto-close files
file_stack.push_async_callback(body.close) try:
else: body: Any = None
body_bytes = await request.body() if body_field:
if body_bytes: if is_body_form:
json_body: Any = Undefined body = await request.form()
content_type_value = request.headers.get("content-type") file_stack.push_async_callback(body.close)
if not content_type_value: else:
json_body = await request.json() body_bytes = await request.body()
else: if body_bytes:
message = email.message.Message() json_body: Any = Undefined
message["content-type"] = content_type_value content_type_value = request.headers.get("content-type")
if message.get_content_maintype() == "application": if not content_type_value:
subtype = message.get_content_subtype() json_body = await request.json()
if subtype == "json" or subtype.endswith("+json"): else:
json_body = await request.json() message = email.message.Message()
if json_body != Undefined: message["content-type"] = content_type_value
body = json_body if message.get_content_maintype() == "application":
else: subtype = message.get_content_subtype()
body = body_bytes if subtype == "json" or subtype.endswith("+json"):
except json.JSONDecodeError as e: json_body = await request.json()
validation_error = RequestValidationError( if json_body != Undefined:
[ body = json_body
{ else:
"type": "json_invalid", body = body_bytes
"loc": ("body", e.pos), except json.JSONDecodeError as e:
"msg": "JSON decode error", validation_error = RequestValidationError(
"input": {}, [
"ctx": {"error": e.msg}, {
} "type": "json_invalid",
], "loc": ("body", e.pos),
body=e.doc, "msg": "JSON decode error",
) "input": {},
raise validation_error from e "ctx": {"error": e.msg},
except HTTPException: }
# If a middleware raises an HTTPException, it should be raised again ],
raise body=e.doc,
except Exception as e: )
http_error = HTTPException( raise validation_error from e
status_code=400, detail="There was an error parsing the body" except HTTPException:
# If a middleware raises an HTTPException, it should be raised again
raise
except Exception as e:
http_error = HTTPException(
status_code=400, detail="There was an error parsing the body"
)
raise http_error from e
# Solve dependencies and run path operation function, auto-closing dependencies
errors: List[Any] = []
async_exit_stack = request.scope.get("fastapi_inner_astack")
assert isinstance(
async_exit_stack, AsyncExitStack
), "fastapi_inner_astack not found in request scope"
solved_result = await solve_dependencies(
request=request,
dependant=dependant,
body=body,
dependency_overrides_provider=dependency_overrides_provider,
async_exit_stack=async_exit_stack,
embed_body_fields=embed_body_fields,
)
errors = solved_result.errors
if not errors:
raw_response = await run_endpoint_function(
dependant=dependant,
values=solved_result.values,
is_coroutine=is_coroutine,
)
if isinstance(raw_response, Response):
if raw_response.background is None:
raw_response.background = solved_result.background_tasks
response = raw_response
else:
response_args: Dict[str, Any] = {
"background": solved_result.background_tasks
}
# If status_code was set, use it, otherwise use the default from the
# response class, in the case of redirect it's 307
current_status_code = (
status_code if status_code else solved_result.response.status_code
) )
raise http_error from e if current_status_code is not None:
errors: List[Any] = [] response_args["status_code"] = current_status_code
async with AsyncExitStack() as async_exit_stack: if solved_result.response.status_code:
solved_result = await solve_dependencies( response_args["status_code"] = solved_result.response.status_code
request=request, content = await serialize_response(
dependant=dependant, field=response_field,
body=body, response_content=raw_response,
dependency_overrides_provider=dependency_overrides_provider, include=response_model_include,
async_exit_stack=async_exit_stack, exclude=response_model_exclude,
embed_body_fields=embed_body_fields, by_alias=response_model_by_alias,
exclude_unset=response_model_exclude_unset,
exclude_defaults=response_model_exclude_defaults,
exclude_none=response_model_exclude_none,
is_coroutine=is_coroutine,
) )
errors = solved_result.errors response = actual_response_class(content, **response_args)
if not errors: if not is_body_allowed_for_status_code(response.status_code):
raw_response = await run_endpoint_function( response.body = b""
dependant=dependant, response.headers.raw.extend(solved_result.response.headers.raw)
values=solved_result.values, if errors:
is_coroutine=is_coroutine, validation_error = RequestValidationError(
) _normalize_errors(errors), body=body
if isinstance(raw_response, Response): )
if raw_response.background is None: raise validation_error
raw_response.background = solved_result.background_tasks
response = raw_response # Return response
else:
response_args: Dict[str, Any] = {
"background": solved_result.background_tasks
}
# If status_code was set, use it, otherwise use the default from the
# response class, in the case of redirect it's 307
current_status_code = (
status_code
if status_code
else solved_result.response.status_code
)
if current_status_code is not None:
response_args["status_code"] = current_status_code
if solved_result.response.status_code:
response_args["status_code"] = (
solved_result.response.status_code
)
content = await serialize_response(
field=response_field,
response_content=raw_response,
include=response_model_include,
exclude=response_model_exclude,
by_alias=response_model_by_alias,
exclude_unset=response_model_exclude_unset,
exclude_defaults=response_model_exclude_defaults,
exclude_none=response_model_exclude_none,
is_coroutine=is_coroutine,
)
response = actual_response_class(content, **response_args)
if not is_body_allowed_for_status_code(response.status_code):
response.body = b""
response.headers.raw.extend(solved_result.response.headers.raw)
if errors:
validation_error = RequestValidationError(
_normalize_errors(errors), body=body
)
raise validation_error
if response is None: if response is None:
raise FastAPIError( raise FastAPIError(
"No response object was returned. There's a high chance that the " "No response object was returned. There's a high chance that the "
@ -370,24 +380,23 @@ def get_websocket_app(
embed_body_fields: bool = False, embed_body_fields: bool = False,
) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:
async def app(websocket: WebSocket) -> None: async def app(websocket: WebSocket) -> None:
async with AsyncExitStack() as async_exit_stack: async_exit_stack = websocket.scope.get("fastapi_inner_astack")
# TODO: remove this scope later, after a few releases assert isinstance(
# This scope fastapi_astack is no longer used by FastAPI, kept for async_exit_stack, AsyncExitStack
# compatibility, just in case ), "fastapi_inner_astack not found in request scope"
websocket.scope["fastapi_astack"] = async_exit_stack solved_result = await solve_dependencies(
solved_result = await solve_dependencies( request=websocket,
request=websocket, dependant=dependant,
dependant=dependant, dependency_overrides_provider=dependency_overrides_provider,
dependency_overrides_provider=dependency_overrides_provider, async_exit_stack=async_exit_stack,
async_exit_stack=async_exit_stack, embed_body_fields=embed_body_fields,
embed_body_fields=embed_body_fields, )
if solved_result.errors:
raise WebSocketRequestValidationError(
_normalize_errors(solved_result.errors)
) )
if solved_result.errors: assert dependant.call is not None, "dependant.call must be a function"
raise WebSocketRequestValidationError( await dependant.call(**solved_result.values)
_normalize_errors(solved_result.errors)
)
assert dependant.call is not None, "dependant.call must be a function"
await dependant.call(**solved_result.values)
return app return app

2
tests/test_tutorial/test_dependencies/test_tutorial008c.py

@ -40,7 +40,7 @@ def test_fastapi_error(mod: ModuleType):
client = TestClient(mod.app) client = TestClient(mod.app)
with pytest.raises(FastAPIError) as exc_info: with pytest.raises(FastAPIError) as exc_info:
client.get("/items/portal-gun") client.get("/items/portal-gun")
assert "No response object was returned" in exc_info.value.args[0] assert "raising an exception and a dependency with yield" in exc_info.value.args[0]
def test_internal_server_error(mod: ModuleType): def test_internal_server_error(mod: ModuleType):

Loading…
Cancel
Save