fastapi/routing.py PYTHON 4,957 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 4,957.
1import contextlib2import email.message3import functools4import inspect5import json6import types7from collections.abc import (8    AsyncIterator,9    Awaitable,10    Callable,11    Collection,12    Coroutine,13    Generator,14    Iterator,15    Mapping,16    Sequence,17)18from contextlib import (19    AbstractAsyncContextManager,20    AbstractContextManager,21    AsyncExitStack,22    asynccontextmanager,23)24from enum import Enum, IntEnum25from typing import (26    Annotated,27    Any,28    TypeVar,29    cast,30)3132import anyio33from annotated_doc import Doc34from anyio.abc import ObjectReceiveStream35from fastapi import params36from fastapi._compat import (37    ModelField,38    Undefined,39    lenient_issubclass,40)41from fastapi.datastructures import Default, DefaultPlaceholder42from fastapi.dependencies.models import Dependant43from fastapi.dependencies.utils import (44    _should_embed_body_fields,45    get_body_field,46    get_dependant,47    get_flat_dependant,48    get_parameterless_sub_dependant,49    get_stream_item_type,50    get_typed_return_annotation,51    solve_dependencies,52)53from fastapi.encoders import jsonable_encoder54from fastapi.exceptions import (55    EndpointContext,56    FastAPIError,57    RequestValidationError,58    ResponseValidationError,59    WebSocketRequestValidationError,60)61from fastapi.sse import (62    _PING_INTERVAL,63    KEEPALIVE_COMMENT,64    EventSourceResponse,65    ServerSentEvent,66    format_sse_event,67)68from fastapi.types import DecoratedCallable, IncEx69from fastapi.utils import (70    create_model_field,71    generate_unique_id,72    get_value_or_default,73    is_body_allowed_for_status_code,74)75from starlette import routing76from starlette._exception_handler import wrap_app_handling_exceptions77from starlette._utils import is_async_callable78from starlette.concurrency import iterate_in_threadpool, run_in_threadpool79from starlette.datastructures import FormData80from starlette.exceptions import HTTPException81from starlette.requests import Request82from starlette.responses import JSONResponse, Response, StreamingResponse83from starlette.routing import (84    BaseRoute,85    Match,86    compile_path,87    get_name,88)89from starlette.routing import Mount as Mount  # noqa90from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send91from starlette.websockets import WebSocket92from typing_extensions import deprecated939495# Copy of starlette.routing.request_response modified to include the96# dependencies' AsyncExitStack97def request_response(98    func: Callable[[Request], Awaitable[Response] | Response],99) -> ASGIApp:100    """101    Takes a function or coroutine `func(request) -> response`,102    and returns an ASGI application.103    """104    f: Callable[[Request], Awaitable[Response]] = (105        func  # type: ignore[assignment]106        if is_async_callable(func)107        else functools.partial(run_in_threadpool, func)  # type: ignore[call-arg]108    )  # ty: ignore[invalid-assignment]109110    async def app(scope: Scope, receive: Receive, send: Send) -> None:111        request = Request(scope, receive, send)112113        async def app(scope: Scope, receive: Receive, send: Send) -> None:114            # Starts customization115            response_awaited = False116            async with AsyncExitStack() as request_stack:117                scope["fastapi_inner_astack"] = request_stack118                async with AsyncExitStack() as function_stack:119                    scope["fastapi_function_astack"] = function_stack120                    response = await f(request)121                await response(scope, receive, send)122                # Continues customization123                response_awaited = True124            if not response_awaited:125                raise FastAPIError(126                    "Response not awaited. There's a high chance that the "127                    "application code is raising an exception and a dependency with yield "128                    "has a block with a bare except, or a block with except Exception, "129                    "and is not raising the exception again. Read more about it in the "130                    "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except"131                )132133        # Same as in Starlette134        await wrap_app_handling_exceptions(app, request)(scope, receive, send)135136    return app137138139# Copy of starlette.routing.websocket_session modified to include the140# dependencies' AsyncExitStack141def websocket_session(142    func: Callable[[WebSocket], Awaitable[None]],143) -> ASGIApp:144    """145    Takes a coroutine `func(session)`, and returns an ASGI application.146    """147    # assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async"148149    async def app(scope: Scope, receive: Receive, send: Send) -> None:150        session = WebSocket(scope, receive=receive, send=send)151152        async def app(scope: Scope, receive: Receive, send: Send) -> None:153            async with AsyncExitStack() as request_stack:154                scope["fastapi_inner_astack"] = request_stack155                async with AsyncExitStack() as function_stack:156                    scope["fastapi_function_astack"] = function_stack157                    await func(session)158159        # Same as in Starlette160        await wrap_app_handling_exceptions(app, session)(scope, receive, send)161162    return app163164165_T = TypeVar("_T")166167168# Vendored from starlette.routing to avoid importing private symbols169class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]):170    """171    Wraps a synchronous context manager to make it async.172173    This is vendored from Starlette to avoid importing private symbols.174    """175176    def __init__(self, cm: AbstractContextManager[_T]) -> None:177        self._cm = cm178179    async def __aenter__(self) -> _T:180        return self._cm.__enter__()181182    async def __aexit__(183        self,184        exc_type: type[BaseException] | None,185        exc_value: BaseException | None,186        traceback: types.TracebackType | None,187    ) -> bool | None:188        return self._cm.__exit__(exc_type, exc_value, traceback)189190191# Vendored from starlette.routing to avoid importing private symbols192def _wrap_gen_lifespan_context(193    lifespan_context: Callable[[Any], Generator[Any, Any, Any]],194) -> Callable[[Any], AbstractAsyncContextManager[Any]]:195    """196    Wrap a generator-based lifespan context into an async context manager.197198    This is vendored from Starlette to avoid importing private symbols.199    """200    cmgr = contextlib.contextmanager(lifespan_context)201202    @functools.wraps(cmgr)203    def wrapper(app: Any) -> _AsyncLiftContextManager[Any]:204        return _AsyncLiftContextManager(cmgr(app))205206    return wrapper207208209def _merge_lifespan_context(210    original_context: Lifespan[Any], nested_context: Lifespan[Any]211) -> Lifespan[Any]:212    @asynccontextmanager213    async def merged_lifespan(214        app: AppType,215    ) -> AsyncIterator[Mapping[str, Any] | None]:216        async with original_context(app) as maybe_original_state:217            async with nested_context(app) as maybe_nested_state:218                if maybe_nested_state is None and maybe_original_state is None:219                    yield None  # old ASGI compatibility220                else:221                    yield {**(maybe_nested_state or {}), **(maybe_original_state or {})}222223    return merged_lifespan  # type: ignore[return-value]  # ty: ignore[invalid-return-type]224225226class _DefaultLifespan:227    """228    Default lifespan context manager that runs on_startup and on_shutdown handlers.229230    This is a copy of the Starlette _DefaultLifespan class that was removed231    in Starlette. FastAPI keeps it to maintain backward compatibility with232    on_startup and on_shutdown event handlers.233234    Ref: https://github.com/Kludex/starlette/pull/3117235    """236237    def __init__(self, router: "APIRouter") -> None:238        self._router = router239240    async def __aenter__(self) -> None:241        await self._router._startup()242243    async def __aexit__(self, *exc_info: object) -> None:244        await self._router._shutdown()245246    def __call__(self: _T, app: object) -> _T:247        return self248249250# Cache for endpoint context to avoid re-extracting on every request251_endpoint_context_cache: dict[int, EndpointContext] = {}252253254def _extract_endpoint_context(func: Any) -> EndpointContext:255    """Extract endpoint context with caching to avoid repeated file I/O."""256    func_id = id(func)257258    if func_id in _endpoint_context_cache:259        return _endpoint_context_cache[func_id]260261    try:262        ctx: EndpointContext = {}263264        if (source_file := inspect.getsourcefile(func)) is not None:265            ctx["file"] = source_file266        if (line_number := inspect.getsourcelines(func)[1]) is not None:267            ctx["line"] = line_number268        if (func_name := getattr(func, "__name__", None)) is not None:269            ctx["function"] = func_name270    except Exception:271        ctx = EndpointContext()272273    _endpoint_context_cache[func_id] = ctx274    return ctx275276277async def serialize_response(278    *,279    field: ModelField | None = None,280    response_content: Any,281    include: IncEx | None = None,282    exclude: IncEx | None = None,283    by_alias: bool = True,284    exclude_unset: bool = False,285    exclude_defaults: bool = False,286    exclude_none: bool = False,287    is_coroutine: bool = True,288    endpoint_ctx: EndpointContext | None = None,289    dump_json: bool = False,290) -> Any:291    if field:292        if is_coroutine:293            value, errors = field.validate(response_content, {}, loc=("response",))294        else:295            value, errors = await run_in_threadpool(296                field.validate, response_content, {}, loc=("response",)297            )298        if errors:299            ctx = endpoint_ctx or EndpointContext()300            raise ResponseValidationError(301                errors=errors,302                body=response_content,303                endpoint_ctx=ctx,304            )305        serializer = field.serialize_json if dump_json else field.serialize306        return serializer(307            value,308            include=include,309            exclude=exclude,310            by_alias=by_alias,311            exclude_unset=exclude_unset,312            exclude_defaults=exclude_defaults,313            exclude_none=exclude_none,314        )315316    else:317        return jsonable_encoder(response_content)318319320async def run_endpoint_function(321    *, dependant: Dependant, values: dict[str, Any], is_coroutine: bool322) -> Any:323    # Only called by get_request_handler. Has been split into its own function to324    # facilitate profiling endpoints, since inner functions are harder to profile.325    assert dependant.call is not None, "dependant.call must be a function"326327    if is_coroutine:328        return await dependant.call(**values)329    else:330        return await run_in_threadpool(dependant.call, **values)331332333def _build_response_args(334    *, status_code: int | None, solved_result: Any335) -> dict[str, Any]:336    response_args: dict[str, Any] = {337        "background": solved_result.background_tasks,338    }339    # If status_code was set, use it, otherwise use the default from the340    # response class, in the case of redirect it's 307341    current_status_code = (342        status_code if status_code else solved_result.response.status_code343    )344    if current_status_code is not None:345        response_args["status_code"] = current_status_code346    if solved_result.response.status_code:347        response_args["status_code"] = solved_result.response.status_code348    return response_args349350351def get_request_handler(352    dependant: Dependant,353    body_field: ModelField | None = None,354    status_code: int | None = None,355    response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),356    response_field: ModelField | None = None,357    response_model_include: IncEx | None = None,358    response_model_exclude: IncEx | None = None,359    response_model_by_alias: bool = True,360    response_model_exclude_unset: bool = False,361    response_model_exclude_defaults: bool = False,362    response_model_exclude_none: bool = False,363    dependency_overrides_provider: Any | None = None,364    embed_body_fields: bool = False,365    strict_content_type: bool | DefaultPlaceholder = Default(True),366    stream_item_field: ModelField | None = None,367    is_json_stream: bool = False,368) -> Callable[[Request], Coroutine[Any, Any, Response]]:369    assert dependant.call is not None, "dependant.call must be a function"370    is_coroutine = dependant.is_coroutine_callable371    is_body_form = body_field and isinstance(body_field.field_info, params.Form)372    if isinstance(response_class, DefaultPlaceholder):373        actual_response_class: type[Response] = response_class.value374    else:375        actual_response_class = response_class376    is_sse_stream = lenient_issubclass(actual_response_class, EventSourceResponse)377    if isinstance(strict_content_type, DefaultPlaceholder):378        actual_strict_content_type: bool = strict_content_type.value379    else:380        actual_strict_content_type = strict_content_type381382    async def app(request: Request) -> Response:383        response: Response | None = None384        file_stack = request.scope.get("fastapi_middleware_astack")385        assert isinstance(file_stack, AsyncExitStack), (386            "fastapi_middleware_astack not found in request scope"387        )388389        # Extract endpoint context for error messages390        endpoint_ctx = (391            _extract_endpoint_context(dependant.call)392            if dependant.call393            else EndpointContext()394        )395396        if dependant.path:397            # For mounted sub-apps, include the mount path prefix398            mount_path = request.scope.get("root_path", "").rstrip("/")399            endpoint_ctx["path"] = f"{request.method} {mount_path}{dependant.path}"400401        # Read body and auto-close files402        try:403            body: Any = None404            if body_field:405                if is_body_form:406                    body = await request.form()407                    file_stack.push_async_callback(body.close)408                else:409                    body_bytes = await request.body()410                    if body_bytes:411                        json_body: Any = Undefined412                        content_type_value = request.headers.get("content-type")413                        if not content_type_value:414                            if not actual_strict_content_type:415                                json_body = await request.json()416                        else:417                            message = email.message.Message()418                            message["content-type"] = content_type_value419                            if message.get_content_maintype() == "application":420                                subtype = message.get_content_subtype()421                                if subtype == "json" or subtype.endswith("+json"):422                                    json_body = await request.json()423                        if json_body != Undefined:424                            body = json_body425                        else:426                            body = body_bytes427        except json.JSONDecodeError as e:428            validation_error = RequestValidationError(429                [430                    {431                        "type": "json_invalid",432                        "loc": ("body", e.pos),433                        "msg": "JSON decode error",434                        "input": {},435                        "ctx": {"error": e.msg},436                    }437                ],438                body=e.doc,439                endpoint_ctx=endpoint_ctx,440            )441            raise validation_error from e442        except HTTPException:443            # If a middleware raises an HTTPException, it should be raised again444            raise445        except Exception as e:446            http_error = HTTPException(447                status_code=400, detail="There was an error parsing the body"448            )449            raise http_error from e450451        # Solve dependencies and run path operation function, auto-closing dependencies452        errors: list[Any] = []453        async_exit_stack = request.scope.get("fastapi_inner_astack")454        assert isinstance(async_exit_stack, AsyncExitStack), (455            "fastapi_inner_astack not found in request scope"456        )457        solved_result = await solve_dependencies(458            request=request,459            dependant=dependant,460            body=cast(dict[str, Any] | FormData | bytes | None, body),461            dependency_overrides_provider=dependency_overrides_provider,462            async_exit_stack=async_exit_stack,463            embed_body_fields=embed_body_fields,464        )465        errors = solved_result.errors466        assert dependant.call  # For types467        if not errors:468            # Shared serializer for stream items (JSONL and SSE).469            # Validates against stream_item_field when set, then470            # serializes to JSON bytes.471            def _serialize_data(data: Any) -> bytes:472                if stream_item_field:473                    value, errors_ = stream_item_field.validate(474                        data, {}, loc=("response",)475                    )476                    if errors_:477                        ctx = endpoint_ctx or EndpointContext()478                        raise ResponseValidationError(479                            errors=errors_,480                            body=data,481                            endpoint_ctx=ctx,482                        )483                    return stream_item_field.serialize_json(484                        value,485                        include=response_model_include,486                        exclude=response_model_exclude,487                        by_alias=response_model_by_alias,488                        exclude_unset=response_model_exclude_unset,489                        exclude_defaults=response_model_exclude_defaults,490                        exclude_none=response_model_exclude_none,491                    )492                else:493                    data = jsonable_encoder(data)494                    return json.dumps(data).encode("utf-8")495496            if is_sse_stream:497                # Generator endpoint: stream as Server-Sent Events498                gen = dependant.call(**solved_result.values)499500                def _serialize_sse_item(item: Any) -> bytes:501                    if isinstance(item, ServerSentEvent):502                        # User controls the event structure.503                        # Serialize the data payload if present.504                        # For ServerSentEvent items we skip stream_item_field505                        # validation (the user may mix types intentionally).506                        if item.raw_data is not None:507                            data_str: str | None = item.raw_data508                        elif item.data is not None:509                            if hasattr(item.data, "model_dump_json"):510                                data_str = item.data.model_dump_json()511                            else:512                                data_str = json.dumps(jsonable_encoder(item.data))513                        else:514                            data_str = None515                        return format_sse_event(516                            data_str=data_str,517                            event=item.event,518                            id=item.id,519                            retry=item.retry,520                            comment=item.comment,521                        )522                    else:523                        # Plain object: validate + serialize via524                        # stream_item_field (if set) and wrap in data field525                        return format_sse_event(526                            data_str=_serialize_data(item).decode("utf-8")527                        )528529                if dependant.is_async_gen_callable:530                    sse_aiter: AsyncIterator[Any] = gen.__aiter__()531                else:532                    sse_aiter = iterate_in_threadpool(gen)533534                @asynccontextmanager535                async def _sse_producer_cm() -> AsyncIterator[536                    ObjectReceiveStream[bytes]537                ]:538                    # Use a memory stream to decouple generator iteration539                    # from the keepalive timer. A producer task pulls items540                    # from the generator independently, so541                    # `anyio.fail_after` never wraps the generator's542                    # `__anext__` directly - avoiding CancelledError that543                    # would finalize the generator and also working for sync544                    # generators running in a thread pool.545                    #546                    # This context manager is entered on the request-scoped547                    # AsyncExitStack so its __aexit__ (which cancels the548                    # task group) is called by the exit stack after the549                    # streaming response completes  not by async generator550                    # finalization via GeneratorExit.551                    # Ref: https://peps.python.org/pep-0789/552                    send_stream, receive_stream = anyio.create_memory_object_stream[553                        bytes554                    ](max_buffer_size=1)555556                    async def _producer() -> None:557                        async with send_stream:558                            async for raw_item in sse_aiter:559                                await send_stream.send(_serialize_sse_item(raw_item))560561                    send_keepalive, receive_keepalive = (562                        anyio.create_memory_object_stream[bytes](max_buffer_size=1)563                    )564565                    async def _keepalive_inserter() -> None:566                        """Read from the producer and forward to the output,567                        inserting keepalive comments on timeout."""568                        async with send_keepalive, receive_stream:569                            try:570                                while True:571                                    try:572                                        with anyio.fail_after(_PING_INTERVAL):573                                            data = await receive_stream.receive()574                                        await send_keepalive.send(data)575                                    except TimeoutError:576                                        await send_keepalive.send(KEEPALIVE_COMMENT)577                            except anyio.EndOfStream:578                                pass579580                    async with anyio.create_task_group() as tg:581                        tg.start_soon(_producer)582                        tg.start_soon(_keepalive_inserter)583                        yield receive_keepalive584                        tg.cancel_scope.cancel()585586                # Enter the SSE context manager on the request-scoped587                # exit stack. The stack outlives the streaming response,588                # so __aexit__ runs via proper structured teardown, not589                # via GeneratorExit thrown into an async generator.590                sse_receive_stream = await async_exit_stack.enter_async_context(591                    _sse_producer_cm()592                )593                # Ensure the receive stream is closed when the exit stack594                # unwinds, preventing ResourceWarning from __del__.595                async_exit_stack.push_async_callback(sse_receive_stream.aclose)596597                async def _sse_with_checkpoints(598                    stream: ObjectReceiveStream[bytes],599                ) -> AsyncIterator[bytes]:600                    async for data in stream:601                        yield data602                        # Guarantee a checkpoint so cancellation can be603                        # delivered even when the producer is faster than604                        # the consumer and receive() never suspends.605                        await anyio.sleep(0)606607                sse_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (608                    _sse_with_checkpoints(sse_receive_stream)609                )610611                response = StreamingResponse(612                    sse_stream_content,613                    media_type="text/event-stream",614                    background=solved_result.background_tasks,615                )616                response.headers["Cache-Control"] = "no-cache"617                # For Nginx proxies to not buffer server sent events618                response.headers["X-Accel-Buffering"] = "no"619                response.headers.raw.extend(solved_result.response.headers.raw)620            elif is_json_stream:621                # Generator endpoint: stream as JSONL622                gen = dependant.call(**solved_result.values)623624                def _serialize_item(item: Any) -> bytes:625                    return _serialize_data(item) + b"\n"626627                if dependant.is_async_gen_callable:628629                    async def _async_stream_jsonl() -> AsyncIterator[bytes]:630                        async for item in gen:631                            yield _serialize_item(item)632                            # To allow for cancellation to trigger633                            # Ref: https://github.com/fastapi/fastapi/issues/14680634                            await anyio.sleep(0)635636                    jsonl_stream_content: AsyncIterator[bytes] | Iterator[bytes] = (637                        _async_stream_jsonl()638                    )639                else:640641                    def _sync_stream_jsonl() -> Iterator[bytes]:642                        for item in gen:  # ty: ignore[not-iterable]643                            yield _serialize_item(item)644645                    jsonl_stream_content = _sync_stream_jsonl()646647                response = StreamingResponse(648                    jsonl_stream_content,649                    media_type="application/jsonl",650                    background=solved_result.background_tasks,651                )652                response.headers.raw.extend(solved_result.response.headers.raw)653            elif dependant.is_async_gen_callable or dependant.is_gen_callable:654                # Raw streaming with explicit response_class (e.g. StreamingResponse)655                gen = dependant.call(**solved_result.values)656                if dependant.is_async_gen_callable:657658                    async def _async_stream_raw(659                        async_gen: AsyncIterator[Any],660                    ) -> AsyncIterator[Any]:661                        async for chunk in async_gen:662                            yield chunk663                            # To allow for cancellation to trigger664                            # Ref: https://github.com/fastapi/fastapi/issues/14680665                            await anyio.sleep(0)666667                    gen = _async_stream_raw(gen)668                response_args = _build_response_args(669                    status_code=status_code, solved_result=solved_result670                )671                response = actual_response_class(content=gen, **response_args)672                response.headers.raw.extend(solved_result.response.headers.raw)673            else:674                raw_response = await run_endpoint_function(675                    dependant=dependant,676                    values=solved_result.values,677                    is_coroutine=is_coroutine,678                )679                if isinstance(raw_response, Response):680                    if raw_response.background is None:681                        raw_response.background = solved_result.background_tasks682                    response = raw_response683                else:684                    response_args = _build_response_args(685                        status_code=status_code, solved_result=solved_result686                    )687                    # Use the fast path (dump_json) when no custom response688                    # class was set and a response field with a TypeAdapter689                    # exists. Serializes directly to JSON bytes via Pydantic's690                    # Rust core, skipping the intermediate Python dict +691                    # json.dumps() step.692                    use_dump_json = response_field is not None and isinstance(693                        response_class, DefaultPlaceholder694                    )695                    content = await serialize_response(696                        field=response_field,697                        response_content=raw_response,698                        include=response_model_include,699                        exclude=response_model_exclude,700                        by_alias=response_model_by_alias,701                        exclude_unset=response_model_exclude_unset,702                        exclude_defaults=response_model_exclude_defaults,703                        exclude_none=response_model_exclude_none,704                        is_coroutine=is_coroutine,705                        endpoint_ctx=endpoint_ctx,706                        dump_json=use_dump_json,707                    )708                    if use_dump_json:709                        response = Response(710                            content=content,711                            media_type="application/json",712                            **response_args,713                        )714                    else:715                        response = actual_response_class(content, **response_args)716                    if not is_body_allowed_for_status_code(response.status_code):717                        response.body = b""718                    response.headers.raw.extend(solved_result.response.headers.raw)719        if errors:720            validation_error = RequestValidationError(721                errors, body=body, endpoint_ctx=endpoint_ctx722            )723            raise validation_error724725        # Return response726        assert response727        return response728729    return app730731732def get_websocket_app(733    dependant: Dependant,734    dependency_overrides_provider: Any | None = None,735    embed_body_fields: bool = False,736) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]:737    async def app(websocket: WebSocket) -> None:738        endpoint_ctx = (739            _extract_endpoint_context(dependant.call)740            if dependant.call741            else EndpointContext()742        )743        if dependant.path:744            # For mounted sub-apps, include the mount path prefix745            mount_path = websocket.scope.get("root_path", "").rstrip("/")746            endpoint_ctx["path"] = f"WS {mount_path}{dependant.path}"747        async_exit_stack = websocket.scope.get("fastapi_inner_astack")748        assert isinstance(async_exit_stack, AsyncExitStack), (749            "fastapi_inner_astack not found in request scope"750        )751        solved_result = await solve_dependencies(752            request=websocket,753            dependant=dependant,754            dependency_overrides_provider=dependency_overrides_provider,755            async_exit_stack=async_exit_stack,756            embed_body_fields=embed_body_fields,757        )758        if solved_result.errors:759            raise WebSocketRequestValidationError(760                solved_result.errors,761                endpoint_ctx=endpoint_ctx,762            )763        assert dependant.call is not None, "dependant.call must be a function"764        await dependant.call(**solved_result.values)765766    return app767768769class APIWebSocketRoute(routing.WebSocketRoute):770    def __init__(771        self,772        path: str,773        endpoint: Callable[..., Any],774        *,775        name: str | None = None,776        dependencies: Sequence[params.Depends] | None = None,777        dependency_overrides_provider: Any | None = None,778    ) -> None:779        self.path = path780        self.endpoint = endpoint781        self.name = get_name(endpoint) if name is None else name782        self.dependencies = list(dependencies or [])783        self.path_regex, self.path_format, self.param_convertors = compile_path(path)784        self.dependant = get_dependant(785            path=self.path_format, call=self.endpoint, scope="function"786        )787        for depends in self.dependencies[::-1]:788            self.dependant.dependencies.insert(789                0,790                get_parameterless_sub_dependant(depends=depends, path=self.path_format),791            )792        self._flat_dependant = get_flat_dependant(self.dependant)793        self._embed_body_fields = _should_embed_body_fields(794            self._flat_dependant.body_params795        )796        self.app = websocket_session(797            get_websocket_app(798                dependant=self.dependant,799                dependency_overrides_provider=dependency_overrides_provider,800                embed_body_fields=self._embed_body_fields,801            )802        )803804    def matches(self, scope: Scope) -> tuple[Match, Scope]:805        match, child_scope = super().matches(scope)806        if match != Match.NONE:807            child_scope["route"] = self808        return match, child_scope809810811class APIRoute(routing.Route):812    def __init__(813        self,814        path: str,815        endpoint: Callable[..., Any],816        *,817        response_model: Any = Default(None),818        status_code: int | None = None,819        tags: list[str | Enum] | None = None,820        dependencies: Sequence[params.Depends] | None = None,821        summary: str | None = None,822        description: str | None = None,823        response_description: str = "Successful Response",824        responses: dict[int | str, dict[str, Any]] | None = None,825        deprecated: bool | None = None,826        name: str | None = None,827        methods: set[str] | list[str] | None = None,828        operation_id: str | None = None,829        response_model_include: IncEx | None = None,830        response_model_exclude: IncEx | None = None,831        response_model_by_alias: bool = True,832        response_model_exclude_unset: bool = False,833        response_model_exclude_defaults: bool = False,834        response_model_exclude_none: bool = False,835        include_in_schema: bool = True,836        response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),837        dependency_overrides_provider: Any | None = None,838        callbacks: list[BaseRoute] | None = None,839        openapi_extra: dict[str, Any] | None = None,840        generate_unique_id_function: Callable[["APIRoute"], str]841        | DefaultPlaceholder = Default(generate_unique_id),842        strict_content_type: bool | DefaultPlaceholder = Default(True),843    ) -> None:844        self.path = path845        self.endpoint = endpoint846        self.stream_item_type: Any | None = None847        if isinstance(response_model, DefaultPlaceholder):848            return_annotation = get_typed_return_annotation(endpoint)849            if lenient_issubclass(return_annotation, Response):850                response_model = None851            else:852                stream_item = get_stream_item_type(return_annotation)853                if stream_item is not None:854                    # Extract item type for JSONL or SSE streaming when855                    # response_class is DefaultPlaceholder (JSONL) or856                    # EventSourceResponse (SSE).857                    # ServerSentEvent is excluded: it's a transport858                    # wrapper, not a data model, so it shouldn't feed859                    # into validation or OpenAPI schema generation.860                    if (861                        isinstance(response_class, DefaultPlaceholder)862                        or lenient_issubclass(response_class, EventSourceResponse)863                    ) and not lenient_issubclass(stream_item, ServerSentEvent):864                        self.stream_item_type = stream_item865                    response_model = None866                else:867                    response_model = return_annotation868        self.response_model = response_model869        self.summary = summary870        self.response_description = response_description871        self.deprecated = deprecated872        self.operation_id = operation_id873        self.response_model_include = response_model_include874        self.response_model_exclude = response_model_exclude875        self.response_model_by_alias = response_model_by_alias876        self.response_model_exclude_unset = response_model_exclude_unset877        self.response_model_exclude_defaults = response_model_exclude_defaults878        self.response_model_exclude_none = response_model_exclude_none879        self.include_in_schema = include_in_schema880        self.response_class = response_class881        self.dependency_overrides_provider = dependency_overrides_provider882        self.callbacks = callbacks883        self.openapi_extra = openapi_extra884        self.generate_unique_id_function = generate_unique_id_function885        self.strict_content_type = strict_content_type886        self.tags = tags or []887        self.responses = responses or {}888        self.name = get_name(endpoint) if name is None else name889        self.path_regex, self.path_format, self.param_convertors = compile_path(path)890        if methods is None:891            methods = ["GET"]892        self.methods: set[str] = {method.upper() for method in methods}893        if isinstance(generate_unique_id_function, DefaultPlaceholder):894            current_generate_unique_id: Callable[[APIRoute], str] = (895                generate_unique_id_function.value896            )897        else:898            current_generate_unique_id = generate_unique_id_function899        self.unique_id = self.operation_id or current_generate_unique_id(self)900        # normalize enums e.g. http.HTTPStatus901        if isinstance(status_code, IntEnum):902            status_code = int(status_code)903        self.status_code = status_code904        if self.response_model:905            assert is_body_allowed_for_status_code(status_code), (906                f"Status code {status_code} must not have a response body"907            )908            response_name = "Response_" + self.unique_id909            self.response_field = create_model_field(910                name=response_name,911                type_=self.response_model,912                mode="serialization",913            )914        else:915            self.response_field = None  # type: ignore[assignment]916        if self.stream_item_type:917            stream_item_name = "StreamItem_" + self.unique_id918            self.stream_item_field: ModelField | None = create_model_field(919                name=stream_item_name,920                type_=self.stream_item_type,921                mode="serialization",922            )923        else:924            self.stream_item_field = None925        self.dependencies = list(dependencies or [])926        self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "")927        # if a "form feed" character (page break) is found in the description text,928        # truncate description text to the content preceding the first "form feed"929        self.description = self.description.split("\f")[0].strip()930        response_fields = {}931        for additional_status_code, response in self.responses.items():932            assert isinstance(response, dict), "An additional response must be a dict"933            model = response.get("model")934            if model:935                assert is_body_allowed_for_status_code(additional_status_code), (936                    f"Status code {additional_status_code} must not have a response body"937                )938                response_name = f"Response_{additional_status_code}_{self.unique_id}"939                response_field = create_model_field(940                    name=response_name, type_=model, mode="serialization"941                )942                response_fields[additional_status_code] = response_field943        if response_fields:944            self.response_fields: dict[int | str, ModelField] = response_fields945        else:946            self.response_fields = {}947948        assert callable(endpoint), "An endpoint must be a callable"949        self.dependant = get_dependant(950            path=self.path_format, call=self.endpoint, scope="function"951        )952        for depends in self.dependencies[::-1]:953            self.dependant.dependencies.insert(954                0,955                get_parameterless_sub_dependant(depends=depends, path=self.path_format),956            )957        self._flat_dependant = get_flat_dependant(self.dependant)958        self._embed_body_fields = _should_embed_body_fields(959            self._flat_dependant.body_params960        )961        self.body_field = get_body_field(962            flat_dependant=self._flat_dependant,963            name=self.unique_id,964            embed_body_fields=self._embed_body_fields,965        )966        # Detect generator endpoints that should stream as JSONL or SSE967        is_generator = (968            self.dependant.is_async_gen_callable or self.dependant.is_gen_callable969        )970        self.is_sse_stream = is_generator and lenient_issubclass(971            response_class, EventSourceResponse972        )973        self.is_json_stream = is_generator and isinstance(974            response_class, DefaultPlaceholder975        )976        self.app = request_response(self.get_route_handler())977978    def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:979        return get_request_handler(980            dependant=self.dependant,981            body_field=self.body_field,982            status_code=self.status_code,983            response_class=self.response_class,984            response_field=self.response_field,985            response_model_include=self.response_model_include,986            response_model_exclude=self.response_model_exclude,987            response_model_by_alias=self.response_model_by_alias,988            response_model_exclude_unset=self.response_model_exclude_unset,989            response_model_exclude_defaults=self.response_model_exclude_defaults,990            response_model_exclude_none=self.response_model_exclude_none,991            dependency_overrides_provider=self.dependency_overrides_provider,992            embed_body_fields=self._embed_body_fields,993            strict_content_type=self.strict_content_type,994            stream_item_field=self.stream_item_field,995            is_json_stream=self.is_json_stream,996        )997998    def matches(self, scope: Scope) -> tuple[Match, Scope]:999        match, child_scope = super().matches(scope)1000        if match != Match.NONE:1001            child_scope["route"] = self1002        return match, child_scope100310041005class APIRouter(routing.Router):1006    """1007    `APIRouter` class, used to group *path operations*, for example to structure1008    an app in multiple files. It would then be included in the `FastAPI` app, or1009    in another `APIRouter` (ultimately included in the app).10101011    Read more about it in the1012    [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/).10131014    ## Example10151016    ```python1017    from fastapi import APIRouter, FastAPI10181019    app = FastAPI()1020    router = APIRouter()102110221023    @router.get("/users/", tags=["users"])1024    async def read_users():1025        return [{"username": "Rick"}, {"username": "Morty"}]102610271028    app.include_router(router)1029    ```1030    """10311032    def __init__(1033        self,1034        *,1035        prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "",1036        tags: Annotated[1037            list[str | Enum] | None,1038            Doc(1039                """1040                A list of tags to be applied to all the *path operations* in this1041                router.10421043                It will be added to the generated OpenAPI (e.g. visible at `/docs`).10441045                Read more about it in the1046                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).1047                """1048            ),1049        ] = None,1050        dependencies: Annotated[1051            Sequence[params.Depends] | None,1052            Doc(1053                """1054                A list of dependencies (using `Depends()`) to be applied to all the1055                *path operations* in this router.10561057                Read more about it in the1058                [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).1059                """1060            ),1061        ] = None,1062        default_response_class: Annotated[1063            type[Response],1064            Doc(1065                """1066                The default response class to be used.10671068                Read more in the1069                [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).1070                """1071            ),1072        ] = Default(JSONResponse),1073        responses: Annotated[1074            dict[int | str, dict[str, Any]] | None,1075            Doc(1076                """1077                Additional responses to be shown in OpenAPI.10781079                It will be added to the generated OpenAPI (e.g. visible at `/docs`).10801081                Read more about it in the1082                [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).10831084                And in the1085                [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).1086                """1087            ),1088        ] = None,1089        callbacks: Annotated[1090            list[BaseRoute] | None,1091            Doc(1092                """1093                OpenAPI callbacks that should apply to all *path operations* in this1094                router.10951096                It will be added to the generated OpenAPI (e.g. visible at `/docs`).10971098                Read more about it in the1099                [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).1100                """1101            ),1102        ] = None,1103        routes: Annotated[1104            list[BaseRoute] | None,1105            Doc(1106                """1107                **Note**: you probably shouldn't use this parameter, it is inherited1108                from Starlette and supported for compatibility.11091110                ---11111112                A list of routes to serve incoming HTTP and WebSocket requests.1113                """1114            ),1115            deprecated(1116                """1117                You normally wouldn't use this parameter with FastAPI, it is inherited1118                from Starlette and supported for compatibility.11191120                In FastAPI, you normally would use the *path operation methods*,1121                like `router.get()`, `router.post()`, etc.1122                """1123            ),1124        ] = None,1125        redirect_slashes: Annotated[1126            bool,1127            Doc(1128                """1129                Whether to detect and redirect slashes in URLs when the client doesn't1130                use the same format.1131                """1132            ),1133        ] = True,1134        default: Annotated[1135            ASGIApp | None,1136            Doc(1137                """1138                Default function handler for this router. Used to handle1139                404 Not Found errors.1140                """1141            ),1142        ] = None,1143        dependency_overrides_provider: Annotated[1144            Any | None,1145            Doc(1146                """1147                Only used internally by FastAPI to handle dependency overrides.11481149                You shouldn't need to use it. It normally points to the `FastAPI` app1150                object.1151                """1152            ),1153        ] = None,1154        route_class: Annotated[1155            type[APIRoute],1156            Doc(1157                """1158                Custom route (*path operation*) class to be used by this router.11591160                Read more about it in the1161                [FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router).1162                """1163            ),1164        ] = APIRoute,1165        on_startup: Annotated[1166            Sequence[Callable[[], Any]] | None,1167            Doc(1168                """1169                A list of startup event handler functions.11701171                You should instead use the `lifespan` handlers.11721173                Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).1174                """1175            ),1176        ] = None,1177        on_shutdown: Annotated[1178            Sequence[Callable[[], Any]] | None,1179            Doc(1180                """1181                A list of shutdown event handler functions.11821183                You should instead use the `lifespan` handlers.11841185                Read more in the1186                [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).1187                """1188            ),1189        ] = None,1190        # the generic to Lifespan[AppType] is the type of the top level application1191        # which the router cannot know statically, so we use typing.Any1192        lifespan: Annotated[1193            Lifespan[Any] | None,1194            Doc(1195                """1196                A `Lifespan` context manager handler. This replaces `startup` and1197                `shutdown` functions with a single context manager.11981199                Read more in the1200                [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/).1201                """1202            ),1203        ] = None,1204        deprecated: Annotated[1205            bool | None,1206            Doc(1207                """1208                Mark all *path operations* in this router as deprecated.12091210                It will be added to the generated OpenAPI (e.g. visible at `/docs`).12111212                Read more about it in the1213                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).1214                """1215            ),1216        ] = None,1217        include_in_schema: Annotated[1218            bool,1219            Doc(1220                """1221                To include (or not) all the *path operations* in this router in the1222                generated OpenAPI.12231224                This affects the generated OpenAPI (e.g. visible at `/docs`).12251226                Read more about it in the1227                [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi).1228                """1229            ),1230        ] = True,1231        generate_unique_id_function: Annotated[1232            Callable[[APIRoute], str],1233            Doc(1234                """1235                Customize the function used to generate unique IDs for the *path1236                operations* shown in the generated OpenAPI.12371238                This is particularly useful when automatically generating clients or1239                SDKs for your API.12401241                Read more about it in the1242                [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).1243                """1244            ),1245        ] = Default(generate_unique_id),1246        strict_content_type: Annotated[1247            bool,1248            Doc(1249                """1250                Enable strict checking for request Content-Type headers.12511252                When `True` (the default), requests with a body that do not include1253                a `Content-Type` header will **not** be parsed as JSON.12541255                This prevents potential cross-site request forgery (CSRF) attacks1256                that exploit the browser's ability to send requests without a1257                Content-Type header, bypassing CORS preflight checks. In particular1258                applicable for apps that need to be run locally (in localhost).12591260                When `False`, requests without a `Content-Type` header will have1261                their body parsed as JSON, which maintains compatibility with1262                certain clients that don't send `Content-Type` headers.12631264                Read more about it in the1265                [FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/).1266                """1267            ),1268        ] = Default(True),1269    ) -> None:1270        # Determine the lifespan context to use1271        if lifespan is None:1272            # Use the default lifespan that runs on_startup/on_shutdown handlers1273            lifespan_context: Lifespan[Any] = _DefaultLifespan(self)1274        elif inspect.isasyncgenfunction(lifespan):1275            lifespan_context = asynccontextmanager(lifespan)1276        elif inspect.isgeneratorfunction(lifespan):1277            lifespan_context = _wrap_gen_lifespan_context(lifespan)1278        else:1279            lifespan_context = lifespan1280        self.lifespan_context = lifespan_context12811282        super().__init__(1283            routes=routes,1284            redirect_slashes=redirect_slashes,1285            default=default,1286            lifespan=lifespan_context,1287        )1288        if prefix:1289            assert prefix.startswith("/"), "A path prefix must start with '/'"1290            assert not prefix.endswith("/"), (1291                "A path prefix must not end with '/', as the routes will start with '/'"1292            )12931294        # Handle on_startup/on_shutdown locally since Starlette removed support1295        # Ref: https://github.com/Kludex/starlette/pull/31171296        # TODO: deprecate this once the lifespan (or alternative) interface is improved1297        self.on_startup: list[Callable[[], Any]] = (1298            [] if on_startup is None else list(on_startup)1299        )1300        self.on_shutdown: list[Callable[[], Any]] = (1301            [] if on_shutdown is None else list(on_shutdown)1302        )13031304        self.prefix = prefix1305        self.tags: list[str | Enum] = tags or []1306        self.dependencies = list(dependencies or [])1307        self.deprecated = deprecated1308        self.include_in_schema = include_in_schema1309        self.responses = responses or {}1310        self.callbacks = callbacks or []1311        self.dependency_overrides_provider = dependency_overrides_provider1312        self.route_class = route_class1313        self.default_response_class = default_response_class1314        self.generate_unique_id_function = generate_unique_id_function1315        self.strict_content_type = strict_content_type13161317    def route(1318        self,1319        path: str,1320        methods: Collection[str] | None = None,1321        name: str | None = None,1322        include_in_schema: bool = True,1323    ) -> Callable[[DecoratedCallable], DecoratedCallable]:1324        def decorator(func: DecoratedCallable) -> DecoratedCallable:1325            self.add_route(1326                path,1327                func,1328                methods=methods,1329                name=name,1330                include_in_schema=include_in_schema,1331            )1332            return func13331334        return decorator13351336    def add_api_route(1337        self,1338        path: str,1339        endpoint: Callable[..., Any],1340        *,1341        response_model: Any = Default(None),1342        status_code: int | None = None,1343        tags: list[str | Enum] | None = None,1344        dependencies: Sequence[params.Depends] | None = None,1345        summary: str | None = None,1346        description: str | None = None,1347        response_description: str = "Successful Response",1348        responses: dict[int | str, dict[str, Any]] | None = None,1349        deprecated: bool | None = None,1350        methods: set[str] | list[str] | None = None,1351        operation_id: str | None = None,1352        response_model_include: IncEx | None = None,1353        response_model_exclude: IncEx | None = None,1354        response_model_by_alias: bool = True,1355        response_model_exclude_unset: bool = False,1356        response_model_exclude_defaults: bool = False,1357        response_model_exclude_none: bool = False,1358        include_in_schema: bool = True,1359        response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse),1360        name: str | None = None,1361        route_class_override: type[APIRoute] | None = None,1362        callbacks: list[BaseRoute] | None = None,1363        openapi_extra: dict[str, Any] | None = None,1364        generate_unique_id_function: Callable[[APIRoute], str]1365        | DefaultPlaceholder = Default(generate_unique_id),1366        strict_content_type: bool | DefaultPlaceholder = Default(True),1367    ) -> None:1368        route_class = route_class_override or self.route_class1369        responses = responses or {}1370        combined_responses = {**self.responses, **responses}1371        current_response_class = get_value_or_default(1372            response_class, self.default_response_class1373        )1374        current_tags = self.tags.copy()1375        if tags:1376            current_tags.extend(tags)1377        current_dependencies = self.dependencies.copy()1378        if dependencies:1379            current_dependencies.extend(dependencies)1380        current_callbacks = self.callbacks.copy()1381        if callbacks:1382            current_callbacks.extend(callbacks)1383        current_generate_unique_id = get_value_or_default(1384            generate_unique_id_function, self.generate_unique_id_function1385        )1386        route = route_class(1387            self.prefix + path,1388            endpoint=endpoint,1389            response_model=response_model,1390            status_code=status_code,1391            tags=current_tags,1392            dependencies=current_dependencies,1393            summary=summary,1394            description=description,1395            response_description=response_description,1396            responses=combined_responses,1397            deprecated=deprecated or self.deprecated,1398            methods=methods,1399            operation_id=operation_id,1400            response_model_include=response_model_include,1401            response_model_exclude=response_model_exclude,1402            response_model_by_alias=response_model_by_alias,1403            response_model_exclude_unset=response_model_exclude_unset,1404            response_model_exclude_defaults=response_model_exclude_defaults,1405            response_model_exclude_none=response_model_exclude_none,1406            include_in_schema=include_in_schema and self.include_in_schema,1407            response_class=current_response_class,1408            name=name,1409            dependency_overrides_provider=self.dependency_overrides_provider,1410            callbacks=current_callbacks,1411            openapi_extra=openapi_extra,1412            generate_unique_id_function=current_generate_unique_id,1413            strict_content_type=get_value_or_default(1414                strict_content_type, self.strict_content_type1415            ),1416        )1417        self.routes.append(route)14181419    def api_route(1420        self,1421        path: str,1422        *,1423        response_model: Any = Default(None),1424        status_code: int | None = None,1425        tags: list[str | Enum] | None = None,1426        dependencies: Sequence[params.Depends] | None = None,1427        summary: str | None = None,1428        description: str | None = None,1429        response_description: str = "Successful Response",1430        responses: dict[int | str, dict[str, Any]] | None = None,1431        deprecated: bool | None = None,1432        methods: list[str] | None = None,1433        operation_id: str | None = None,1434        response_model_include: IncEx | None = None,1435        response_model_exclude: IncEx | None = None,1436        response_model_by_alias: bool = True,1437        response_model_exclude_unset: bool = False,1438        response_model_exclude_defaults: bool = False,1439        response_model_exclude_none: bool = False,1440        include_in_schema: bool = True,1441        response_class: type[Response] = Default(JSONResponse),1442        name: str | None = None,1443        callbacks: list[BaseRoute] | None = None,1444        openapi_extra: dict[str, Any] | None = None,1445        generate_unique_id_function: Callable[[APIRoute], str] = Default(1446            generate_unique_id1447        ),1448    ) -> Callable[[DecoratedCallable], DecoratedCallable]:1449        def decorator(func: DecoratedCallable) -> DecoratedCallable:1450            self.add_api_route(1451                path,1452                func,1453                response_model=response_model,1454                status_code=status_code,1455                tags=tags,1456                dependencies=dependencies,1457                summary=summary,1458                description=description,1459                response_description=response_description,1460                responses=responses,1461                deprecated=deprecated,1462                methods=methods,1463                operation_id=operation_id,1464                response_model_include=response_model_include,1465                response_model_exclude=response_model_exclude,1466                response_model_by_alias=response_model_by_alias,1467                response_model_exclude_unset=response_model_exclude_unset,1468                response_model_exclude_defaults=response_model_exclude_defaults,1469                response_model_exclude_none=response_model_exclude_none,1470                include_in_schema=include_in_schema,1471                response_class=response_class,1472                name=name,1473                callbacks=callbacks,1474                openapi_extra=openapi_extra,1475                generate_unique_id_function=generate_unique_id_function,1476            )1477            return func14781479        return decorator14801481    def add_api_websocket_route(1482        self,1483        path: str,1484        endpoint: Callable[..., Any],1485        name: str | None = None,1486        *,1487        dependencies: Sequence[params.Depends] | None = None,1488    ) -> None:1489        current_dependencies = self.dependencies.copy()1490        if dependencies:1491            current_dependencies.extend(dependencies)14921493        route = APIWebSocketRoute(1494            self.prefix + path,1495            endpoint=endpoint,1496            name=name,1497            dependencies=current_dependencies,1498            dependency_overrides_provider=self.dependency_overrides_provider,1499        )1500        self.routes.append(route)15011502    def websocket(1503        self,1504        path: Annotated[1505            str,1506            Doc(1507                """1508                WebSocket path.1509                """1510            ),1511        ],1512        name: Annotated[1513            str | None,1514            Doc(1515                """1516                A name for the WebSocket. Only used internally.1517                """1518            ),1519        ] = None,1520        *,1521        dependencies: Annotated[1522            Sequence[params.Depends] | None,1523            Doc(1524                """1525                A list of dependencies (using `Depends()`) to be used for this1526                WebSocket.15271528                Read more about it in the1529                [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).1530                """1531            ),1532        ] = None,1533    ) -> Callable[[DecoratedCallable], DecoratedCallable]:1534        """1535        Decorate a WebSocket function.15361537        Read more about it in the1538        [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/).15391540        **Example**15411542        ## Example15431544        ```python1545        from fastapi import APIRouter, FastAPI, WebSocket15461547        app = FastAPI()1548        router = APIRouter()15491550        @router.websocket("/ws")1551        async def websocket_endpoint(websocket: WebSocket):1552            await websocket.accept()1553            while True:1554                data = await websocket.receive_text()1555                await websocket.send_text(f"Message text was: {data}")15561557        app.include_router(router)1558        ```1559        """15601561        def decorator(func: DecoratedCallable) -> DecoratedCallable:1562            self.add_api_websocket_route(1563                path, func, name=name, dependencies=dependencies1564            )1565            return func15661567        return decorator15681569    def websocket_route(1570        self, path: str, name: str | None = None1571    ) -> Callable[[DecoratedCallable], DecoratedCallable]:1572        def decorator(func: DecoratedCallable) -> DecoratedCallable:1573            self.add_websocket_route(path, func, name=name)1574            return func15751576        return decorator15771578    def include_router(1579        self,1580        router: Annotated["APIRouter", Doc("The `APIRouter` to include.")],1581        *,1582        prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "",1583        tags: Annotated[1584            list[str | Enum] | None,1585            Doc(1586                """1587                A list of tags to be applied to all the *path operations* in this1588                router.15891590                It will be added to the generated OpenAPI (e.g. visible at `/docs`).15911592                Read more about it in the1593                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).1594                """1595            ),1596        ] = None,1597        dependencies: Annotated[1598            Sequence[params.Depends] | None,1599            Doc(1600                """1601                A list of dependencies (using `Depends()`) to be applied to all the1602                *path operations* in this router.16031604                Read more about it in the1605                [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).1606                """1607            ),1608        ] = None,1609        default_response_class: Annotated[1610            type[Response],1611            Doc(1612                """1613                The default response class to be used.16141615                Read more in the1616                [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class).1617                """1618            ),1619        ] = Default(JSONResponse),1620        responses: Annotated[1621            dict[int | str, dict[str, Any]] | None,1622            Doc(1623                """1624                Additional responses to be shown in OpenAPI.16251626                It will be added to the generated OpenAPI (e.g. visible at `/docs`).16271628                Read more about it in the1629                [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/).16301631                And in the1632                [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies).1633                """1634            ),1635        ] = None,1636        callbacks: Annotated[1637            list[BaseRoute] | None,1638            Doc(1639                """1640                OpenAPI callbacks that should apply to all *path operations* in this1641                router.16421643                It will be added to the generated OpenAPI (e.g. visible at `/docs`).16441645                Read more about it in the1646                [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/).1647                """1648            ),1649        ] = None,1650        deprecated: Annotated[1651            bool | None,1652            Doc(1653                """1654                Mark all *path operations* in this router as deprecated.16551656                It will be added to the generated OpenAPI (e.g. visible at `/docs`).16571658                Read more about it in the1659                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).1660                """1661            ),1662        ] = None,1663        include_in_schema: Annotated[1664            bool,1665            Doc(1666                """1667                Include (or not) all the *path operations* in this router in the1668                generated OpenAPI schema.16691670                This affects the generated OpenAPI (e.g. visible at `/docs`).1671                """1672            ),1673        ] = True,1674        generate_unique_id_function: Annotated[1675            Callable[[APIRoute], str],1676            Doc(1677                """1678                Customize the function used to generate unique IDs for the *path1679                operations* shown in the generated OpenAPI.16801681                This is particularly useful when automatically generating clients or1682                SDKs for your API.16831684                Read more about it in the1685                [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).1686                """1687            ),1688        ] = Default(generate_unique_id),1689    ) -> None:1690        """1691        Include another `APIRouter` in the same current `APIRouter`.16921693        Read more about it in the1694        [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/).16951696        ## Example16971698        ```python1699        from fastapi import APIRouter, FastAPI17001701        app = FastAPI()1702        internal_router = APIRouter()1703        users_router = APIRouter()17041705        @users_router.get("/users/")1706        def read_users():1707            return [{"name": "Rick"}, {"name": "Morty"}]17081709        internal_router.include_router(users_router)1710        app.include_router(internal_router)1711        ```1712        """1713        assert self is not router, (1714            "Cannot include the same APIRouter instance into itself. "1715            "Did you mean to include a different router?"1716        )1717        if prefix:1718            assert prefix.startswith("/"), "A path prefix must start with '/'"1719            assert not prefix.endswith("/"), (1720                "A path prefix must not end with '/', as the routes will start with '/'"1721            )1722        else:1723            for r in router.routes:1724                path = getattr(r, "path")  # noqa: B0091725                name = getattr(r, "name", "unknown")1726                if path is not None and not path:1727                    raise FastAPIError(1728                        f"Prefix and path cannot be both empty (path operation: {name})"1729                    )1730        if responses is None:1731            responses = {}1732        for route in router.routes:1733            if isinstance(route, APIRoute):1734                combined_responses = {**responses, **route.responses}1735                use_response_class = get_value_or_default(1736                    route.response_class,1737                    router.default_response_class,1738                    default_response_class,1739                    self.default_response_class,1740                )1741                current_tags = []1742                if tags:1743                    current_tags.extend(tags)1744                if route.tags:1745                    current_tags.extend(route.tags)1746                current_dependencies: list[params.Depends] = []1747                if dependencies:1748                    current_dependencies.extend(dependencies)1749                if route.dependencies:1750                    current_dependencies.extend(route.dependencies)1751                current_callbacks = []1752                if callbacks:1753                    current_callbacks.extend(callbacks)1754                if route.callbacks:1755                    current_callbacks.extend(route.callbacks)1756                current_generate_unique_id = get_value_or_default(1757                    route.generate_unique_id_function,1758                    router.generate_unique_id_function,1759                    generate_unique_id_function,1760                    self.generate_unique_id_function,1761                )1762                self.add_api_route(1763                    prefix + route.path,1764                    route.endpoint,1765                    response_model=route.response_model,1766                    status_code=route.status_code,1767                    tags=current_tags,1768                    dependencies=current_dependencies,1769                    summary=route.summary,1770                    description=route.description,1771                    response_description=route.response_description,1772                    responses=combined_responses,1773                    deprecated=route.deprecated or deprecated or self.deprecated,1774                    methods=route.methods,1775                    operation_id=route.operation_id,1776                    response_model_include=route.response_model_include,1777                    response_model_exclude=route.response_model_exclude,1778                    response_model_by_alias=route.response_model_by_alias,1779                    response_model_exclude_unset=route.response_model_exclude_unset,1780                    response_model_exclude_defaults=route.response_model_exclude_defaults,1781                    response_model_exclude_none=route.response_model_exclude_none,1782                    include_in_schema=route.include_in_schema1783                    and self.include_in_schema1784                    and include_in_schema,1785                    response_class=use_response_class,1786                    name=route.name,1787                    route_class_override=type(route),1788                    callbacks=current_callbacks,1789                    openapi_extra=route.openapi_extra,1790                    generate_unique_id_function=current_generate_unique_id,1791                    strict_content_type=get_value_or_default(1792                        route.strict_content_type,1793                        router.strict_content_type,1794                        self.strict_content_type,1795                    ),1796                )1797            elif isinstance(route, routing.Route):1798                methods = list(route.methods or [])1799                self.add_route(1800                    prefix + route.path,1801                    route.endpoint,1802                    methods=methods,1803                    include_in_schema=route.include_in_schema,1804                    name=route.name,1805                )1806            elif isinstance(route, APIWebSocketRoute):1807                current_dependencies = []1808                if dependencies:1809                    current_dependencies.extend(dependencies)1810                if route.dependencies:1811                    current_dependencies.extend(route.dependencies)1812                self.add_api_websocket_route(1813                    prefix + route.path,1814                    route.endpoint,1815                    dependencies=current_dependencies,1816                    name=route.name,1817                )1818            elif isinstance(route, routing.WebSocketRoute):1819                self.add_websocket_route(1820                    prefix + route.path, route.endpoint, name=route.name1821                )1822        for handler in router.on_startup:1823            self.add_event_handler("startup", handler)1824        for handler in router.on_shutdown:1825            self.add_event_handler("shutdown", handler)1826        self.lifespan_context = _merge_lifespan_context(1827            self.lifespan_context,1828            router.lifespan_context,1829        )18301831    def get(1832        self,1833        path: Annotated[1834            str,1835            Doc(1836                """1837                The URL path to be used for this *path operation*.18381839                For example, in `http://example.com/items`, the path is `/items`.1840                """1841            ),1842        ],1843        *,1844        response_model: Annotated[1845            Any,1846            Doc(1847                """1848                The type to use for the response.18491850                It could be any valid Pydantic *field* type. So, it doesn't have to1851                be a Pydantic model, it could be other things, like a `list`, `dict`,1852                etc.18531854                It will be used for:18551856                * Documentation: the generated OpenAPI (and the UI at `/docs`) will1857                    show it as the response (JSON Schema).1858                * Serialization: you could return an arbitrary object and the1859                    `response_model` would be used to serialize that object into the1860                    corresponding JSON.1861                * Filtering: the JSON sent to the client will only contain the data1862                    (fields) defined in the `response_model`. If you returned an object1863                    that contains an attribute `password` but the `response_model` does1864                    not include that field, the JSON sent to the client would not have1865                    that `password`.1866                * Validation: whatever you return will be serialized with the1867                    `response_model`, converting any data as necessary to generate the1868                    corresponding JSON. But if the data in the object returned is not1869                    valid, that would mean a violation of the contract with the client,1870                    so it's an error from the API developer. So, FastAPI will raise an1871                    error and return a 500 error code (Internal Server Error).18721873                Read more about it in the1874                [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).1875                """1876            ),1877        ] = Default(None),1878        status_code: Annotated[1879            int | None,1880            Doc(1881                """1882                The default status code to be used for the response.18831884                You could override the status code by returning a response directly.18851886                Read more about it in the1887                [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/).1888                """1889            ),1890        ] = None,1891        tags: Annotated[1892            list[str | Enum] | None,1893            Doc(1894                """1895                A list of tags to be applied to the *path operation*.18961897                It will be added to the generated OpenAPI (e.g. visible at `/docs`).18981899                Read more about it in the1900                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags).1901                """1902            ),1903        ] = None,1904        dependencies: Annotated[1905            Sequence[params.Depends] | None,1906            Doc(1907                """1908                A list of dependencies (using `Depends()`) to be applied to the1909                *path operation*.19101911                Read more about it in the1912                [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/).1913                """1914            ),1915        ] = None,1916        summary: Annotated[1917            str | None,1918            Doc(1919                """1920                A summary for the *path operation*.19211922                It will be added to the generated OpenAPI (e.g. visible at `/docs`).19231924                Read more about it in the1925                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).1926                """1927            ),1928        ] = None,1929        description: Annotated[1930            str | None,1931            Doc(1932                """1933                A description for the *path operation*.19341935                If not provided, it will be extracted automatically from the docstring1936                of the *path operation function*.19371938                It can contain Markdown.19391940                It will be added to the generated OpenAPI (e.g. visible at `/docs`).19411942                Read more about it in the1943                [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/).1944                """1945            ),1946        ] = None,1947        response_description: Annotated[1948            str,1949            Doc(1950                """1951                The description for the default response.19521953                It will be added to the generated OpenAPI (e.g. visible at `/docs`).1954                """1955            ),1956        ] = "Successful Response",1957        responses: Annotated[1958            dict[int | str, dict[str, Any]] | None,1959            Doc(1960                """1961                Additional responses that could be returned by this *path operation*.19621963                It will be added to the generated OpenAPI (e.g. visible at `/docs`).1964                """1965            ),1966        ] = None,1967        deprecated: Annotated[1968            bool | None,1969            Doc(1970                """1971                Mark this *path operation* as deprecated.19721973                It will be added to the generated OpenAPI (e.g. visible at `/docs`).1974                """1975            ),1976        ] = None,1977        operation_id: Annotated[1978            str | None,1979            Doc(1980                """1981                Custom operation ID to be used by this *path operation*.19821983                By default, it is generated automatically.19841985                If you provide a custom operation ID, you need to make sure it is1986                unique for the whole API.19871988                You can customize the1989                operation ID generation with the parameter1990                `generate_unique_id_function` in the `FastAPI` class.19911992                Read more about it in the1993                [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function).1994                """1995            ),1996        ] = None,1997        response_model_include: Annotated[1998            IncEx | None,1999            Doc(2000                """

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.