Browse Source

🔨 refactoring in fastapi/routing

pull/14232/head
Evgeny Bokshitsky 9 months ago
parent
commit
3cd1ba3c14
  1. 50
      fastapi/routing.py

50
fastapi/routing.py

@ -380,7 +380,6 @@ def get_request_handler(
actual_strict_content_type = strict_content_type actual_strict_content_type = strict_content_type
async def app(request: Request) -> Response: async def app(request: Request) -> Response:
response: Response | None = None
file_stack = request.scope.get("fastapi_middleware_astack") file_stack = request.scope.get("fastapi_middleware_astack")
assert isinstance(file_stack, AsyncExitStack), ( assert isinstance(file_stack, AsyncExitStack), (
"fastapi_middleware_astack not found in request scope" "fastapi_middleware_astack not found in request scope"
@ -449,7 +448,6 @@ def get_request_handler(
raise http_error from e raise http_error from e
# Solve dependencies and run path operation function, auto-closing dependencies # Solve dependencies and run path operation function, auto-closing dependencies
errors: list[Any] = []
async_exit_stack = request.scope.get("fastapi_inner_astack") async_exit_stack = request.scope.get("fastapi_inner_astack")
assert isinstance(async_exit_stack, AsyncExitStack), ( assert isinstance(async_exit_stack, AsyncExitStack), (
"fastapi_inner_astack not found in request scope" "fastapi_inner_astack not found in request scope"
@ -464,15 +462,17 @@ def get_request_handler(
) )
errors = solved_result.errors errors = solved_result.errors
assert dependant.call # For types assert dependant.call # For types
if not errors: if errors:
raise RequestValidationError(
errors, body=body, endpoint_ctx=endpoint_ctx
)
# Shared serializer for stream items (JSONL and SSE). # Shared serializer for stream items (JSONL and SSE).
# Validates against stream_item_field when set, then # Validates against stream_item_field when set, then
# serializes to JSON bytes. # serializes to JSON bytes.
def _serialize_data(data: Any) -> bytes: def _serialize_data(data: Any) -> bytes:
if stream_item_field: if stream_item_field:
value, errors_ = stream_item_field.validate( value, errors_ = stream_item_field.validate(data, {}, loc=("response",))
data, {}, loc=("response",)
)
if errors_: if errors_:
ctx = endpoint_ctx or EndpointContext() ctx = endpoint_ctx or EndpointContext()
raise ResponseValidationError( raise ResponseValidationError(
@ -489,7 +489,7 @@ def get_request_handler(
exclude_defaults=response_model_exclude_defaults, exclude_defaults=response_model_exclude_defaults,
exclude_none=response_model_exclude_none, exclude_none=response_model_exclude_none,
) )
else:
data = jsonable_encoder(data) data = jsonable_encoder(data)
return json.dumps(data).encode("utf-8") return json.dumps(data).encode("utf-8")
@ -519,9 +519,7 @@ def get_request_handler(
retry=item.retry, retry=item.retry,
comment=item.comment, comment=item.comment,
) )
else:
# Plain object: validate + serialize via
# stream_item_field (if set) and wrap in data field
return format_sse_event( return format_sse_event(
data_str=_serialize_data(item).decode("utf-8") data_str=_serialize_data(item).decode("utf-8")
) )
@ -583,15 +581,9 @@ def get_request_handler(
yield receive_keepalive yield receive_keepalive
tg.cancel_scope.cancel() tg.cancel_scope.cancel()
# Enter the SSE context manager on the request-scoped
# exit stack. The stack outlives the streaming response,
# so __aexit__ runs via proper structured teardown, not
# via GeneratorExit thrown into an async generator.
sse_receive_stream = await async_exit_stack.enter_async_context( sse_receive_stream = await async_exit_stack.enter_async_context(
_sse_producer_cm() _sse_producer_cm()
) )
# Ensure the receive stream is closed when the exit stack
# unwinds, preventing ResourceWarning from __del__.
async_exit_stack.push_async_callback(sse_receive_stream.aclose) async_exit_stack.push_async_callback(sse_receive_stream.aclose)
async def _sse_with_checkpoints( async def _sse_with_checkpoints(
@ -607,7 +599,6 @@ def get_request_handler(
sse_stream_content: AsyncIterator[bytes] | Iterator[bytes] = ( sse_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (
_sse_with_checkpoints(sse_receive_stream) _sse_with_checkpoints(sse_receive_stream)
) )
response = StreamingResponse( response = StreamingResponse(
sse_stream_content, sse_stream_content,
media_type="text/event-stream", media_type="text/event-stream",
@ -617,7 +608,9 @@ def get_request_handler(
# For Nginx proxies to not buffer server sent events # For Nginx proxies to not buffer server sent events
response.headers["X-Accel-Buffering"] = "no" response.headers["X-Accel-Buffering"] = "no"
response.headers.raw.extend(solved_result.response.headers.raw) response.headers.raw.extend(solved_result.response.headers.raw)
elif is_json_stream: return response
if is_json_stream:
# Generator endpoint: stream as JSONL # Generator endpoint: stream as JSONL
gen = dependant.call(**solved_result.values) gen = dependant.call(**solved_result.values)
@ -650,8 +643,10 @@ def get_request_handler(
background=solved_result.background_tasks, background=solved_result.background_tasks,
) )
response.headers.raw.extend(solved_result.response.headers.raw) response.headers.raw.extend(solved_result.response.headers.raw)
elif dependant.is_async_gen_callable or dependant.is_gen_callable: return response
# Raw streaming with explicit response_class (e.g. StreamingResponse)
if dependant.is_async_gen_callable or dependant.is_gen_callable:
# Raw streaming with explicit response_class
gen = dependant.call(**solved_result.values) gen = dependant.call(**solved_result.values)
if dependant.is_async_gen_callable: if dependant.is_async_gen_callable:
@ -670,7 +665,8 @@ def get_request_handler(
) )
response = actual_response_class(content=gen, **response_args) response = actual_response_class(content=gen, **response_args)
response.headers.raw.extend(solved_result.response.headers.raw) response.headers.raw.extend(solved_result.response.headers.raw)
else: return response
raw_response = await run_endpoint_function( raw_response = await run_endpoint_function(
dependant=dependant, dependant=dependant,
values=solved_result.values, values=solved_result.values,
@ -679,8 +675,8 @@ def get_request_handler(
if isinstance(raw_response, Response): if isinstance(raw_response, Response):
if raw_response.background is None: if raw_response.background is None:
raw_response.background = solved_result.background_tasks raw_response.background = solved_result.background_tasks
response = raw_response return raw_response
else:
response_args = _build_response_args( response_args = _build_response_args(
status_code=status_code, solved_result=solved_result status_code=status_code, solved_result=solved_result
) )
@ -716,14 +712,6 @@ def get_request_handler(
if not is_body_allowed_for_status_code(response.status_code): if not is_body_allowed_for_status_code(response.status_code):
response.body = b"" response.body = b""
response.headers.raw.extend(solved_result.response.headers.raw) response.headers.raw.extend(solved_result.response.headers.raw)
if errors:
validation_error = RequestValidationError(
errors, body=body, endpoint_ctx=endpoint_ctx
)
raise validation_error
# Return response
assert response
return response return response
return app return app

Loading…
Cancel
Save