Source code for litestar.handlers.asgi_handlers

from __future__ import annotations

import warnings
from typing import TYPE_CHECKING, Any, Callable

from litestar.exceptions import ImproperlyConfiguredException
from litestar.handlers.base import BaseRouteHandler
from litestar.types.builtin_types import NoneType
from litestar.utils.predicates import is_async_callable

__all__ = ("ASGIRouteHandler", "asgi")


if TYPE_CHECKING:
    from collections.abc import Mapping, Sequence

    from litestar import Litestar, Router
    from litestar.connection import ASGIConnection
    from litestar.routes import BaseRoute
    from litestar.types import (
        AsyncAnyCallable,
        ExceptionHandlersMap,
        Guard,
        ParametersMap,
    )


[docs] class ASGIRouteHandler(BaseRouteHandler): __slots__ = ( "copy_scope", "is_mount", )
[docs] def __init__( self, path: str | Sequence[str] | None = None, *, fn: AsyncAnyCallable, exception_handlers: ExceptionHandlersMap | None = None, guards: Sequence[Guard] | None = None, name: str | None = None, opt: Mapping[str, Any] | None = None, is_mount: bool = False, signature_namespace: Mapping[str, Any] | None = None, copy_scope: bool | None = None, parameters: ParametersMap | None = None, **kwargs: Any, ) -> None: """Route handler for ASGI routes. Args: path: A path fragment for the route handler function or a list of path fragments. If not given defaults to ``/``. fn: The handler function. .. versionadded:: 3.0 exception_handlers: A mapping of status codes and/or exception types to handler functions. guards: A sequence of :class:`Guard <.types.Guard>` callables. name: A string identifying the route handler. opt: A string key mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. is_mount: A boolean dictating whether the handler's paths should be regarded as mount paths. Mount path accept any arbitrary paths that begin with the defined prefixed path. For example, a mount with the path ``/some-path/`` will accept requests for ``/some-path/`` and any sub path under this, e.g. ``/some-path/sub-path/`` etc. signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. type_encoders: A mapping of types to callables that transform them into types supported for serialization. copy_scope: Copy the ASGI 'scope' before calling the mounted application. Should be set to 'True' unless side effects via scope mutations by the mounted ASGI application are intentional parameters: A mapping of :func:`Parameter <.params.Parameter>` definitions **kwargs: Any additional kwarg - will be set in the opt dictionary. """ self.is_mount = is_mount self.copy_scope = copy_scope super().__init__( path, fn=fn, exception_handlers=exception_handlers, guards=guards, name=name, opt=opt, signature_namespace=signature_namespace, parameters=parameters, **kwargs, )
[docs] def on_registration(self, route: BaseRoute, app: Litestar) -> None: super().on_registration(app=app, route=route) if self.copy_scope is None: warnings.warn( f"{self}: 'copy_scope' not set for ASGI handler. Leaving 'copy_scope' unset will warn about mounted " "ASGI applications modifying the scope. Set 'copy_scope=True' to ensure calling into mounted ASGI apps " "does not cause any side effects via scope mutations, or set 'copy_scope=False' if those mutations are " "desired. 'copy'scope' will default to 'True' in Litestar 3", category=DeprecationWarning, stacklevel=1, )
def _get_merge_opts(self, others: tuple[Router, ...]) -> dict[str, Any]: merge_opts = super()._get_merge_opts(others) merge_opts["is_mount"] = self.is_mount merge_opts["copy_scope"] = self.copy_scope return merge_opts def _validate_handler_function(self) -> None: """Validate the route handler function once it's set by inspecting its return annotations.""" super()._validate_handler_function() if not self.parsed_fn_signature.return_type.is_subclass_of(NoneType): raise ImproperlyConfiguredException("ASGI handler functions should return 'None'") if any(key not in self.parsed_fn_signature.parameters for key in ("scope", "send", "receive")): raise ImproperlyConfiguredException( "ASGI handler functions should define 'scope', 'send' and 'receive' arguments" ) if not is_async_callable(self.fn): raise ImproperlyConfiguredException("Functions decorated with 'asgi' must be async functions")
[docs] async def handle(self, connection: ASGIConnection[ASGIRouteHandler, Any, Any, Any]) -> None: """ASGI app that authorizes the connection and then awaits the handler function. .. versionadded: 3.0 Args: connection: The ASGI connection Returns: None """ if self.guards: await self.authorize_connection(connection=connection) await self.fn(scope=connection.scope, receive=connection.receive, send=connection.send)
[docs] def asgi( path: str | Sequence[str] | None = None, *, exception_handlers: ExceptionHandlersMap | None = None, guards: Sequence[Guard] | None = None, name: str | None = None, opt: Mapping[str, Any] | None = None, is_mount: bool = False, signature_namespace: Mapping[str, Any] | None = None, handler_class: type[ASGIRouteHandler] = ASGIRouteHandler, **kwargs: Any, ) -> Callable[[AsyncAnyCallable], ASGIRouteHandler]: """Create an :class:`ASGIRouteHandler`. Args: path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/`` exception_handlers: A mapping of status codes and/or exception types to handler functions. guards: A sequence of :class:`Guard <.types.Guard>` callables. name: A string identifying the route handler. opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. is_mount: A boolean dictating whether the handler's paths should be regarded as mount paths. Mount path accept any arbitrary paths that begin with the defined prefixed path. For example, a mount with the path ``/some-path/`` will accept requests for ``/some-path/`` and any sub path under this, e.g. ``/some-path/sub-path/`` etc. handler_class: Route handler class instantiated by the decorator **kwargs: Any additional kwarg - will be set in the opt dictionary. """ def decorator(fn: AsyncAnyCallable) -> ASGIRouteHandler: return handler_class( fn=fn, path=path, exception_handlers=exception_handlers, guards=guards, name=name, opt=opt, is_mount=is_mount, signature_namespace=signature_namespace, **kwargs, ) return decorator