fastapi/routing.py PYTHON 6,551 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 6,551.
1import contextlib2import copy3import email.message4import errno5import functools6import inspect7import json8import math9import os10import stat11import threading12import types13import warnings14from collections.abc import (15    AsyncIterator,16    Awaitable,17    Callable,18    Collection,19    Coroutine,20    Generator,21    Iterator,22    Mapping,23    Sequence,24)25from contextlib import (26    AbstractAsyncContextManager,27    AbstractContextManager,28    AsyncExitStack,29    asynccontextmanager,30)31from contextvars import ContextVar32from dataclasses import dataclass, field33from enum import Enum, IntEnum34from typing import (35    Annotated,36    Any,37    Literal,38    Protocol,39    TypeVar,40    cast,41)4243import anyio44from annotated_doc import Doc45from anyio.abc import ObjectReceiveStream46from fastapi import params47from fastapi._compat import (48    ModelField,49    Undefined,50    lenient_issubclass,51)52from fastapi.datastructures import Default, DefaultPlaceholder53from fastapi.dependencies.models import (54    Dependant,55    _is_async_gen_callable,56    _is_coroutine_callable,57    _is_gen_callable,58)59from fastapi.dependencies.utils import (60    SolvedDependency,61    _get_body_field,62    _get_flat_body_params,63    _should_embed_body_fields,64    get_dependant,65    get_parameterless_sub_dependant,66    get_stream_item_type,67    get_typed_return_annotation,68    solve_dependencies,69)70from fastapi.encoders import jsonable_encoder71from fastapi.exceptions import (72    EndpointContext,73    FastAPIError,74    RequestValidationError,75    ResponseValidationError,76    WebSocketRequestValidationError,77)78from fastapi.sse import (79    _PING_INTERVAL,80    KEEPALIVE_COMMENT,81    EventSourceResponse,82    ServerSentEvent,83    format_sse_event,84)85from fastapi.telemetry._api import (86    _operation,87    _route_selected,88    _run_sync_endpoint,89    _validation_failed,90    get_telemetry_data,91)92from fastapi.types import DecoratedCallable, IncEx93from fastapi.utils import (94    create_model_field,95    generate_unique_id,96    get_value_or_default,97    is_body_allowed_for_status_code,98)99from starlette import routing100from starlette._exception_handler import wrap_app_handling_exceptions101from starlette._utils import get_route_path, is_async_callable102from starlette.concurrency import iterate_in_threadpool, run_in_threadpool103from starlette.datastructures import URL, FormData, URLPath104from starlette.exceptions import HTTPException105from starlette.requests import Request106from starlette.responses import (107    JSONResponse,108    PlainTextResponse,109    RedirectResponse,110    Response,111    StreamingResponse,112)113from starlette.routing import (114    BaseRoute,115    Match,116    NoMatchFound,117    compile_path,118    get_name,119)120from starlette.routing import Mount as Mount  # noqa121from starlette.staticfiles import StaticFiles122from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send123from starlette.websockets import WebSocket124from typing_extensions import deprecated125126127# Copy of starlette.routing.request_response modified to include the128# dependencies' AsyncExitStack129def request_response(130    func: Callable[[Request], Awaitable[Response] | Response],131) -> ASGIApp:132    """133    Takes a function or coroutine `func(request) -> response`,134    and returns an ASGI application.135    """136    f: Callable[[Request], Awaitable[Response]] = (137        func  # type: ignore[assignment]138        if is_async_callable(func)139        else functools.partial(run_in_threadpool, func)  # type: ignore[call-arg]140    )  # ty: ignore[invalid-assignment]141142    async def app(scope: Scope, receive: Receive, send: Send) -> None:143        request = Request(scope, receive, send)144145        async def app(scope: Scope, receive: Receive, send: Send) -> None:146            # Starts customization147            response_awaited = False148            async with AsyncExitStack() as request_stack:149                scope["fastapi_inner_astack"] = request_stack150                async with AsyncExitStack() as function_stack:151                    scope["fastapi_function_astack"] = function_stack152                    response = await f(request)153                await response(scope, receive, send)154                # Continues customization155                response_awaited = True156            if not response_awaited:157                raise FastAPIError(158                    "Response not awaited. There's a high chance that the "159                    "application code is raising an exception and a dependency with yield "160                    "has a block with a bare except, or a block with except Exception, "161                    "and is not raising the exception again. Read more about it in the "162                    "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except"163                )164165        # Same as in Starlette166        await wrap_app_handling_exceptions(app, request)(scope, receive, send)167168    return app169170171# Copy of starlette.routing.websocket_session modified to include the172# dependencies' AsyncExitStack173def websocket_session(174    func: Callable[[WebSocket], Awaitable[None]],175) -> ASGIApp:176    """177    Takes a coroutine `func(session)`, and returns an ASGI application.178    """179    # assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async"180181    async def app(scope: Scope, receive: Receive, send: Send) -> None:182        session = WebSocket(scope, receive=receive, send=send)183184        async def app(scope: Scope, receive: Receive, send: Send) -> None:185            async with AsyncExitStack() as request_stack:186                scope["fastapi_inner_astack"] = request_stack187                async with AsyncExitStack() as function_stack:188                    scope["fastapi_function_astack"] = function_stack189                    await func(session)190191        # Same as in Starlette192        await wrap_app_handling_exceptions(app, session)(scope, receive, send)193194    return app195196197_T = TypeVar("_T")198199200# Vendored from starlette.routing to avoid importing private symbols201class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]):202    """203    Wraps a synchronous context manager to make it async.204205    This is vendored from Starlette to avoid importing private symbols.206    """207208    def __init__(self, cm: AbstractContextManager[_T]) -> None:209        self._cm = cm210211    async def __aenter__(self) -> _T:212        return self._cm.__enter__()213214    async def __aexit__(215        self,216        exc_type: type[BaseException] | None,217        exc_value: BaseException | None,218        traceback: types.TracebackType | None,219    ) -> bool | None:220        return self._cm.__exit__(exc_type, exc_value, traceback)221222223# Vendored from starlette.routing to avoid importing private symbols224def _wrap_gen_lifespan_context(225    lifespan_context: Callable[[Any], Generator[Any, Any, Any]],226) -> Callable[[Any], AbstractAsyncContextManager[Any]]:227    """228    Wrap a generator-based lifespan context into an async context manager.229230    This is vendored from Starlette to avoid importing private symbols.231    """232    cmgr = contextlib.contextmanager(lifespan_context)233234    @functools.wraps(cmgr)235    def wrapper(app: Any) -> _AsyncLiftContextManager[Any]:236        return _AsyncLiftContextManager(cmgr(app))237238    return wrapper239240241def _merge_lifespan_context(242    original_context: Lifespan[Any], nested_context: Lifespan[Any]243) -> Lifespan[Any]:244    @asynccontextmanager245    async def merged_lifespan(246        app: AppType,247    ) -> AsyncIterator[Mapping[str, Any] | None]:248        async with original_context(app) as maybe_original_state:249            async with nested_context(app) as maybe_nested_state:250                if maybe_nested_state is None and maybe_original_state is None:251                    yield None  # old ASGI compatibility252                else:253                    yield {**(maybe_nested_state or {}), **(maybe_original_state or {})}254255    return merged_lifespan  # type: ignore[return-value]  # ty: ignore[invalid-return-type]256257258class _DefaultLifespan:259    """260    Default lifespan context manager that runs on_startup and on_shutdown handlers.261262    This is a copy of the Starlette _DefaultLifespan class that was removed263    in Starlette. FastAPI keeps it to maintain backward compatibility with264    on_startup and on_shutdown event handlers.265266    Ref: https://github.com/Kludex/starlette/pull/3117267    """268269    def __init__(self, router: "APIRouter") -> None:270        self._router = router271272    async def __aenter__(self) -> None:273        await self._router._startup()274275    async def __aexit__(self, *exc_info: object) -> None:276        await self._router._shutdown()277278    def __call__(self: _T, app: object) -> _T:279        return self280281282# Cache for endpoint context to avoid re-extracting on every request283_endpoint_context_cache: dict[int, EndpointContext] = {}284285286def _extract_endpoint_context(func: Any) -> EndpointContext:287    """Extract endpoint context with caching to avoid repeated file I/O."""288    func_id = id(func)289290    if func_id in _endpoint_context_cache:291        return _endpoint_context_cache[func_id]292293    try:294        ctx: EndpointContext = {}295296        if (source_file := inspect.getsourcefile(func)) is not None:297            ctx["file"] = source_file298        if (line_number := inspect.getsourcelines(func)[1]) is not None:299            ctx["line"] = line_number300        if (func_name := getattr(func, "__name__", None)) is not None:301            ctx["function"] = func_name302    except Exception:303        ctx = EndpointContext()304305    _endpoint_context_cache[func_id] = ctx306    return ctx307308309def _normalize_non_finite_floats(data: Any) -> Any:310    """Replace non-finite values in JSON-compatible data, preserving dict keys."""311    if isinstance(data, float):312        return data if math.isfinite(data) else None313    if isinstance(data, dict):314        return {key: _normalize_non_finite_floats(value) for key, value in data.items()}315    if isinstance(data, list):316        return [_normalize_non_finite_floats(value) for value in data]317    return data318319320async def serialize_response(321    *,322    field: ModelField | None = None,323    response_content: Any,324    include: IncEx | None = None,325    exclude: IncEx | None = None,326    by_alias: bool = True,327    exclude_unset: bool = False,328    exclude_defaults: bool = False,329    exclude_none: bool = False,330    is_coroutine: bool = True,331    endpoint_ctx: EndpointContext | None = None,332    dump_json: bool = False,333) -> Any:334    if field:335        if is_coroutine:336            value, errors = field.validate(response_content, {}, loc=("response",))337        else:338            value, errors = await run_in_threadpool(339                field.validate, response_content, {}, loc=("response",)340            )341        if errors:342            ctx = endpoint_ctx or EndpointContext()343            raise ResponseValidationError(344                errors=errors,345                body=response_content,346                endpoint_ctx=ctx,347            )348        serializer = field.serialize_json if dump_json else field.serialize349        return serializer(350            value,351            include=include,352            exclude=exclude,353            by_alias=by_alias,354            exclude_unset=exclude_unset,355            exclude_defaults=exclude_defaults,356            exclude_none=exclude_none,357        )358359    else:360        return jsonable_encoder(response_content)361362363async def run_endpoint_function(364    *, dependant: Dependant, values: dict[str, Any], is_coroutine: bool365) -> Any:366    # Only called by get_request_handler. Has been split into its own function to367    # facilitate profiling endpoints, since inner functions are harder to profile.368    assert dependant.call is not None, "dependant.call must be a function"369370    if is_coroutine:371        with _operation(name="endpoint", function=dependant.call):372            return await dependant.call(**values)373    else:374        return await run_in_threadpool(375            _run_sync_endpoint, function=dependant.call, arguments=values376        )377378379def _build_response_args(380    *, status_code: int | None, solved_result: Any381) -> dict[str, Any]:382    response_args: dict[str, Any] = {383        "background": solved_result.background_tasks,384    }385    # If status_code was set, use it, otherwise use the default from the386    # response class, in the case of redirect it's 307387    current_status_code = (388        status_code if status_code else solved_result.response.status_code389    )390    if current_status_code is not None:391        response_args["status_code"] = current_status_code392    if solved_result.response.status_code:393        response_args["status_code"] = solved_result.response.status_code394    return response_args395396397def get_request_handler(398    dependant: Dependant,399    body_field: ModelField | None = None,400    status_code: int | None = None,401    response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),402    response_field: ModelField | None = None,403    response_model_include: IncEx | None = None,404    response_model_exclude: IncEx | None = None,405    response_model_by_alias: bool = True,406    response_model_exclude_unset: bool = False,407    response_model_exclude_defaults: bool = False,408    response_model_exclude_none: bool = False,409    dependency_overrides_provider: Any | None = None,410    embed_body_fields: bool = False,411    strict_content_type: bool | DefaultPlaceholder = Default(True),412    stream_item_field: ModelField | None = None,413    is_json_stream: bool = False,414) -> Callable[[Request], Coroutine[Any, Any, Response]]:415    assert dependant.call is not None, "dependant.call must be a function"416    is_coroutine = _is_coroutine_callable(dependant.call)417    is_body_form = body_field and isinstance(body_field.field_info, params.Form)418    if isinstance(response_class, DefaultPlaceholder):419        actual_response_class: type[Response] = response_class.value420    else:421        actual_response_class = response_class422    is_sse_stream = lenient_issubclass(actual_response_class, EventSourceResponse)423    if isinstance(strict_content_type, DefaultPlaceholder):424        actual_strict_content_type: bool = strict_content_type.value425    else:426        actual_strict_content_type = strict_content_type427428    async def app(request: Request) -> Response:429        telemetry_data = get_telemetry_data()430        if telemetry_data is not None:431            telemetry_data.request = request432        response: Response | None = None433        file_stack = request.scope.get("fastapi_middleware_astack")434        assert isinstance(file_stack, AsyncExitStack), (435            "fastapi_middleware_astack not found in request scope"436        )437438        # Extract endpoint context for error messages439        endpoint_ctx = (440            _extract_endpoint_context(dependant.call)441            if dependant.call442            else EndpointContext()443        )444445        if dependant.path:446            # For mounted sub-apps, include the mount path prefix447            mount_path = request.scope.get("root_path", "").rstrip("/")448            endpoint_ctx["path"] = f"{request.method} {mount_path}{dependant.path}"449450        # Read body and auto-close files451        try:452            body: Any = None453            if body_field:454                if is_body_form:455                    body = await request.form()456                    file_stack.push_async_callback(body.close)457                else:458                    body_bytes = await request.body()459                    if body_bytes:460                        json_body: Any = Undefined461                        content_type_value = request.headers.get("content-type")462                        if not content_type_value:463                            if not actual_strict_content_type:464                                json_body = await request.json()465                        else:466                            message = email.message.Message()467                            message["content-type"] = content_type_value468                            if message.get_content_maintype() == "application":469                                subtype = message.get_content_subtype()470                                if subtype == "json" or subtype.endswith("+json"):471                                    json_body = await request.json()472                        if json_body != Undefined:473                            body = json_body474                        else:475                            body = body_bytes476        except json.JSONDecodeError as e:477            validation_error = RequestValidationError(478                [479                    {480                        "type": "json_invalid",481                        "loc": ("body", e.pos),482                        "msg": "JSON decode error",483                        "input": {},484                        "ctx": {"error": e.msg},485                    }486                ],487                body=e.doc,488                endpoint_ctx=endpoint_ctx,489            )490            _validation_failed(validation_error)491            raise validation_error from e492        except HTTPException:493            # If a middleware raises an HTTPException, it should be raised again494            raise495        except Exception as e:496            http_error = HTTPException(497                status_code=400, detail="There was an error parsing the body"498            )499            raise http_error from e500501        if telemetry_data is not None:502            telemetry_data.body = body503504        # Solve dependencies and run path operation function, auto-closing dependencies505        errors: list[Any] = []506        async_exit_stack = request.scope.get("fastapi_inner_astack")507        assert isinstance(async_exit_stack, AsyncExitStack), (508            "fastapi_inner_astack not found in request scope"509        )510        with _operation(name="dependencies", function=dependant.call):511            solved_result = await solve_dependencies(512                request=request,513                dependant=dependant,514                body=cast(dict[str, Any] | FormData | bytes | None, body),515                dependency_overrides_provider=dependency_overrides_provider,516                async_exit_stack=async_exit_stack,517                embed_body_fields=embed_body_fields,518            )519            if telemetry_data is not None:520                telemetry_data.values = solved_result.values521                telemetry_data.errors = solved_result.errors522        errors = solved_result.errors523        assert dependant.call  # For types524        if not errors:525            # Shared serializer for stream items (JSONL and SSE).526            # Validates against stream_item_field when set, then527            # serializes to JSON bytes.528            def _serialize_data(data: Any) -> bytes:529                if stream_item_field:530                    value, errors_ = stream_item_field.validate(531                        data, {}, loc=("response",)532                    )533                    if errors_:534                        ctx = endpoint_ctx or EndpointContext()535                        raise ResponseValidationError(536                            errors=errors_,537                            body=data,538                            endpoint_ctx=ctx,539                        )540                    return stream_item_field.serialize_json(541                        value,542                        include=response_model_include,543                        exclude=response_model_exclude,544                        by_alias=response_model_by_alias,545                        exclude_unset=response_model_exclude_unset,546                        exclude_defaults=response_model_exclude_defaults,547                        exclude_none=response_model_exclude_none,548                    )549                else:550                    data = jsonable_encoder(data)551                    return json.dumps(_normalize_non_finite_floats(data)).encode(552                        "utf-8"553                    )554555            if is_sse_stream:556                # Generator endpoint: stream as Server-Sent Events557                gen = dependant.call(**solved_result.values)558559                def _serialize_sse_item(item: Any) -> bytes:560                    if isinstance(item, ServerSentEvent):561                        # User controls the event structure.562                        # Serialize the data payload if present.563                        # For ServerSentEvent items we skip stream_item_field564                        # validation (the user may mix types intentionally).565                        if item.raw_data is not None:566                            data_str: str | None = item.raw_data567                        elif item.data is not None:568                            if hasattr(item.data, "model_dump_json"):569                                data_str = item.data.model_dump_json()570                            else:571                                data_str = json.dumps(572                                    _normalize_non_finite_floats(573                                        jsonable_encoder(item.data)574                                    )575                                )576                        else:577                            data_str = None578                        return format_sse_event(579                            data_str=data_str,580                            event=item.event,581                            id=item.id,582                            retry=item.retry,583                            comment=item.comment,584                        )585                    else:586                        # Plain object: validate + serialize via587                        # stream_item_field (if set) and wrap in data field588                        return format_sse_event(589                            data_str=_serialize_data(item).decode("utf-8")590                        )591592                if _is_async_gen_callable(dependant.call):593                    sse_aiter: AsyncIterator[Any] = gen.__aiter__()594                else:595                    sse_aiter = iterate_in_threadpool(gen)596597                @asynccontextmanager598                async def _sse_producer_cm() -> AsyncIterator[599                    ObjectReceiveStream[bytes]600                ]:601                    # Use a memory stream to decouple generator iteration602                    # from the keepalive timer. A producer task pulls items603                    # from the generator independently, so604                    # `anyio.fail_after` never wraps the generator's605                    # `__anext__` directly - avoiding CancelledError that606                    # would finalize the generator and also working for sync607                    # generators running in a thread pool.608                    #609                    # This context manager is entered on the request-scoped610                    # AsyncExitStack so its __aexit__ (which cancels the611                    # task group) is called by the exit stack after the612                    # streaming response completes — not by async generator613                    # finalization via GeneratorExit.614                    # Ref: https://peps.python.org/pep-0789/615                    send_stream, receive_stream = anyio.create_memory_object_stream[616                        bytes617                    ](max_buffer_size=1)618619                    async def _producer() -> None:620                        async with send_stream:621                            async for raw_item in sse_aiter:622                                await send_stream.send(_serialize_sse_item(raw_item))623624                    send_keepalive, receive_keepalive = (625                        anyio.create_memory_object_stream[bytes](max_buffer_size=1)626                    )627628                    async def _keepalive_inserter() -> None:629                        """Read from the producer and forward to the output,630                        inserting keepalive comments on timeout."""631                        async with send_keepalive, receive_stream:632                            try:633                                while True:634                                    try:635                                        with anyio.fail_after(_PING_INTERVAL):636                                            data = await receive_stream.receive()637                                        await send_keepalive.send(data)638                                    except TimeoutError:639                                        await send_keepalive.send(KEEPALIVE_COMMENT)640                            except anyio.EndOfStream:641                                pass642643                    async with anyio.create_task_group() as tg:644                        tg.start_soon(_producer)645                        tg.start_soon(_keepalive_inserter)646                        yield receive_keepalive647                        tg.cancel_scope.cancel()648649                # Enter the SSE context manager on the request-scoped650                # exit stack. The stack outlives the streaming response,651                # so __aexit__ runs via proper structured teardown, not652                # via GeneratorExit thrown into an async generator.653                sse_receive_stream = await async_exit_stack.enter_async_context(654                    _sse_producer_cm()655                )656                # Ensure the receive stream is closed when the exit stack657                # unwinds, preventing ResourceWarning from __del__.658                async_exit_stack.push_async_callback(sse_receive_stream.aclose)659660                async def _sse_with_checkpoints(661                    stream: ObjectReceiveStream[bytes],662                ) -> AsyncIterator[bytes]:663                    async for data in stream:664                        yield data665                        # Guarantee a checkpoint so cancellation can be666                        # delivered even when the producer is faster than667                        # the consumer and receive() never suspends.668                        await anyio.sleep(0)669670                sse_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (671                    _sse_with_checkpoints(sse_receive_stream)672                )673674                response_args = _build_response_args(675                    status_code=status_code, solved_result=solved_result676                )677                response = StreamingResponse(678                    sse_stream_content,679                    media_type="text/event-stream",680                    **response_args,681                )682                response.headers["Cache-Control"] = "no-cache"683                # For Nginx proxies to not buffer server sent events684                response.headers["X-Accel-Buffering"] = "no"685                response.headers.raw.extend(solved_result.response.headers.raw)686            elif is_json_stream:687                # Generator endpoint: stream as JSONL688                gen = dependant.call(**solved_result.values)689690                def _serialize_item(item: Any) -> bytes:691                    return _serialize_data(item) + b"\n"692693                if _is_async_gen_callable(dependant.call):694695                    async def _async_stream_jsonl() -> AsyncIterator[bytes]:696                        async for item in gen:697                            yield _serialize_item(item)698                            # To allow for cancellation to trigger699                            # Ref: https://github.com/fastapi/fastapi/issues/14680700                            await anyio.sleep(0)701702                    jsonl_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (703                        _async_stream_jsonl()704                    )705                else:706707                    def _sync_stream_jsonl() -> Iterator[bytes]:708                        for item in gen:  # ty: ignore[not-iterable]709                            yield _serialize_item(item)710711                    jsonl_stream_content = _sync_stream_jsonl()712713                response_args = _build_response_args(714                    status_code=status_code, solved_result=solved_result715                )716                response = StreamingResponse(717                    jsonl_stream_content,718                    media_type="application/jsonl",719                    **response_args,720                )721                response.headers.raw.extend(solved_result.response.headers.raw)722            elif _is_async_gen_callable(dependant.call) or _is_gen_callable(723                dependant.call724            ):725                # Raw streaming with explicit response_class (e.g. StreamingResponse)726                gen = dependant.call(**solved_result.values)727                if _is_async_gen_callable(dependant.call):728729                    async def _async_stream_raw(730                        async_gen: AsyncIterator[Any],731                    ) -> AsyncIterator[Any]:732                        async for chunk in async_gen:733                            yield chunk734                            # To allow for cancellation to trigger735                            # Ref: https://github.com/fastapi/fastapi/issues/14680736                            await anyio.sleep(0)737738                    gen = _async_stream_raw(gen)739                response_args = _build_response_args(740                    status_code=status_code, solved_result=solved_result741                )742                response = actual_response_class(content=gen, **response_args)743                response.headers.raw.extend(solved_result.response.headers.raw)744            else:745                raw_response = await run_endpoint_function(746                    dependant=dependant,747                    values=solved_result.values,748                    is_coroutine=is_coroutine,749                )750                if isinstance(raw_response, Response):751                    if raw_response.background is None:752                        raw_response.background = solved_result.background_tasks753                    response = raw_response754                else:755                    response_args = _build_response_args(756                        status_code=status_code, solved_result=solved_result757                    )758                    # Use the fast path (dump_json) when no custom response759                    # class was set and a response field with a TypeAdapter760                    # exists. Serializes directly to JSON bytes via Pydantic's761                    # Rust core, skipping the intermediate Python dict +762                    # json.dumps() step.763                    use_dump_json = response_field is not None and isinstance(764                        response_class, DefaultPlaceholder765                    )766                    with _operation(name="serialization", function=dependant.call):767                        content = await serialize_response(768                            field=response_field,769                            response_content=raw_response,770                            include=response_model_include,771                            exclude=response_model_exclude,772                            by_alias=response_model_by_alias,773                            exclude_unset=response_model_exclude_unset,774                            exclude_defaults=response_model_exclude_defaults,775                            exclude_none=response_model_exclude_none,776                            is_coroutine=is_coroutine,777                            endpoint_ctx=endpoint_ctx,778                            dump_json=use_dump_json,779                        )780                    if use_dump_json:781                        response = Response(782                            content=content,783                            media_type="application/json",784                            **response_args,785                        )786                    else:787                        response = actual_response_class(content, **response_args)788                    if not is_body_allowed_for_status_code(response.status_code):789                        response.body = b""790                    response.headers.raw.extend(solved_result.response.headers.raw)791        if errors:792            validation_error = RequestValidationError(793                errors, body=body, endpoint_ctx=endpoint_ctx794            )795            _validation_failed(validation_error)796            raise validation_error797798        # Return response799        assert response800        return response801802    return app803804805def get_websocket_app(806    dependant: Dependant,807    dependency_overrides_provider: Any | None = None,808    embed_body_fields: bool = False,809) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:810    async def app(websocket: WebSocket) -> None:811        telemetry_data = get_telemetry_data()812        if telemetry_data is not None:813            telemetry_data.websocket = websocket814        endpoint_ctx = (815            _extract_endpoint_context(dependant.call)816            if dependant.call817            else EndpointContext()818        )819        if dependant.path:820            # For mounted sub-apps, include the mount path prefix821            mount_path = websocket.scope.get("root_path", "").rstrip("/")822            endpoint_ctx["path"] = f"WS {mount_path}{dependant.path}"823        async_exit_stack = websocket.scope.get("fastapi_inner_astack")824        assert isinstance(async_exit_stack, AsyncExitStack), (825            "fastapi_inner_astack not found in request scope"826        )827        with _operation(name="dependencies", function=dependant.call):828            solved_result = await solve_dependencies(829                request=websocket,830                dependant=dependant,831                dependency_overrides_provider=dependency_overrides_provider,832                async_exit_stack=async_exit_stack,833                embed_body_fields=embed_body_fields,834            )835            if telemetry_data is not None:836                telemetry_data.values = solved_result.values837                telemetry_data.errors = solved_result.errors838        if solved_result.errors:839            validation_error = WebSocketRequestValidationError(840                solved_result.errors,841                endpoint_ctx=endpoint_ctx,842            )843            _validation_failed(validation_error)844            raise validation_error845        assert dependant.call is not None, "dependant.call must be a function"846        with _operation(name="endpoint", function=dependant.call):847            await dependant.call(**solved_result.values)848849    return app850851852class APIWebSocketRoute(routing.WebSocketRoute):853    def __init__(854        self,855        path: str,856        endpoint: Callable[..., Any],857        *,858        name: str | None = None,859        dependencies: Sequence[params.Depends] | None = None,860        dependency_overrides_provider: Any | None = None,861    ) -> None:862        self.path = path863        self.endpoint = endpoint864        self.name = get_name(endpoint) if name is None else name865        self.dependencies = list(dependencies or [])866        self.path_regex, self.path_format, self.param_convertors = compile_path(path)867        (868            self.dependant,869            _,870            self._embed_body_fields,871        ) = _build_dependant_with_parameterless_dependencies(872            path=self.path_format,873            call=self.endpoint,874            dependencies=self.dependencies,875        )876        self.app = websocket_session(877            get_websocket_app(878                dependant=self.dependant,879                dependency_overrides_provider=dependency_overrides_provider,880                embed_body_fields=self._embed_body_fields,881            )882        )883884    def matches(self, scope: Scope) -> tuple[Match, Scope]:885        match, child_scope = super().matches(scope)886        if match != Match.NONE:887            child_scope["route"] = self888        return match, child_scope889890891_FASTAPI_SCOPE_KEY = "fastapi"892_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY = "effective_route_context"893_FASTAPI_FRONTEND_PATH_KEY = "frontend_path"894_FASTAPI_FRONTEND_SPECIFICITY_KEY = "frontend_specificity"895_FASTAPI_INCLUDED_ROUTER_KEY = "included_router"896_effective_route_context_var: ContextVar[Any | None] = ContextVar(897    "fastapi_effective_route_context", default=None898)899_SCOPE_MISSING = object()900901902def _frontend_dependency_endpoint() -> None:903    pass  # pragma: no cover904905906def _build_dependant_with_parameterless_dependencies(907    *,908    path: str,909    call: Callable[..., Any],910    dependencies: Sequence[params.Depends],911) -> tuple[Dependant, list[ModelField], bool]:912    dependant = get_dependant(path=path, call=call, scope="function")913    for depends in dependencies[::-1]:914        dependant.dependencies.insert(915            0,916            get_parameterless_sub_dependant(depends=depends, path=path),917        )918    body_params = _get_flat_body_params(dependant)919    embed_body_fields = _should_embed_body_fields(body_params)920    return dependant, body_params, embed_body_fields921922923class _RouteWithPath(Protocol):924    path: str925926927def _get_fastapi_scope(scope: Scope) -> dict[str, Any]:928    fastapi_scope = scope.setdefault(_FASTAPI_SCOPE_KEY, {})929    assert isinstance(fastapi_scope, dict)930    return fastapi_scope931932933def _update_scope(scope: Scope, child_scope: Scope) -> None:934    fastapi_child_scope = child_scope.get(_FASTAPI_SCOPE_KEY)935    for key, value in child_scope.items():936        if key != _FASTAPI_SCOPE_KEY:937            scope[key] = value938    if isinstance(fastapi_child_scope, dict):939        _get_fastapi_scope(scope).update(fastapi_child_scope)940941942def _get_scope_effective_route_context(scope: Scope) -> Any | None:943    return scope.get(_FASTAPI_SCOPE_KEY, {}).get(_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY)944945946def _get_scope_included_router(scope: Scope) -> Any | None:947    return scope.get(_FASTAPI_SCOPE_KEY, {}).get(_FASTAPI_INCLUDED_ROUTER_KEY)948949950def _frontend_scope_specificity(scope: Scope) -> int | None:951    specificity = scope.get(_FASTAPI_SCOPE_KEY, {}).get(952        _FASTAPI_FRONTEND_SPECIFICITY_KEY953    )954    if isinstance(specificity, int):955        return specificity956    return None957958959def _restore_fastapi_scope_key(scope: Scope, key: str, previous: Any) -> None:960    fastapi_scope = scope.get(_FASTAPI_SCOPE_KEY)961    if not isinstance(fastapi_scope, dict):962        return963    if previous is _SCOPE_MISSING:964        fastapi_scope.pop(key, None)965    else:966        fastapi_scope[key] = previous967968969class _APIRouteLike(Protocol):970    path: str971    endpoint: Callable[..., Any]972    stream_item_type: Any | None973    response_model: Any974    summary: str | None975    response_description: str976    deprecated: bool | None977    operation_id: str | None978    response_model_include: IncEx | None979    response_model_exclude: IncEx | None980    response_model_by_alias: bool981    response_model_exclude_unset: bool982    response_model_exclude_defaults: bool983    response_model_exclude_none: bool984    include_in_schema: bool985    response_class: type[Response] | DefaultPlaceholder986    dependency_overrides_provider: Any | None987    callbacks: list[BaseRoute] | None988    openapi_extra: dict[str, Any] | None989    generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder990    strict_content_type: bool | DefaultPlaceholder991    tags: list[str | Enum]992    responses: dict[int | str, dict[str, Any]]993    name: str994    path_regex: Any995    path_format: str996    param_convertors: dict[str, Any]997    methods: set[str]998    unique_id: str999    status_code: int | None1000    response_field: ModelField | None1001    stream_item_field: ModelField | None1002    dependencies: list[params.Depends]1003    description: str1004    response_fields: dict[int | str, ModelField]1005    dependant: Dependant1006    _embed_body_fields: bool1007    body_field: ModelField | None1008    is_sse_stream: bool1009    is_json_stream: bool101010111012def _populate_api_route_state(1013    route: _APIRouteLike,1014    path: str,1015    endpoint: Callable[..., Any],1016    *,1017    response_model: Any = Default(None),1018    status_code: int | None = None,1019    tags: list[str | Enum] | None = None,1020    dependencies: Sequence[params.Depends] | None = None,1021    summary: str | None = None,1022    description: str | None = None,1023    response_description: str = "Successful Response",1024    responses: dict[int | str, dict[str, Any]] | None = None,1025    deprecated: bool | None = None,1026    name: str | None = None,1027    methods: set[str] | list[str] | None = None,1028    operation_id: str | None = None,1029    response_model_include: IncEx | None = None,1030    response_model_exclude: IncEx | None = None,1031    response_model_by_alias: bool = True,1032    response_model_exclude_unset: bool = False,1033    response_model_exclude_defaults: bool = False,1034    response_model_exclude_none: bool = False,1035    include_in_schema: bool = True,1036    response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),1037    dependency_overrides_provider: Any | None = None,1038    callbacks: list[BaseRoute] | None = None,1039    openapi_extra: dict[str, Any] | None = None,1040    generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default(1041        generate_unique_id1042    ),1043    strict_content_type: bool | DefaultPlaceholder = Default(True),1044    stream_item_type: Any | None = None,1045) -> None:1046    route.path = path1047    route.endpoint = endpoint1048    route.stream_item_type = stream_item_type1049    route.summary = summary1050    route.response_description = response_description1051    route.deprecated = deprecated1052    route.operation_id = operation_id1053    route.response_model_include = response_model_include1054    route.response_model_exclude = response_model_exclude1055    route.response_model_by_alias = response_model_by_alias1056    route.response_model_exclude_unset = response_model_exclude_unset1057    route.response_model_exclude_defaults = response_model_exclude_defaults1058    route.response_model_exclude_none = response_model_exclude_none1059    route.include_in_schema = include_in_schema1060    route.response_class = response_class1061    route.dependency_overrides_provider = dependency_overrides_provider1062    route.callbacks = callbacks1063    route.openapi_extra = openapi_extra1064    route.generate_unique_id_function = generate_unique_id_function1065    route.strict_content_type = strict_content_type1066    route.tags = tags or []1067    route.responses = responses or {}1068    route.name = get_name(endpoint) if name is None else name1069    route.path_regex, route.path_format, route.param_convertors = compile_path(path)1070    if methods is None:1071        methods = ["GET"]1072    route.methods = {method.upper() for method in methods}1073    if isinstance(generate_unique_id_function, DefaultPlaceholder):1074        current_generate_unique_id: Callable[[Any], str] = (1075            generate_unique_id_function.value1076        )1077    else:1078        current_generate_unique_id = generate_unique_id_function1079    route.unique_id = route.operation_id or current_generate_unique_id(route)1080    # normalize enums e.g. http.HTTPStatus1081    if isinstance(status_code, IntEnum):1082        status_code = int(status_code)1083    route.status_code = status_code1084    route.dependencies = list(dependencies or [])1085    route.description = description or inspect.cleandoc(route.endpoint.__doc__ or "")1086    # if a "form feed" character (page break) is found in the description text,1087    # truncate description text to the content preceding the first "form feed"1088    route.description = route.description.split("\f")[0].strip()1089    response_fields = {}1090    for additional_status_code, response in route.responses.items():1091        assert isinstance(response, dict), "An additional response must be a dict"1092        model = response.get("model")1093        if model:1094            assert is_body_allowed_for_status_code(additional_status_code), (1095                f"Status code {additional_status_code} must not have a response body"1096            )1097            response_name = f"Response_{additional_status_code}_{route.unique_id}"1098            response_field = create_model_field(1099                name=response_name, type_=model, mode="serialization"1100            )1101            response_fields[additional_status_code] = response_field1102    if response_fields:1103        route.response_fields = response_fields1104    else:1105        route.response_fields = {}11061107    assert callable(endpoint), "An endpoint must be a callable"1108    (1109        route.dependant,1110        body_params,1111        route._embed_body_fields,1112    ) = _build_dependant_with_parameterless_dependencies(1113        path=route.path_format,1114        call=route.endpoint,1115        dependencies=route.dependencies,1116    )1117    route.body_field = _get_body_field(1118        body_params=body_params,1119        name=route.unique_id,1120        embed_body_fields=route._embed_body_fields,1121    )1122    # Detect generator endpoints that should stream as JSONL or SSE1123    is_generator = _is_async_gen_callable(route.dependant.call) or _is_gen_callable(1124        route.dependant.call1125    )1126    route.is_sse_stream = is_generator and lenient_issubclass(1127        response_class, EventSourceResponse1128    )1129    route.is_json_stream = is_generator and isinstance(1130        response_class, DefaultPlaceholder1131    )1132    if isinstance(response_model, DefaultPlaceholder):1133        return_annotation = get_typed_return_annotation(endpoint)1134        if lenient_issubclass(return_annotation, Response):1135            response_model = None1136        else:1137            stream_item = get_stream_item_type(return_annotation)1138            if stream_item is not None and is_generator:1139                # Extract item type for JSONL or SSE streaming for1140                # generator endpoints when response_class is1141                # DefaultPlaceholder (JSONL) or EventSourceResponse (SSE).1142                # ServerSentEvent is excluded: it's a transport1143                # wrapper, not a data model, so it shouldn't feed1144                # into validation or OpenAPI schema generation.1145                if (1146                    isinstance(response_class, DefaultPlaceholder)1147                    or lenient_issubclass(response_class, EventSourceResponse)1148                ) and not lenient_issubclass(stream_item, ServerSentEvent):1149                    route.stream_item_type = stream_item1150                response_model = None1151            else:1152                response_model = return_annotation1153    route.response_model = response_model1154    if route.response_model:1155        assert is_body_allowed_for_status_code(status_code), (1156            f"Status code {status_code} must not have a response body"1157        )1158        response_name = "Response_" + route.unique_id1159        route.response_field = create_model_field(1160            name=response_name,1161            type_=route.response_model,1162            mode="serialization",1163        )1164    else:1165        route.response_field = None1166    if route.stream_item_type:1167        stream_item_name = "StreamItem_" + route.unique_id1168        route.stream_item_field = create_model_field(1169            name=stream_item_name,1170            type_=route.stream_item_type,1171            mode="serialization",1172        )1173    else:1174        route.stream_item_field = None117511761177class APIRoute(routing.Route):1178    stream_item_type: Any | None1179    response_model: Any1180    summary: str | None1181    response_description: str1182    deprecated: bool | None1183    operation_id: str | None1184    response_model_include: IncEx | None1185    response_model_exclude: IncEx | None1186    response_model_by_alias: bool1187    response_model_exclude_unset: bool1188    response_model_exclude_defaults: bool1189    response_model_exclude_none: bool1190    include_in_schema: bool1191    response_class: type[Response] | DefaultPlaceholder1192    dependency_overrides_provider: Any | None1193    callbacks: list[BaseRoute] | None1194    openapi_extra: dict[str, Any] | None1195    generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder1196    strict_content_type: bool | DefaultPlaceholder1197    tags: list[str | Enum]1198    responses: dict[int | str, dict[str, Any]]1199    unique_id: str1200    status_code: int | None1201    response_field: ModelField | None1202    stream_item_field: ModelField | None1203    dependencies: list[params.Depends]1204    description: str1205    response_fields: dict[int | str, ModelField]1206    dependant: Dependant1207    _embed_body_fields: bool1208    body_field: ModelField | None1209    is_sse_stream: bool1210    is_json_stream: bool12111212    def __init__(1213        self,1214        path: str,1215        endpoint: Callable[..., Any],1216        *,1217        response_model: Any = Default(None),1218        status_code: int | None = None,1219        tags: list[str | Enum] | None = None,1220        dependencies: Sequence[params.Depends] | None = None,1221        summary: str | None = None,1222        description: str | None = None,1223        response_description: str = "Successful Response",1224        responses: dict[int | str, dict[str, Any]] | None = None,1225        deprecated: bool | None = None,1226        name: str | None = None,1227        methods: set[str] | list[str] | None = None,1228        operation_id: str | None = None,1229        response_model_include: IncEx | None = None,1230        response_model_exclude: IncEx | None = None,1231        response_model_by_alias: bool = True,1232        response_model_exclude_unset: bool = False,1233        response_model_exclude_defaults: bool = False,1234        response_model_exclude_none: bool = False,1235        include_in_schema: bool = True,1236        response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),1237        dependency_overrides_provider: Any | None = None,1238        callbacks: list[BaseRoute] | None = None,1239        openapi_extra: dict[str, Any] | None = None,1240        generate_unique_id_function: Callable[["APIRoute"], str]1241        | DefaultPlaceholder = Default(generate_unique_id),1242        strict_content_type: bool | DefaultPlaceholder = Default(True),1243    ) -> None:1244        _populate_api_route_state(1245            cast(_APIRouteLike, self),1246            path,1247            endpoint,1248            response_model=response_model,1249            status_code=status_code,1250            tags=tags,1251            dependencies=dependencies,1252            summary=summary,1253            description=description,1254            response_description=response_description,1255            responses=responses,1256            deprecated=deprecated,1257            name=name,1258            methods=methods,1259            operation_id=operation_id,1260            response_model_include=response_model_include,1261            response_model_exclude=response_model_exclude,1262            response_model_by_alias=response_model_by_alias,1263            response_model_exclude_unset=response_model_exclude_unset,1264            response_model_exclude_defaults=response_model_exclude_defaults,1265            response_model_exclude_none=response_model_exclude_none,1266            include_in_schema=include_in_schema,1267            response_class=response_class,1268            dependency_overrides_provider=dependency_overrides_provider,1269            callbacks=callbacks,1270            openapi_extra=openapi_extra,1271            generate_unique_id_function=generate_unique_id_function,1272            strict_content_type=strict_content_type,1273        )1274        self.app = request_response(self.get_route_handler())12751276    def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:1277        route = cast(_APIRouteLike, self)1278        # TODO: Replace or deprecate this no-scope hook so included-route1279        # effective context can be passed explicitly instead of via ContextVar.1280        effective_context = _effective_route_context_var.get()1281        if effective_context is not None and effective_context.original_route is self:1282            route = cast(_APIRouteLike, effective_context)1283        return get_request_handler(1284            dependant=route.dependant,1285            body_field=route.body_field,1286            status_code=route.status_code,1287            response_class=route.response_class,1288            response_field=route.response_field,1289            response_model_include=route.response_model_include,1290            response_model_exclude=route.response_model_exclude,1291            response_model_by_alias=route.response_model_by_alias,1292            response_model_exclude_unset=route.response_model_exclude_unset,1293            response_model_exclude_defaults=route.response_model_exclude_defaults,1294            response_model_exclude_none=route.response_model_exclude_none,1295            dependency_overrides_provider=route.dependency_overrides_provider,1296            embed_body_fields=route._embed_body_fields,1297            strict_content_type=route.strict_content_type,1298            stream_item_field=route.stream_item_field,1299            is_json_stream=route.is_json_stream,1300        )13011302    def matches(self, scope: Scope) -> tuple[Match, Scope]:1303        effective_context = _get_scope_effective_route_context(scope)1304        if effective_context is not None and effective_context.original_route is self:1305            match, child_scope = effective_context.matches(scope)1306        else:1307            match, child_scope = super().matches(scope)1308        if match != Match.NONE:1309            child_scope["route"] = self1310        return match, child_scope13111312    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:1313        effective_context = _get_scope_effective_route_context(scope)1314        if effective_context is not None and effective_context.original_route is self:1315            methods = effective_context.methods1316            if methods and scope["method"] not in methods:1317                headers = {"Allow": ", ".join(methods)}1318                if "app" in scope:1319                    raise HTTPException(status_code=405, headers=headers)1320                response = PlainTextResponse(1321                    "Method Not Allowed", status_code=405, headers=headers1322                )1323                await response(scope, receive, send)1324                return1325            assert effective_context.app is not None1326            await effective_context.app(scope, receive, send)1327            return1328        await super().handle(scope, receive, send)132913301331@dataclass1332class _RouterIncludeContext:1333    included_router: "APIRouter"1334    prefix: str = ""1335    tags: list[str | Enum] = field(default_factory=list)1336    dependencies: list[params.Depends] = field(default_factory=list)1337    default_response_class: type[Response] | DefaultPlaceholder = field(1338        default_factory=lambda: Default(JSONResponse)1339    )1340    responses: dict[int | str, dict[str, Any]] = field(default_factory=dict)1341    callbacks: list[BaseRoute] = field(default_factory=list)1342    deprecated: bool | None = None1343    include_in_schema: bool = True1344    generate_unique_id_function: Callable[[APIRoute], str] | DefaultPlaceholder = field(1345        default_factory=lambda: Default(generate_unique_id)1346    )1347    strict_content_type: bool | DefaultPlaceholder = field(1348        default_factory=lambda: Default(True)1349    )1350    dependency_overrides_provider: Any | None = None13511352    @classmethod1353    def for_include(1354        cls,1355        *,1356        parent_router: "APIRouter",1357        included_router: "APIRouter",1358        prefix: str = "",1359        tags: list[str | Enum] | None = None,1360        dependencies: Sequence[params.Depends] | None = None,1361        default_response_class: type[Response] | DefaultPlaceholder = Default(1362            JSONResponse1363        ),1364        responses: dict[int | str, dict[str, Any]] | None = None,1365        callbacks: list[BaseRoute] | None = None,1366        deprecated: bool | None = None,1367        include_in_schema: bool = True,1368        generate_unique_id_function: Callable[[APIRoute], str]1369        | DefaultPlaceholder = Default(generate_unique_id),1370    ) -> "_RouterIncludeContext":1371        return cls(1372            included_router=included_router,1373            prefix=parent_router.prefix + prefix,1374            tags=[*parent_router.tags, *(tags or [])],1375            dependencies=[*parent_router.dependencies, *(dependencies or [])],1376            default_response_class=get_value_or_default(1377                default_response_class, parent_router.default_response_class1378            ),1379            responses={**parent_router.responses, **(responses or {})},1380            callbacks=[*parent_router.callbacks, *(callbacks or [])],1381            deprecated=deprecated or parent_router.deprecated,1382            include_in_schema=parent_router.include_in_schema and include_in_schema,1383            generate_unique_id_function=get_value_or_default(1384                generate_unique_id_function, parent_router.generate_unique_id_function1385            ),1386            strict_content_type=parent_router.strict_content_type,1387            dependency_overrides_provider=parent_router.dependency_overrides_provider,1388        )13891390    def combine(1391        self, child_context: "_RouterIncludeContext"1392    ) -> "_RouterIncludeContext":1393        return _RouterIncludeContext(1394            included_router=child_context.included_router,1395            prefix=self.prefix + child_context.prefix,1396            tags=[*self.tags, *child_context.tags],1397            dependencies=[*self.dependencies, *child_context.dependencies],1398            default_response_class=get_value_or_default(1399                child_context.default_response_class, self.default_response_class1400            ),1401            responses={**self.responses, **child_context.responses},1402            callbacks=[*self.callbacks, *child_context.callbacks],1403            deprecated=self.deprecated or child_context.deprecated,1404            include_in_schema=self.include_in_schema1405            and child_context.include_in_schema,1406            generate_unique_id_function=get_value_or_default(1407                child_context.generate_unique_id_function,1408                self.generate_unique_id_function,1409            ),1410            strict_content_type=get_value_or_default(1411                child_context.strict_content_type, self.strict_content_type1412            ),1413            dependency_overrides_provider=self.dependency_overrides_provider,1414        )14151416    def path_for(self, route: _RouteWithPath) -> str:1417        return self.prefix + route.path141814191420@dataclass1421class _EffectiveRouteContext:1422    original_route: BaseRoute1423    starlette_route: BaseRoute | None = None1424    app: ASGIApp | None = field(default=None, repr=False, compare=False)1425    frontend_prefix: str = ""1426    path: str = ""1427    endpoint: Callable[..., Any] | None = None1428    stream_item_type: Any | None = None1429    response_model: Any = None1430    summary: str | None = None1431    response_description: str = "Successful Response"1432    deprecated: bool | None = None1433    operation_id: str | None = None1434    response_model_include: IncEx | None = None1435    response_model_exclude: IncEx | None = None1436    response_model_by_alias: bool = True1437    response_model_exclude_unset: bool = False1438    response_model_exclude_defaults: bool = False1439    response_model_exclude_none: bool = False1440    include_in_schema: bool = True1441    response_class: type[Response] | DefaultPlaceholder = field(1442        default_factory=lambda: Default(JSONResponse)1443    )1444    dependency_overrides_provider: Any | None = None1445    callbacks: list[BaseRoute] | None = None1446    openapi_extra: dict[str, Any] | None = None1447    generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = field(1448        default_factory=lambda: Default(generate_unique_id)1449    )1450    strict_content_type: bool | DefaultPlaceholder = field(1451        default_factory=lambda: Default(True)1452    )1453    tags: list[str | Enum] = field(default_factory=list)1454    responses: dict[int | str, dict[str, Any]] = field(default_factory=dict)1455    name: str = ""1456    path_regex: Any = None1457    path_format: str = ""1458    param_convertors: dict[str, Any] = field(default_factory=dict)1459    methods: set[str] = field(default_factory=set)1460    unique_id: str = ""1461    status_code: int | None = None1462    response_field: ModelField | None = None1463    stream_item_field: ModelField | None = None1464    dependencies: list[params.Depends] = field(default_factory=list)1465    description: str = ""1466    response_fields: dict[int | str, ModelField] = field(default_factory=dict)1467    dependant: Dependant | None = None1468    _embed_body_fields: bool = False1469    body_field: ModelField | None = None1470    is_sse_stream: bool = False1471    is_json_stream: bool = False14721473    @classmethod1474    def from_api_route(1475        cls,1476        *,1477        original_route: APIRoute,1478        include_context: _RouterIncludeContext,1479    ) -> "_EffectiveRouteContext":1480        route = cast(_APIRouteLike, original_route)1481        context = cls(original_route=original_route)1482        _populate_api_route_state(1483            cast(_APIRouteLike, context),1484            include_context.path_for(original_route),1485            route.endpoint,1486            response_model=route.response_model,1487            status_code=route.status_code,1488            tags=[*include_context.tags, *route.tags],1489            dependencies=[*include_context.dependencies, *route.dependencies],1490            summary=route.summary,1491            description=route.description,1492            response_description=route.response_description,1493            responses={**include_context.responses, **route.responses},1494            deprecated=route.deprecated or include_context.deprecated,1495            methods=route.methods,1496            operation_id=route.operation_id,1497            response_model_include=route.response_model_include,1498            response_model_exclude=route.response_model_exclude,1499            response_model_by_alias=route.response_model_by_alias,1500            response_model_exclude_unset=route.response_model_exclude_unset,1501            response_model_exclude_defaults=route.response_model_exclude_defaults,1502            response_model_exclude_none=route.response_model_exclude_none,1503            include_in_schema=route.include_in_schema1504            and include_context.include_in_schema,1505            response_class=get_value_or_default(1506                route.response_class,1507                include_context.included_router.default_response_class,1508                include_context.default_response_class,1509            ),1510            name=route.name,1511            dependency_overrides_provider=include_context.dependency_overrides_provider,1512            callbacks=[*include_context.callbacks, *(route.callbacks or [])],1513            openapi_extra=route.openapi_extra,1514            generate_unique_id_function=get_value_or_default(1515                route.generate_unique_id_function,1516                include_context.included_router.generate_unique_id_function,1517                include_context.generate_unique_id_function,1518            ),1519            strict_content_type=get_value_or_default(1520                route.strict_content_type,1521                include_context.included_router.strict_content_type,1522                include_context.strict_content_type,1523            ),1524            stream_item_type=route.stream_item_type,1525        )1526        # Build once per inclusion context, just as APIRoute does for direct routes.1527        # Integrations may wrap dependant.call while constructing the handler.1528        token = _effective_route_context_var.set(context)1529        try:1530            context.app = request_response(original_route.get_route_handler())1531        finally:1532            _effective_route_context_var.reset(token)1533        return context15341535    @classmethod1536    def from_frontend_route_group(1537        cls,1538        *,1539        original_route: "_FrontendRouteGroup",1540        include_context: _RouterIncludeContext,1541    ) -> "_EffectiveRouteContext":1542        dependencies = [*include_context.dependencies, *original_route.dependencies]1543        context = cls(1544            original_route=original_route,1545            frontend_prefix=include_context.prefix,1546            dependencies=dependencies,1547            dependency_overrides_provider=include_context.dependency_overrides_provider,1548        )1549        (1550            context.dependant,1551            _,1552            context._embed_body_fields,1553        ) = _build_dependant_with_parameterless_dependencies(1554            path="",1555            call=_frontend_dependency_endpoint,1556            dependencies=dependencies,1557        )1558        return context15591560    def matches(self, scope: Scope) -> tuple[Match, Scope]:1561        if isinstance(self.original_route, _FrontendRouteGroup):1562            return self.original_route.matches_with_prefix(scope, self.frontend_prefix)1563        if not isinstance(self.original_route, APIRoute):1564            assert self.starlette_route is not None1565            return self.starlette_route.matches(scope)1566        if scope["type"] != "http":1567            return Match.NONE, {}1568        route_path = get_route_path(scope)1569        match = self.path_regex.match(route_path)1570        if not match:1571            return Match.NONE, {}1572        matched_params = match.groupdict()1573        for key, value in matched_params.items():1574            matched_params[key] = self.param_convertors[key].convert(value)1575        path_params = dict(scope.get("path_params", {}))1576        path_params.update(matched_params)1577        child_scope = {"endpoint": self.endpoint, "path_params": path_params}1578        methods = self.methods1579        if methods and scope["method"] not in methods:1580            return Match.PARTIAL, child_scope1581        return Match.FULL, child_scope15821583    def url_path_for(self, name: str, /, **path_params: Any) -> Any:1584        if not isinstance(self.original_route, APIRoute):1585            assert self.starlette_route is not None1586            return self.starlette_route.url_path_for(name, **path_params)1587        seen_params = set(path_params.keys())1588        param_convertors = self.param_convertors1589        expected_params = set(param_convertors.keys())1590        if name != self.name or seen_params != expected_params:1591            raise routing.NoMatchFound(name, path_params)1592        path, remaining_params = routing.replace_params(1593            self.path_format, param_convertors, path_params1594        )1595        assert not remaining_params1596        return URLPath(path=path, protocol="http")159715981599@dataclass(frozen=True)1600class RouteContext:1601    route: BaseRoute1602    _route_context: _EffectiveRouteContext | None = field(default=None, repr=False)16031604    @property1605    def original_route(self) -> BaseRoute:1606        if self._route_context is not None:1607            return self._route_context.original_route1608        return self.route16091610    @property1611    def _effective_route(self) -> BaseRoute | _EffectiveRouteContext:1612        if self._route_context is not None:1613            if self._route_context.starlette_route is not None:1614                return self._route_context.starlette_route1615            return self._route_context1616        return self.route16171618    @property1619    def path(self) -> str | None:1620        return getattr(self._effective_route, "path", None)16211622    @property1623    def path_format(self) -> str | None:1624        return getattr(self._effective_route, "path_format", None)16251626    @property1627    def name(self) -> str | None:1628        return getattr(self._effective_route, "name", None)16291630    @property1631    def methods(self) -> set[str] | None:1632        return getattr(self._effective_route, "methods", None)16331634    @property1635    def endpoint(self) -> Callable[..., Any] | None:1636        return getattr(self._effective_route, "endpoint", None)16371638    def __getattr__(self, name: str) -> Any:1639        return getattr(self._effective_route, name)164016411642@dataclass1643class _IncludedRouter(BaseRoute):1644    original_router: "APIRouter"1645    include_context: _RouterIncludeContext1646    _effective_routes_lock: Any = field(1647        default_factory=threading.Lock, repr=False, compare=False1648    )1649    _effective_candidates: list["_EffectiveRouteContext | _IncludedRouter"] = field(1650        default_factory=list1651    )1652    _effective_candidates_version: int | None = None1653    _effective_low_priority_routes: list["_EffectiveRouteContext"] = field(1654        default_factory=list1655    )1656    _effective_low_priority_routes_version: int | None = None16571658    def effective_candidates(self) -> list["_EffectiveRouteContext | _IncludedRouter"]:1659        routes_version = self.original_router._get_routes_version()1660        if routes_version == self._effective_candidates_version:1661            return self._effective_candidates1662        with self._effective_routes_lock:1663            routes_version = self.original_router._get_routes_version()1664            if routes_version == self._effective_candidates_version:1665                return self._effective_candidates1666            effective_candidates: list[_EffectiveRouteContext | _IncludedRouter] = []1667            for route in self.original_router.routes:1668                if isinstance(route, _IncludedRouter):1669                    child_context = self.include_context.combine(route.include_context)1670                    child_branch = _IncludedRouter(1671                        original_router=route.original_router,1672                        include_context=child_context,1673                    )1674                    effective_candidates.append(child_branch)1675                    continue1676                route_context = self._build_effective_context(route)1677                if route_context is not None:1678                    effective_candidates.append(route_context)1679            self._effective_candidates = effective_candidates1680            self._effective_candidates_version = routes_version1681            return effective_candidates16821683    def effective_low_priority_routes(self) -> list["_EffectiveRouteContext"]:1684        routes_version = self.original_router._get_routes_version()1685        if routes_version == self._effective_low_priority_routes_version:1686            return self._effective_low_priority_routes1687        with self._effective_routes_lock:1688            routes_version = self.original_router._get_routes_version()1689            if routes_version == self._effective_low_priority_routes_version:1690                return self._effective_low_priority_routes1691            effective_low_priority_routes: list[_EffectiveRouteContext] = []1692            for route in self.original_router._low_priority_routes:1693                route_context = self._build_effective_context(route)1694                if route_context is not None:1695                    effective_low_priority_routes.append(route_context)1696            for route in self.original_router.routes:1697                if isinstance(route, _IncludedRouter):1698                    child_context = self.include_context.combine(route.include_context)1699                    child_branch = _IncludedRouter(1700                        original_router=route.original_router,1701                        include_context=child_context,1702                    )1703                    effective_low_priority_routes.extend(1704                        child_branch.effective_low_priority_routes()1705                    )1706            self._effective_low_priority_routes = effective_low_priority_routes1707            self._effective_low_priority_routes_version = routes_version1708            return effective_low_priority_routes17091710    def _build_effective_context(1711        self, route: BaseRoute1712    ) -> _EffectiveRouteContext | None:1713        if isinstance(route, APIRoute):1714            return _EffectiveRouteContext.from_api_route(1715                original_route=route,1716                include_context=self.include_context,1717            )1718        if isinstance(route, _FrontendRouteGroup):1719            return _EffectiveRouteContext.from_frontend_route_group(1720                original_route=route,1721                include_context=self.include_context,1722            )1723        if isinstance(route, routing.Route):1724            starlette_route: BaseRoute = routing.Route(1725                self.include_context.path_for(route),1726                endpoint=route.endpoint,1727                methods=list(route.methods or []),1728                name=route.name,1729                include_in_schema=route.include_in_schema,1730            )1731            return _EffectiveRouteContext(1732                original_route=route,1733                starlette_route=starlette_route,1734            )1735        if isinstance(route, APIWebSocketRoute):1736            starlette_route = APIWebSocketRoute(1737                self.include_context.path_for(route),1738                endpoint=route.endpoint,1739                name=route.name,1740                dependencies=[*self.include_context.dependencies, *route.dependencies],1741                dependency_overrides_provider=(1742                    self.include_context.dependency_overrides_provider1743                ),1744            )1745            return _EffectiveRouteContext(1746                original_route=route,1747                starlette_route=starlette_route,1748            )1749        if isinstance(route, routing.WebSocketRoute):1750            starlette_route = routing.WebSocketRoute(1751                self.include_context.path_for(route), route.endpoint, name=route.name1752            )1753            return _EffectiveRouteContext(1754                original_route=route,1755                starlette_route=starlette_route,1756            )1757        if isinstance(route, routing.Mount):1758            starlette_route = copy.copy(route)1759            starlette_route.path = self.include_context.path_for(route).rstrip("/")1760            (1761                starlette_route.path_regex,1762                starlette_route.path_format,1763                starlette_route.param_convertors,1764            ) = compile_path(starlette_route.path + "/{path:path}")1765            return _EffectiveRouteContext(1766                original_route=route,1767                starlette_route=starlette_route,1768            )1769        if isinstance(route, routing.Host):1770            if self.include_context.prefix:1771                prefixed_app: ASGIApp = routing.Router(1772                    routes=[routing.Mount(self.include_context.prefix, app=route.app)]1773                )1774            else:1775                prefixed_app = route.app1776            starlette_route = routing.Host(1777                route.host, app=prefixed_app, name=route.name1778            )1779            return _EffectiveRouteContext(1780                original_route=route,1781                starlette_route=starlette_route,1782            )1783        return None17841785    def _match(1786        self, scope: Scope1787    ) -> tuple[Match, Scope, BaseRoute | None, _EffectiveRouteContext | None]:1788        partial: tuple[Scope, BaseRoute, _EffectiveRouteContext | None] | None = None1789        for candidate in self.effective_candidates():1790            if isinstance(candidate, _IncludedRouter):1791                match, child_scope = candidate.matches(scope)1792                route: BaseRoute = candidate1793                route_context = None1794            elif isinstance(candidate.original_route, APIRoute):1795                route_context = candidate1796                fastapi_scope = _get_fastapi_scope(scope)1797                previous_context = fastapi_scope.get(1798                    _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY, _SCOPE_MISSING1799                )1800                fastapi_scope[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = route_context1801                try:1802                    match, child_scope = candidate.original_route.matches(scope)1803                finally:1804                    _restore_fastapi_scope_key(1805                        scope, _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY, previous_context1806                    )1807                route = candidate.original_route1808            else:1809                route_context = candidate1810                match, child_scope = candidate.matches(scope)1811                route = candidate.starlette_route or candidate.original_route1812            if match == Match.FULL:1813                return match, child_scope, route, route_context1814            if match == Match.PARTIAL and partial is None:1815                partial = (child_scope, route, route_context)1816        if partial is not None:1817            child_scope, route, route_context = partial1818            return Match.PARTIAL, child_scope, route, route_context1819        return Match.NONE, {}, None, None18201821    def matches(self, scope: Scope) -> tuple[Match, Scope]:1822        fastapi_scope = _get_fastapi_scope(scope)1823        previous_router = fastapi_scope.get(1824            _FASTAPI_INCLUDED_ROUTER_KEY, _SCOPE_MISSING1825        )1826        fastapi_scope[_FASTAPI_INCLUDED_ROUTER_KEY] = self1827        try:1828            match, _ = self.original_router.matches(scope)1829            return match, {}1830        finally:1831            _restore_fastapi_scope_key(1832                scope, _FASTAPI_INCLUDED_ROUTER_KEY, previous_router1833            )18341835    async def handle(self, scope: Scope, receive: Receive, send: Send) -> None:1836        _get_fastapi_scope(scope)[_FASTAPI_INCLUDED_ROUTER_KEY] = self1837        await self.original_router.handle(scope, receive, send)18381839    async def _handle_selected(1840        self, scope: Scope, receive: Receive, send: Send1841    ) -> None:1842        match, child_scope, route, effective_context = self._match(scope)1843        if match == Match.NONE or route is None:1844            await self.original_router.default(scope, receive, send)1845            return1846        scope.update(child_scope)1847        if isinstance(route, _IncludedRouter):1848            await route.handle(scope, receive, send)1849            return1850        if effective_context is not None:1851            _route_selected(1852                scope=scope,1853                path=getattr(effective_context.starlette_route, "path_format", None)1854                or effective_context.path_format,1855                mount=isinstance(route, routing.Mount),1856            )1857            _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = (1858                effective_context1859            )1860            original_route = effective_context.original_route1861            if isinstance(original_route, APIRoute):1862                scope["route"] = original_route1863                await original_route.handle(scope, receive, send)1864                return1865        await route.handle(scope, receive, send)18661867    def effective_route_contexts(self) -> Iterator[_EffectiveRouteContext]:1868        for candidate in self.effective_candidates():1869            if isinstance(candidate, _IncludedRouter):1870                yield from candidate.effective_route_contexts()1871            else:1872                yield candidate18731874    def url_path_for(self, name: str, /, **path_params: Any) -> Any:1875        for route_context in self.effective_route_contexts():1876            try:1877                return route_context.url_path_for(name, **path_params)1878            except routing.NoMatchFound:1879                pass1880        raise routing.NoMatchFound(name, path_params)188118821883def _iter_included_route_candidates(routes: Sequence[BaseRoute]) -> Iterator[BaseRoute]:1884    for route, route_context in _iter_routes_with_context(routes):1885        if route_context is not None and route_context.starlette_route is not None:1886            yield route_context.starlette_route1887        else:1888            yield route188918901891def iter_route_contexts(1892    routes: Sequence[BaseRoute | RouteContext],1893) -> Iterator[RouteContext]:1894    for route in routes:1895        if isinstance(route, RouteContext):1896            yield route1897            continue1898        for original_route, route_context in _iter_routes_with_context([route]):1899            if route_context is None:1900                yield RouteContext(original_route)1901            else:1902                yield RouteContext(original_route, route_context)190319041905def _iter_routes_with_context(1906    routes: Sequence[BaseRoute],1907) -> Iterator[tuple[BaseRoute, _EffectiveRouteContext | None]]:1908    for route in routes:1909        if isinstance(route, _IncludedRouter):1910            for route_context in route.effective_route_contexts():1911                yield route_context.original_route, route_context1912        else:1913            yield route, None191419151916def _normalize_frontend_path(path: str) -> str:1917    if not path:1918        raise AssertionError("A frontend path cannot be empty")1919    if not path.startswith("/"):1920        raise AssertionError("A frontend path must start with '/'")1921    if path != "/":1922        path = path.rstrip("/")1923    return path192419251926def _join_frontend_paths(prefix: str, path: str) -> str:1927    if not prefix:1928        return path1929    if path == "/":1930        return prefix1931    return prefix + path193219331934def _frontend_path_specificity(path: str) -> int:1935    if path == "/":1936        return 01937    return len(path)193819391940def _get_resolved_absolute_path(path: str | os.PathLike[str]) -> str:1941    return os.path.realpath(os.fspath(path))194219431944def _resolve_frontend_check_dir(1945    *,1946    directory: str | os.PathLike[str],1947    check_dir: bool | Literal["auto"],1948) -> bool:1949    if check_dir != "auto":1950        return check_dir1951    if os.environ.get("FASTAPI_ENV") != "development":1952        return True1953    if not os.path.isdir(directory):1954        warnings.warn(1955            f"Frontend directory '{directory}' does not exist. "1956            f"Resolved absolute path: '{_get_resolved_absolute_path(directory)}'",1957            stacklevel=3,1958        )1959    return False196019611962class _FrontendStaticFiles(StaticFiles):1963    def __init__(1964        self,1965        *,1966        directory: str | os.PathLike[str],1967        fallback: Literal["auto", "index.html", "404.html"] | None,1968        check_dir: bool,1969    ) -> None:1970        self.fallback = fallback1971        if check_dir and not os.path.isdir(directory):1972            raise RuntimeError(1973                f"Frontend directory '{directory}' does not exist. "1974                f"Resolved absolute path: '{_get_resolved_absolute_path(directory)}'"1975            )1976        super().__init__(1977            directory=directory,1978            html=True,1979            check_dir=check_dir,1980            follow_symlink=False,1981        )1982        if check_dir and fallback in {"index.html", "404.html"}:1983            self._check_fallback_file(fallback)19841985    def _check_fallback_file(self, fallback: str) -> None:1986        _, stat_result = self.lookup_path(fallback)1987        if stat_result is None or not stat.S_ISREG(stat_result.st_mode):1988            raise RuntimeError(1989                f"Frontend fallback file '{fallback}' does not exist in "1990                f"directory '{self.directory}'. Resolved absolute directory: "1991                f"'{self._get_resolved_directory()}'"1992            )19931994    def _get_resolved_directory(self) -> str:1995        assert self.directory is not None1996        return _get_resolved_absolute_path(self.directory)19971998    def get_path(self, scope: Scope) -> str:1999        path = _get_fastapi_scope(scope).get(_FASTAPI_FRONTEND_PATH_KEY, "")2000        assert isinstance(path, str)

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings