From 813742492e6d0712bd7d8a82f341f2581da677fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:36:16 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/routing-architecture.md | 8 +- fastapi/routing.py | 7 +- fastapi/routing_handlers.py | 25 +++-- fastapi/routing_router.py | 99 +++---------------- fastapi/routing_routes.py | 38 +++---- fastapi/routing_utils.py | 15 +-- tests/test_routing_facade_contract.py | 1 - 7 files changed, 68 insertions(+), 125 deletions(-) diff --git a/docs/en/docs/advanced/routing-architecture.md b/docs/en/docs/advanced/routing-architecture.md index a2f5676b5b..6bb4d140be 100644 --- a/docs/en/docs/advanced/routing-architecture.md +++ b/docs/en/docs/advanced/routing-architecture.md @@ -1,15 +1,15 @@ -# Routing Architecture +# Routing Architecture { #routing-architecture } FastAPI keeps public routing imports stable at `fastapi.routing`, but the implementation is split into focused internal modules for maintainability. -## Public facade +## Public facade { #public-facade } - `fastapi.routing` - compatibility facade and stable imports - re-exports core symbols such as `APIRouter`, `APIRoute`, and helper hooks -## Internal modules +## Internal modules { #internal-modules } - `fastapi.routing_router` - `APIRouter` implementation @@ -20,7 +20,7 @@ implementation is split into focused internal modules for maintainability. - `fastapi.routing_utils` - low-level shared utilities (response serialization, lifespan helpers, wrappers) -## Stability contract +## Stability contract { #stability-contract } - Application code should import from `fastapi.routing` (or top-level `fastapi`). - Internal modules are implementation details and may evolve. diff --git a/fastapi/routing.py b/fastapi/routing.py index 89d843808c..0f002e74c4 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -9,8 +9,6 @@ split across internal modules: - `fastapi.routing_utils` """ -from starlette.routing import Mount as Mount - from fastapi.routing_handlers import ( RouteHandlerConfig as RouteHandlerConfig, ) @@ -21,7 +19,9 @@ from fastapi.routing_routes import APIRoute as APIRoute from fastapi.routing_routes import APIWebSocketRoute as APIWebSocketRoute from fastapi.routing_utils import _DefaultLifespan as _DefaultLifespan from fastapi.routing_utils import _merge_lifespan_context as _merge_lifespan_context -from fastapi.routing_utils import _wrap_gen_lifespan_context as _wrap_gen_lifespan_context +from fastapi.routing_utils import ( + _wrap_gen_lifespan_context as _wrap_gen_lifespan_context, +) from fastapi.routing_utils import build_response_args as _build_response_args from fastapi.routing_utils import extract_endpoint_context as _extract_endpoint_context from fastapi.routing_utils import request_response as request_response @@ -33,6 +33,7 @@ from fastapi.sse import KEEPALIVE_COMMENT as KEEPALIVE_COMMENT from fastapi.sse import EventSourceResponse as EventSourceResponse from fastapi.sse import ServerSentEvent as ServerSentEvent from fastapi.sse import format_sse_event as format_sse_event +from starlette.routing import Mount as Mount __all__ = [ "Mount", diff --git a/fastapi/routing_handlers.py b/fastapi/routing_handlers.py index a4ef3d2ec8..133150cac6 100644 --- a/fastapi/routing_handlers.py +++ b/fastapi/routing_handlers.py @@ -223,9 +223,12 @@ def get_request_handler( errors = solved_result.errors assert dependant.call if not errors: + def _serialize_data(data: Any) -> bytes: if stream_item_field: - value, errors_ = stream_item_field.validate(data, {}, loc=("response",)) + value, errors_ = stream_item_field.validate( + data, {}, loc=("response",) + ) if errors_: ctx = endpoint_ctx or EndpointContext() raise ResponseValidationError( @@ -266,7 +269,9 @@ def get_request_handler( retry=item.retry, comment=item.comment, ) - return format_sse_event(data_str=_serialize_data(item).decode("utf-8")) + return format_sse_event( + data_str=_serialize_data(item).decode("utf-8") + ) if dependant.is_async_gen_callable: sse_aiter: AsyncIterator[Any] = gen.__aiter__() @@ -274,19 +279,21 @@ def get_request_handler( sse_aiter = iterate_in_threadpool(gen) @asynccontextmanager - async def _sse_producer_cm() -> AsyncIterator[ObjectReceiveStream[bytes]]: - send_stream, receive_stream = anyio.create_memory_object_stream[bytes]( - max_buffer_size=1 - ) + async def _sse_producer_cm() -> AsyncIterator[ + ObjectReceiveStream[bytes] + ]: + send_stream, receive_stream = anyio.create_memory_object_stream[ + bytes + ](max_buffer_size=1) async def _producer() -> None: async with send_stream: async for raw_item in sse_aiter: await send_stream.send(_serialize_sse_item(raw_item)) - send_keepalive, receive_keepalive = anyio.create_memory_object_stream[ - bytes - ](max_buffer_size=1) + send_keepalive, receive_keepalive = ( + anyio.create_memory_object_stream[bytes](max_buffer_size=1) + ) async def _keepalive_inserter() -> None: async with send_keepalive, receive_stream: diff --git a/fastapi/routing_router.py b/fastapi/routing_router.py index 47fa3e05ec..89c3c6c402 100644 --- a/fastapi/routing_router.py +++ b/fastapi/routing_router.py @@ -1,122 +1,53 @@ -import contextlib -import email.message -import functools import inspect -import json -import types from collections.abc import ( - AsyncIterator, - Awaitable, Callable, Collection, - Coroutine, - Generator, - Iterator, - Mapping, Sequence, ) from contextlib import ( - AbstractAsyncContextManager, - AbstractContextManager, - AsyncExitStack, asynccontextmanager, ) -from enum import Enum, IntEnum +from enum import Enum from typing import ( Annotated, Any, - TypeVar, - cast, ) -import anyio from annotated_doc import Doc -from anyio.abc import ObjectReceiveStream from fastapi import params -from fastapi._compat import ( - ModelField, - Undefined, - lenient_issubclass, -) from fastapi.datastructures import Default, DefaultPlaceholder -from fastapi.dependencies.models import Dependant -from fastapi.dependencies.utils import ( - _should_embed_body_fields, - get_body_field, - get_dependant, - get_flat_dependant, - get_parameterless_sub_dependant, - get_stream_item_type, - get_typed_return_annotation, - solve_dependencies, -) -from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( - EndpointContext, FastAPIError, - RequestValidationError, - ResponseValidationError, - WebSocketRequestValidationError, ) -from fastapi.sse import ( - _PING_INTERVAL, - KEEPALIVE_COMMENT, - EventSourceResponse, - ServerSentEvent, - format_sse_event, +from fastapi.routing_handlers import ( + get_request_handler as get_request_handler, +) +from fastapi.routing_handlers import ( + get_websocket_app as get_websocket_app, +) +from fastapi.routing_routes import APIRoute as APIRoute +from fastapi.routing_routes import APIWebSocketRoute as APIWebSocketRoute +from fastapi.routing_utils import ( + _DefaultLifespan, + _merge_lifespan_context, + _wrap_gen_lifespan_context, ) from fastapi.types import DecoratedCallable, IncEx from fastapi.utils import ( - create_model_field, generate_unique_id, get_value_or_default, - is_body_allowed_for_status_code, ) from starlette import routing -from starlette._exception_handler import wrap_app_handling_exceptions from starlette._utils import is_async_callable -from starlette.concurrency import iterate_in_threadpool, run_in_threadpool -from starlette.datastructures import FormData -from starlette.exceptions import HTTPException -from starlette.requests import Request -from starlette.responses import JSONResponse, Response, StreamingResponse +from starlette.responses import JSONResponse, Response from starlette.routing import ( BaseRoute, - Match, - compile_path, - get_name, ) from starlette.routing import Mount as Mount # noqa -from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send -from starlette.websockets import WebSocket +from starlette.types import ASGIApp, Lifespan from typing_extensions import deprecated -from fastapi.routing_utils import ( - _DefaultLifespan, - _merge_lifespan_context, - _wrap_gen_lifespan_context, - build_response_args as _build_response_args, - extract_endpoint_context as _extract_endpoint_context, - request_response, - run_endpoint_function, - serialize_response, - websocket_session, -) - - -from fastapi.routing_handlers import ( - get_request_handler as get_request_handler, -) -from fastapi.routing_handlers import ( - get_websocket_app as get_websocket_app, -) - - -from fastapi.routing_routes import APIRoute as APIRoute -from fastapi.routing_routes import APIWebSocketRoute as APIWebSocketRoute - - class APIRouter(routing.Router): """ `APIRouter` class, used to group *path operations*, for example to structure diff --git a/fastapi/routing_routes.py b/fastapi/routing_routes.py index e2d34c3819..61035d77c1 100644 --- a/fastapi/routing_routes.py +++ b/fastapi/routing_routes.py @@ -23,7 +23,11 @@ from fastapi.routing_handlers import ( from fastapi.routing_utils import request_response, websocket_session from fastapi.sse import EventSourceResponse, ServerSentEvent from fastapi.types import IncEx -from fastapi.utils import create_model_field, generate_unique_id, is_body_allowed_for_status_code +from fastapi.utils import ( + create_model_field, + generate_unique_id, + is_body_allowed_for_status_code, +) from starlette import routing from starlette.requests import Request from starlette.responses import JSONResponse, Response @@ -233,22 +237,22 @@ class APIRoute(routing.Route): def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: return get_request_handler( RouteHandlerConfig( - dependant=self.dependant, - body_field=self.body_field, - status_code=self.status_code, - response_class=self.response_class, - response_field=self.response_field, - response_model_include=self.response_model_include, - response_model_exclude=self.response_model_exclude, - response_model_by_alias=self.response_model_by_alias, - response_model_exclude_unset=self.response_model_exclude_unset, - response_model_exclude_defaults=self.response_model_exclude_defaults, - response_model_exclude_none=self.response_model_exclude_none, - dependency_overrides_provider=self.dependency_overrides_provider, - embed_body_fields=self._embed_body_fields, - strict_content_type=self.strict_content_type, - stream_item_field=self.stream_item_field, - is_json_stream=self.is_json_stream, + dependant=self.dependant, + body_field=self.body_field, + status_code=self.status_code, + response_class=self.response_class, + response_field=self.response_field, + response_model_include=self.response_model_include, + response_model_exclude=self.response_model_exclude, + response_model_by_alias=self.response_model_by_alias, + response_model_exclude_unset=self.response_model_exclude_unset, + response_model_exclude_defaults=self.response_model_exclude_defaults, + response_model_exclude_none=self.response_model_exclude_none, + dependency_overrides_provider=self.dependency_overrides_provider, + embed_body_fields=self._embed_body_fields, + strict_content_type=self.strict_content_type, + stream_item_field=self.stream_item_field, + is_json_stream=self.is_json_stream, ) ) diff --git a/fastapi/routing_utils.py b/fastapi/routing_utils.py index 3f0ffe282f..6baf2226b9 100644 --- a/fastapi/routing_utils.py +++ b/fastapi/routing_utils.py @@ -6,7 +6,6 @@ from collections.abc import ( AsyncIterator, Awaitable, Callable, - Coroutine, Generator, Mapping, ) @@ -32,7 +31,7 @@ from starlette._utils import is_async_callable from starlette.concurrency import run_in_threadpool from starlette.requests import Request from starlette.responses import Response -from starlette.types import ASGIApp, AppType, Lifespan, Receive, Scope, Send +from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send from starlette.websockets import WebSocket _T = TypeVar("_T") @@ -42,9 +41,7 @@ def request_response( func: Callable[[Request], Awaitable[Response] | Response], ) -> ASGIApp: f: Callable[[Request], Awaitable[Response]] = ( - func - if is_async_callable(func) - else functools.partial(run_in_threadpool, func) # type: ignore[call-arg] + func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type: ignore[call-arg] ) async def app(scope: Scope, receive: Receive, send: Send) -> None: @@ -221,9 +218,13 @@ async def run_endpoint_function( return await run_in_threadpool(dependant.call, **values) -def build_response_args(*, status_code: int | None, solved_result: Any) -> dict[str, Any]: +def build_response_args( + *, status_code: int | None, solved_result: Any +) -> dict[str, Any]: response_args: dict[str, Any] = {"background": solved_result.background_tasks} - current_status_code = status_code if status_code else solved_result.response.status_code + 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: diff --git a/tests/test_routing_facade_contract.py b/tests/test_routing_facade_contract.py index 65392ad96f..dc044d8dba 100644 --- a/tests/test_routing_facade_contract.py +++ b/tests/test_routing_facade_contract.py @@ -1,5 +1,4 @@ import fastapi.routing as routing - from fastapi.routing_handlers import RouteHandlerConfig from fastapi.routing_router import APIRouter from fastapi.routing_routes import APIRoute, APIWebSocketRoute