diff options
Diffstat (limited to 'venv/lib/python3.11/site-packages/litestar/handlers')
22 files changed, 3305 insertions, 0 deletions
| diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/__init__.py b/venv/lib/python3.11/site-packages/litestar/handlers/__init__.py new file mode 100644 index 0000000..822fe7e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/__init__.py @@ -0,0 +1,29 @@ +from .asgi_handlers import ASGIRouteHandler, asgi +from .base import BaseRouteHandler +from .http_handlers import HTTPRouteHandler, delete, get, head, patch, post, put, route +from .websocket_handlers import ( +    WebsocketListener, +    WebsocketListenerRouteHandler, +    WebsocketRouteHandler, +    websocket, +    websocket_listener, +) + +__all__ = ( +    "ASGIRouteHandler", +    "BaseRouteHandler", +    "HTTPRouteHandler", +    "WebsocketListener", +    "WebsocketRouteHandler", +    "WebsocketListenerRouteHandler", +    "asgi", +    "delete", +    "get", +    "head", +    "patch", +    "post", +    "put", +    "route", +    "websocket", +    "websocket_listener", +) diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/__init__.cpython-311.pycBinary files differ new file mode 100644 index 0000000..6976d76 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/asgi_handlers.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/asgi_handlers.cpython-311.pycBinary files differ new file mode 100644 index 0000000..85de8f8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/asgi_handlers.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/base.cpython-311.pycBinary files differ new file mode 100644 index 0000000..2331fc7 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/__pycache__/base.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/asgi_handlers.py b/venv/lib/python3.11/site-packages/litestar/handlers/asgi_handlers.py new file mode 100644 index 0000000..91f3517 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/asgi_handlers.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Mapping, Sequence + +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 litestar.types import ( +        ExceptionHandlersMap, +        Guard, +        MaybePartial,  # noqa: F401 +    ) + + +class ASGIRouteHandler(BaseRouteHandler): +    """ASGI Route Handler decorator. + +    Use this decorator to decorate ASGI applications. +    """ + +    __slots__ = ("is_mount", "is_static") + +    def __init__( +        self, +        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, +        is_static: bool = False, +        signature_namespace: Mapping[str, Any] | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``ASGIRouteHandler``. + +        Args: +            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>`. +            path: A path fragment for the route handler function or a list of path fragments. If not given defaults to +                ``/`` +            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. +            is_static: A boolean dictating whether the handler's paths should be regarded as static paths. Static paths +                are used to deliver static files. +            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. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        self.is_mount = is_mount or is_static +        self.is_static = is_static +        super().__init__( +            path, +            exception_handlers=exception_handlers, +            guards=guards, +            name=name, +            opt=opt, +            signature_namespace=signature_namespace, +            **kwargs, +        ) + +    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") + + +asgi = ASGIRouteHandler diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/base.py b/venv/lib/python3.11/site-packages/litestar/handlers/base.py new file mode 100644 index 0000000..9dbb70e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/base.py @@ -0,0 +1,577 @@ +from __future__ import annotations + +from copy import copy +from functools import partial +from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence, cast + +from litestar._signature import SignatureModel +from litestar.config.app import ExperimentalFeatures +from litestar.di import Provide +from litestar.dto import DTOData +from litestar.exceptions import ImproperlyConfiguredException +from litestar.plugins import DIPlugin, PluginRegistry +from litestar.serialization import default_deserializer, default_serializer +from litestar.types import ( +    Dependencies, +    Empty, +    ExceptionHandlersMap, +    Guard, +    Middleware, +    TypeDecodersSequence, +    TypeEncodersMap, +) +from litestar.typing import FieldDefinition +from litestar.utils import ensure_async_callable, get_name, normalize_path +from litestar.utils.helpers import unwrap_partial +from litestar.utils.signature import ParsedSignature, add_types_to_signature_namespace + +if TYPE_CHECKING: +    from typing_extensions import Self + +    from litestar.app import Litestar +    from litestar.connection import ASGIConnection +    from litestar.controller import Controller +    from litestar.dto import AbstractDTO +    from litestar.dto._backend import DTOBackend +    from litestar.params import ParameterKwarg +    from litestar.router import Router +    from litestar.types import AnyCallable, AsyncAnyCallable, ExceptionHandler +    from litestar.types.empty import EmptyType + +__all__ = ("BaseRouteHandler",) + + +class BaseRouteHandler: +    """Base route handler. + +    Serves as a subclass for all route handlers +    """ + +    __slots__ = ( +        "_fn", +        "_parsed_data_field", +        "_parsed_fn_signature", +        "_parsed_return_field", +        "_resolved_data_dto", +        "_resolved_dependencies", +        "_resolved_guards", +        "_resolved_layered_parameters", +        "_resolved_return_dto", +        "_resolved_signature_namespace", +        "_resolved_type_decoders", +        "_resolved_type_encoders", +        "_signature_model", +        "dependencies", +        "dto", +        "exception_handlers", +        "guards", +        "middleware", +        "name", +        "opt", +        "owner", +        "paths", +        "return_dto", +        "signature_namespace", +        "type_decoders", +        "type_encoders", +    ) + +    def __init__( +        self, +        path: str | Sequence[str] | None = None, +        *, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        signature_types: Sequence[Any] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``HTTPRouteHandler``. + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults +                to ``/`` +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature +                modelling. +            signature_types: A sequence of types for use in forward reference resolution during signature modeling. +                These types will be added to the signature namespace using their ``__name__`` attribute. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        self._parsed_fn_signature: ParsedSignature | EmptyType = Empty +        self._parsed_return_field: FieldDefinition | EmptyType = Empty +        self._parsed_data_field: FieldDefinition | None | EmptyType = Empty +        self._resolved_data_dto: type[AbstractDTO] | None | EmptyType = Empty +        self._resolved_dependencies: dict[str, Provide] | EmptyType = Empty +        self._resolved_guards: list[Guard] | EmptyType = Empty +        self._resolved_layered_parameters: dict[str, FieldDefinition] | EmptyType = Empty +        self._resolved_return_dto: type[AbstractDTO] | None | EmptyType = Empty +        self._resolved_signature_namespace: dict[str, Any] | EmptyType = Empty +        self._resolved_type_decoders: TypeDecodersSequence | EmptyType = Empty +        self._resolved_type_encoders: TypeEncodersMap | EmptyType = Empty +        self._signature_model: type[SignatureModel] | EmptyType = Empty + +        self.dependencies = dependencies +        self.dto = dto +        self.exception_handlers = exception_handlers +        self.guards = guards +        self.middleware = middleware +        self.name = name +        self.opt = dict(opt or {}) +        self.opt.update(**kwargs) +        self.owner: Controller | Router | None = None +        self.return_dto = return_dto +        self.signature_namespace = add_types_to_signature_namespace( +            signature_types or [], dict(signature_namespace or {}) +        ) +        self.type_decoders = type_decoders +        self.type_encoders = type_encoders + +        self.paths = ( +            {normalize_path(p) for p in path} if path and isinstance(path, list) else {normalize_path(path or "/")}  # type: ignore[arg-type] +        ) + +    def __call__(self, fn: AsyncAnyCallable) -> Self: +        """Replace a function with itself.""" +        self._fn = fn +        return self + +    @property +    def handler_id(self) -> str: +        """A unique identifier used for generation of DTOs.""" +        return f"{self!s}::{sum(id(layer) for layer in self.ownership_layers)}" + +    @property +    def default_deserializer(self) -> Callable[[Any, Any], Any]: +        """Get a default deserializer for the route handler. + +        Returns: +            A default deserializer for the route handler. + +        """ +        return partial(default_deserializer, type_decoders=self.resolve_type_decoders()) + +    @property +    def default_serializer(self) -> Callable[[Any], Any]: +        """Get a default serializer for the route handler. + +        Returns: +            A default serializer for the route handler. + +        """ +        return partial(default_serializer, type_encoders=self.resolve_type_encoders()) + +    @property +    def signature_model(self) -> type[SignatureModel]: +        """Get the signature model for the route handler. + +        Returns: +            A signature model for the route handler. + +        """ +        if self._signature_model is Empty: +            self._signature_model = SignatureModel.create( +                dependency_name_set=self.dependency_name_set, +                fn=cast("AnyCallable", self.fn), +                parsed_signature=self.parsed_fn_signature, +                data_dto=self.resolve_data_dto(), +                type_decoders=self.resolve_type_decoders(), +            ) +        return self._signature_model + +    @property +    def fn(self) -> AsyncAnyCallable: +        """Get the handler function. + +        Raises: +            ImproperlyConfiguredException: if handler fn is not set. + +        Returns: +            Handler function +        """ +        if not hasattr(self, "_fn"): +            raise ImproperlyConfiguredException("No callable has been registered for this handler") +        return self._fn + +    @property +    def parsed_fn_signature(self) -> ParsedSignature: +        """Return the parsed signature of the handler function. + +        This method is memoized so the computation occurs only once. + +        Returns: +            A ParsedSignature instance +        """ +        if self._parsed_fn_signature is Empty: +            self._parsed_fn_signature = ParsedSignature.from_fn( +                unwrap_partial(self.fn), self.resolve_signature_namespace() +            ) + +        return self._parsed_fn_signature + +    @property +    def parsed_return_field(self) -> FieldDefinition: +        if self._parsed_return_field is Empty: +            self._parsed_return_field = self.parsed_fn_signature.return_type +        return self._parsed_return_field + +    @property +    def parsed_data_field(self) -> FieldDefinition | None: +        if self._parsed_data_field is Empty: +            self._parsed_data_field = self.parsed_fn_signature.parameters.get("data") +        return self._parsed_data_field + +    @property +    def handler_name(self) -> str: +        """Get the name of the handler function. + +        Raises: +            ImproperlyConfiguredException: if handler fn is not set. + +        Returns: +            Name of the handler function +        """ +        return get_name(unwrap_partial(self.fn)) + +    @property +    def dependency_name_set(self) -> set[str]: +        """Set of all dependency names provided in the handler's ownership layers.""" +        layered_dependencies = (layer.dependencies or {} for layer in self.ownership_layers) +        return {name for layer in layered_dependencies for name in layer}  # pyright: ignore + +    @property +    def ownership_layers(self) -> list[Self | Controller | Router]: +        """Return the handler layers from the app down to the route handler. + +        ``app -> ... -> route handler`` +        """ +        layers = [] + +        cur: Any = self +        while cur: +            layers.append(cur) +            cur = cur.owner + +        return list(reversed(layers)) + +    @property +    def app(self) -> Litestar: +        return cast("Litestar", self.ownership_layers[0]) + +    def resolve_type_encoders(self) -> TypeEncodersMap: +        """Return a merged type_encoders mapping. + +        This method is memoized so the computation occurs only once. + +        Returns: +            A dict of type encoders +        """ +        if self._resolved_type_encoders is Empty: +            self._resolved_type_encoders = {} + +            for layer in self.ownership_layers: +                if type_encoders := getattr(layer, "type_encoders", None): +                    self._resolved_type_encoders.update(type_encoders) +        return cast("TypeEncodersMap", self._resolved_type_encoders) + +    def resolve_type_decoders(self) -> TypeDecodersSequence: +        """Return a merged type_encoders mapping. + +        This method is memoized so the computation occurs only once. + +        Returns: +            A dict of type encoders +        """ +        if self._resolved_type_decoders is Empty: +            self._resolved_type_decoders = [] + +            for layer in self.ownership_layers: +                if type_decoders := getattr(layer, "type_decoders", None): +                    self._resolved_type_decoders.extend(list(type_decoders)) +        return cast("TypeDecodersSequence", self._resolved_type_decoders) + +    def resolve_layered_parameters(self) -> dict[str, FieldDefinition]: +        """Return all parameters declared above the handler.""" +        if self._resolved_layered_parameters is Empty: +            parameter_kwargs: dict[str, ParameterKwarg] = {} + +            for layer in self.ownership_layers: +                parameter_kwargs.update(getattr(layer, "parameters", {}) or {}) + +            self._resolved_layered_parameters = { +                key: FieldDefinition.from_kwarg(name=key, annotation=parameter.annotation, kwarg_definition=parameter) +                for key, parameter in parameter_kwargs.items() +            } + +        return self._resolved_layered_parameters + +    def resolve_guards(self) -> list[Guard]: +        """Return all guards in the handlers scope, starting from highest to current layer.""" +        if self._resolved_guards is Empty: +            self._resolved_guards = [] + +            for layer in self.ownership_layers: +                self._resolved_guards.extend(layer.guards or [])  # pyright: ignore + +            self._resolved_guards = cast( +                "list[Guard]", [ensure_async_callable(guard) for guard in self._resolved_guards] +            ) + +        return self._resolved_guards + +    def _get_plugin_registry(self) -> PluginRegistry | None: +        from litestar.app import Litestar + +        root_owner = self.ownership_layers[0] +        if isinstance(root_owner, Litestar): +            return root_owner.plugins +        return None + +    def resolve_dependencies(self) -> dict[str, Provide]: +        """Return all dependencies correlating to handler function's kwargs that exist in the handler's scope.""" +        plugin_registry = self._get_plugin_registry() +        if self._resolved_dependencies is Empty: +            self._resolved_dependencies = {} +            for layer in self.ownership_layers: +                for key, provider in (layer.dependencies or {}).items(): +                    self._resolved_dependencies[key] = self._resolve_dependency( +                        key=key, provider=provider, plugin_registry=plugin_registry +                    ) + +        return self._resolved_dependencies + +    def _resolve_dependency( +        self, key: str, provider: Provide | AnyCallable, plugin_registry: PluginRegistry | None +    ) -> Provide: +        if not isinstance(provider, Provide): +            provider = Provide(provider) + +        if self._resolved_dependencies is not Empty:  # pragma: no cover +            self._validate_dependency_is_unique(dependencies=self._resolved_dependencies, key=key, provider=provider) + +        if not getattr(provider, "parsed_fn_signature", None): +            dependency = unwrap_partial(provider.dependency) +            plugin: DIPlugin | None = None +            if plugin_registry: +                plugin = next( +                    (p for p in plugin_registry.di if isinstance(p, DIPlugin) and p.has_typed_init(dependency)), +                    None, +                ) +            if plugin: +                signature, init_type_hints = plugin.get_typed_init(dependency) +                provider.parsed_fn_signature = ParsedSignature.from_signature(signature, init_type_hints) +            else: +                provider.parsed_fn_signature = ParsedSignature.from_fn(dependency, self.resolve_signature_namespace()) + +        if not getattr(provider, "signature_model", None): +            provider.signature_model = SignatureModel.create( +                dependency_name_set=self.dependency_name_set, +                fn=provider.dependency, +                parsed_signature=provider.parsed_fn_signature, +                data_dto=self.resolve_data_dto(), +                type_decoders=self.resolve_type_decoders(), +            ) +        return provider + +    def resolve_middleware(self) -> list[Middleware]: +        """Build the middleware stack for the RouteHandler and return it. + +        The middlewares are added from top to bottom (``app -> router -> controller -> route handler``) and then +        reversed. +        """ +        resolved_middleware: list[Middleware] = [] +        for layer in self.ownership_layers: +            resolved_middleware.extend(layer.middleware or [])  # pyright: ignore +        return list(reversed(resolved_middleware)) + +    def resolve_exception_handlers(self) -> ExceptionHandlersMap: +        """Resolve the exception_handlers by starting from the route handler and moving up. + +        This method is memoized so the computation occurs only once. +        """ +        resolved_exception_handlers: dict[int | type[Exception], ExceptionHandler] = {} +        for layer in self.ownership_layers: +            resolved_exception_handlers.update(layer.exception_handlers or {})  # pyright: ignore +        return resolved_exception_handlers + +    def resolve_opts(self) -> None: +        """Build the route handler opt dictionary by going from top to bottom. + +        When merging keys from multiple layers, if the same key is defined by multiple layers, the value from the +        layer closest to the response handler will take precedence. +        """ + +        opt: dict[str, Any] = {} +        for layer in self.ownership_layers: +            opt.update(layer.opt or {})  # pyright: ignore + +        self.opt = opt + +    def resolve_signature_namespace(self) -> dict[str, Any]: +        """Build the route handler signature namespace dictionary by going from top to bottom. + +        When merging keys from multiple layers, if the same key is defined by multiple layers, the value from the +        layer closest to the response handler will take precedence. +        """ +        if self._resolved_layered_parameters is Empty: +            ns: dict[str, Any] = {} +            for layer in self.ownership_layers: +                ns.update(layer.signature_namespace) + +            self._resolved_signature_namespace = ns +        return cast("dict[str, Any]", self._resolved_signature_namespace) + +    def _get_dto_backend_cls(self) -> type[DTOBackend] | None: +        if ExperimentalFeatures.DTO_CODEGEN in self.app.experimental_features: +            from litestar.dto._codegen_backend import DTOCodegenBackend + +            return DTOCodegenBackend +        return None + +    def resolve_data_dto(self) -> type[AbstractDTO] | None: +        """Resolve the data_dto by starting from the route handler and moving up. +        If a handler is found it is returned, otherwise None is set. +        This method is memoized so the computation occurs only once. + +        Returns: +            An optional :class:`DTO type <.dto.base_dto.AbstractDTO>` +        """ +        if self._resolved_data_dto is Empty: +            if data_dtos := cast( +                "list[type[AbstractDTO] | None]", +                [layer.dto for layer in self.ownership_layers if layer.dto is not Empty], +            ): +                data_dto: type[AbstractDTO] | None = data_dtos[-1] +            elif self.parsed_data_field and ( +                plugins_for_data_type := [ +                    plugin +                    for plugin in self.app.plugins.serialization +                    if self.parsed_data_field.match_predicate_recursively(plugin.supports_type) +                ] +            ): +                data_dto = plugins_for_data_type[0].create_dto_for_type(self.parsed_data_field) +            else: +                data_dto = None + +            if self.parsed_data_field and data_dto: +                data_dto.create_for_field_definition( +                    field_definition=self.parsed_data_field, +                    handler_id=self.handler_id, +                    backend_cls=self._get_dto_backend_cls(), +                ) + +            self._resolved_data_dto = data_dto + +        return self._resolved_data_dto + +    def resolve_return_dto(self) -> type[AbstractDTO] | None: +        """Resolve the return_dto by starting from the route handler and moving up. +        If a handler is found it is returned, otherwise None is set. +        This method is memoized so the computation occurs only once. + +        Returns: +            An optional :class:`DTO type <.dto.base_dto.AbstractDTO>` +        """ +        if self._resolved_return_dto is Empty: +            if return_dtos := cast( +                "list[type[AbstractDTO] | None]", +                [layer.return_dto for layer in self.ownership_layers if layer.return_dto is not Empty], +            ): +                return_dto: type[AbstractDTO] | None = return_dtos[-1] +            elif plugins_for_return_type := [ +                plugin +                for plugin in self.app.plugins.serialization +                if self.parsed_return_field.match_predicate_recursively(plugin.supports_type) +            ]: +                return_dto = plugins_for_return_type[0].create_dto_for_type(self.parsed_return_field) +            else: +                return_dto = self.resolve_data_dto() + +            if return_dto and return_dto.is_supported_model_type_field(self.parsed_return_field): +                return_dto.create_for_field_definition( +                    field_definition=self.parsed_return_field, +                    handler_id=self.handler_id, +                    backend_cls=self._get_dto_backend_cls(), +                ) +                self._resolved_return_dto = return_dto +            else: +                self._resolved_return_dto = None + +        return self._resolved_return_dto + +    async def authorize_connection(self, connection: ASGIConnection) -> None: +        """Ensure the connection is authorized by running all the route guards in scope.""" +        for guard in self.resolve_guards(): +            await guard(connection, copy(self))  # type: ignore[misc] + +    @staticmethod +    def _validate_dependency_is_unique(dependencies: dict[str, Provide], key: str, provider: Provide) -> None: +        """Validate that a given provider has not been already defined under a different key.""" +        for dependency_key, value in dependencies.items(): +            if provider == value: +                raise ImproperlyConfiguredException( +                    f"Provider for key {key} is already defined under the different key {dependency_key}. " +                    f"If you wish to override a provider, it must have the same key." +                ) + +    def on_registration(self, app: Litestar) -> None: +        """Called once per handler when the app object is instantiated. + +        Args: +            app: The :class:`Litestar<.app.Litestar>` app object. + +        Returns: +            None +        """ +        self._validate_handler_function() +        self.resolve_dependencies() +        self.resolve_guards() +        self.resolve_middleware() +        self.resolve_opts() +        self.resolve_data_dto() +        self.resolve_return_dto() + +    def _validate_handler_function(self) -> None: +        """Validate the route handler function once set by inspecting its return annotations.""" +        if ( +            self.parsed_data_field is not None +            and self.parsed_data_field.is_subclass_of(DTOData) +            and not self.resolve_data_dto() +        ): +            raise ImproperlyConfiguredException( +                f"Handler function {self.handler_name} has a data parameter that is a subclass of DTOData but no " +                "DTO has been registered for it." +            ) + +    def __str__(self) -> str: +        """Return a unique identifier for the route handler. + +        Returns: +            A string +        """ +        target: type[AsyncAnyCallable] | AsyncAnyCallable  # pyright: ignore +        target = unwrap_partial(self.fn) +        if not hasattr(target, "__qualname__"): +            target = type(target) +        return f"{target.__module__}.{target.__qualname__}" diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__init__.py b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__init__.py new file mode 100644 index 0000000..844f046 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__init__.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from .base import HTTPRouteHandler, route +from .decorators import delete, get, head, patch, post, put + +__all__ = ( +    "HTTPRouteHandler", +    "delete", +    "get", +    "head", +    "patch", +    "post", +    "put", +    "route", +) diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/__init__.cpython-311.pycBinary files differ new file mode 100644 index 0000000..848ac8f --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/_utils.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/_utils.cpython-311.pycBinary files differ new file mode 100644 index 0000000..22a7f67 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/_utils.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/base.cpython-311.pycBinary files differ new file mode 100644 index 0000000..eb39166 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/base.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/decorators.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/decorators.cpython-311.pycBinary files differ new file mode 100644 index 0000000..0acd7c8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/__pycache__/decorators.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/_utils.py b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/_utils.py new file mode 100644 index 0000000..ec95145 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/_utils.py @@ -0,0 +1,221 @@ +from __future__ import annotations + +from functools import lru_cache +from inspect import isawaitable +from typing import TYPE_CHECKING, Any, Sequence, cast + +from litestar.enums import HttpMethod +from litestar.exceptions import ValidationException +from litestar.response import Response +from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT +from litestar.types.builtin_types import NoneType + +if TYPE_CHECKING: +    from litestar.app import Litestar +    from litestar.background_tasks import BackgroundTask, BackgroundTasks +    from litestar.connection import Request +    from litestar.datastructures import Cookie, ResponseHeader +    from litestar.types import AfterRequestHookHandler, ASGIApp, AsyncAnyCallable, Method, TypeEncodersMap +    from litestar.typing import FieldDefinition + +__all__ = ( +    "create_data_handler", +    "create_generic_asgi_response_handler", +    "create_response_handler", +    "get_default_status_code", +    "is_empty_response_annotation", +    "normalize_headers", +    "normalize_http_method", +) + + +def create_data_handler( +    after_request: AfterRequestHookHandler | None, +    background: BackgroundTask | BackgroundTasks | None, +    cookies: frozenset[Cookie], +    headers: frozenset[ResponseHeader], +    media_type: str, +    response_class: type[Response], +    status_code: int, +    type_encoders: TypeEncodersMap | None, +) -> AsyncAnyCallable: +    """Create a handler function for arbitrary data. + +    Args: +        after_request: An after request handler. +        background: A background task or background tasks. +        cookies: A set of pre-defined cookies. +        headers: A set of response headers. +        media_type: The response media type. +        response_class: The response class to use. +        status_code: The response status code. +        type_encoders: A mapping of types to encoder functions. + +    Returns: +        A handler function. + +    """ + +    async def handler( +        data: Any, +        request: Request[Any, Any, Any], +        app: Litestar, +        **kwargs: Any, +    ) -> ASGIApp: +        if isawaitable(data): +            data = await data + +        response = response_class( +            background=background, +            content=data, +            media_type=media_type, +            status_code=status_code, +            type_encoders=type_encoders, +        ) + +        if after_request: +            response = await after_request(response)  # type: ignore[arg-type,misc] + +        return response.to_asgi_response(app=None, request=request, headers=normalize_headers(headers), cookies=cookies)  # pyright: ignore + +    return handler + + +def create_generic_asgi_response_handler(after_request: AfterRequestHookHandler | None) -> AsyncAnyCallable: +    """Create a handler function for Responses. + +    Args: +        after_request: An after request handler. + +    Returns: +        A handler function. +    """ + +    async def handler(data: ASGIApp, **kwargs: Any) -> ASGIApp: +        return await after_request(data) if after_request else data  # type: ignore[arg-type, misc, no-any-return] + +    return handler + + +@lru_cache(1024) +def normalize_headers(headers: frozenset[ResponseHeader]) -> dict[str, str]: +    """Given a dictionary of ResponseHeader, filter them and return a dictionary of values. + +    Args: +        headers: A dictionary of :class:`ResponseHeader <litestar.datastructures.ResponseHeader>` values + +    Returns: +        A string keyed dictionary of normalized values +    """ +    return { +        header.name: cast("str", header.value)  # we know value to be a string at this point because we validate it +        # that it's not None when initializing a header with documentation_only=True +        for header in headers +        if not header.documentation_only +    } + + +def create_response_handler( +    after_request: AfterRequestHookHandler | None, +    background: BackgroundTask | BackgroundTasks | None, +    cookies: frozenset[Cookie], +    headers: frozenset[ResponseHeader], +    media_type: str, +    status_code: int, +    type_encoders: TypeEncodersMap | None, +) -> AsyncAnyCallable: +    """Create a handler function for Litestar Responses. + +    Args: +        after_request: An after request handler. +        background: A background task or background tasks. +        cookies: A set of pre-defined cookies. +        headers: A set of response headers. +        media_type: The response media type. +        status_code: The response status code. +        type_encoders: A mapping of types to encoder functions. + +    Returns: +        A handler function. +    """ + +    normalized_headers = normalize_headers(headers) +    cookie_list = list(cookies) + +    async def handler( +        data: Response, +        app: Litestar, +        request: Request, +        **kwargs: Any,  # kwargs is for return dto +    ) -> ASGIApp: +        response = await after_request(data) if after_request else data  # type:ignore[arg-type,misc] +        return response.to_asgi_response(  # type: ignore[no-any-return] +            app=None, +            background=background, +            cookies=cookie_list, +            headers=normalized_headers, +            media_type=media_type, +            request=request, +            status_code=status_code, +            type_encoders=type_encoders, +        ) + +    return handler + + +def normalize_http_method(http_methods: HttpMethod | Method | Sequence[HttpMethod | Method]) -> set[Method]: +    """Normalize HTTP method(s) into a set of upper-case method names. + +    Args: +        http_methods: A value for http method. + +    Returns: +        A normalized set of http methods. +    """ +    output: set[str] = set() + +    if isinstance(http_methods, str): +        http_methods = [http_methods]  # pyright: ignore + +    for method in http_methods: +        method_name = method.value.upper() if isinstance(method, HttpMethod) else method.upper() +        if method_name not in HTTP_METHOD_NAMES: +            raise ValidationException(f"Invalid HTTP method: {method_name}") +        output.add(method_name) + +    return cast("set[Method]", output) + + +def get_default_status_code(http_methods: set[Method]) -> int: +    """Return the default status code for a given set of HTTP methods. + +    Args: +        http_methods: A set of method strings + +    Returns: +        A status code +    """ +    if HttpMethod.POST in http_methods: +        return HTTP_201_CREATED +    if HttpMethod.DELETE in http_methods: +        return HTTP_204_NO_CONTENT +    return HTTP_200_OK + + +def is_empty_response_annotation(return_annotation: FieldDefinition) -> bool: +    """Return whether the return annotation is an empty response. + +    Args: +        return_annotation: A return annotation. + +    Returns: +        Whether the return annotation is an empty response. +    """ +    return ( +        return_annotation.is_subclass_of(NoneType) +        or return_annotation.is_subclass_of(Response) +        and return_annotation.has_inner_subclass_of(NoneType) +    ) + + +HTTP_METHOD_NAMES = {m.value for m in HttpMethod} diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/base.py b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/base.py new file mode 100644 index 0000000..757253e --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/base.py @@ -0,0 +1,591 @@ +from __future__ import annotations + +from enum import Enum +from typing import TYPE_CHECKING, AnyStr, Mapping, Sequence, TypedDict, cast + +from litestar._layers.utils import narrow_response_cookies, narrow_response_headers +from litestar.connection import Request +from litestar.datastructures.cookie import Cookie +from litestar.datastructures.response_header import ResponseHeader +from litestar.enums import HttpMethod, MediaType +from litestar.exceptions import ( +    HTTPException, +    ImproperlyConfiguredException, +) +from litestar.handlers.base import BaseRouteHandler +from litestar.handlers.http_handlers._utils import ( +    create_data_handler, +    create_generic_asgi_response_handler, +    create_response_handler, +    get_default_status_code, +    is_empty_response_annotation, +    normalize_http_method, +) +from litestar.openapi.spec import Operation +from litestar.response import Response +from litestar.status_codes import HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED +from litestar.types import ( +    AfterRequestHookHandler, +    AfterResponseHookHandler, +    AnyCallable, +    ASGIApp, +    BeforeRequestHookHandler, +    CacheKeyBuilder, +    Dependencies, +    Empty, +    EmptyType, +    ExceptionHandlersMap, +    Guard, +    Method, +    Middleware, +    ResponseCookies, +    ResponseHeaders, +    TypeEncodersMap, +) +from litestar.utils import ensure_async_callable +from litestar.utils.predicates import is_async_callable +from litestar.utils.warnings import warn_implicit_sync_to_thread, warn_sync_to_thread_with_async_callable + +if TYPE_CHECKING: +    from typing import Any, Awaitable, Callable + +    from litestar.app import Litestar +    from litestar.background_tasks import BackgroundTask, BackgroundTasks +    from litestar.config.response_cache import CACHE_FOREVER +    from litestar.datastructures import CacheControlHeader, ETag +    from litestar.dto import AbstractDTO +    from litestar.openapi.datastructures import ResponseSpec +    from litestar.openapi.spec import SecurityRequirement +    from litestar.types.callable_types import AsyncAnyCallable, OperationIDCreator +    from litestar.types.composite_types import TypeDecodersSequence + +__all__ = ("HTTPRouteHandler", "route") + + +class ResponseHandlerMap(TypedDict): +    default_handler: Callable[[Any], Awaitable[ASGIApp]] | EmptyType +    response_type_handler: Callable[[Any], Awaitable[ASGIApp]] | EmptyType + + +class HTTPRouteHandler(BaseRouteHandler): +    """HTTP Route Decorator. + +    Use this decorator to decorate an HTTP handler with multiple methods. +    """ + +    __slots__ = ( +        "_resolved_after_response", +        "_resolved_before_request", +        "_response_handler_mapping", +        "_resolved_include_in_schema", +        "_resolved_tags", +        "_resolved_security", +        "after_request", +        "after_response", +        "background", +        "before_request", +        "cache", +        "cache_control", +        "cache_key_builder", +        "content_encoding", +        "content_media_type", +        "deprecated", +        "description", +        "etag", +        "has_sync_callable", +        "http_methods", +        "include_in_schema", +        "media_type", +        "operation_class", +        "operation_id", +        "raises", +        "request_class", +        "response_class", +        "response_cookies", +        "response_description", +        "response_headers", +        "responses", +        "security", +        "status_code", +        "summary", +        "sync_to_thread", +        "tags", +        "template_name", +    ) + +    has_sync_callable: bool + +    def __init__( +        self, +        path: str | Sequence[str] | None = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        http_method: HttpMethod | Method | Sequence[HttpMethod | Method], +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        signature_namespace: Mapping[str, Any] | None = None, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``HTTPRouteHandler``. + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`Request <.connection.Request>` instance and any non-``None`` return value is used for the +                response, bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a +                number of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <.enums.HttpMethod>` or a list of these that correlates to the methods the route +                handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a valid IANA +                Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and +                ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``"base64"``. +            content_media_type: A string designating the media-type of the content, e.g. ``"image/png"``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if not http_method: +            raise ImproperlyConfiguredException("An http_method kwarg is required") + +        self.http_methods = normalize_http_method(http_methods=http_method) +        self.status_code = status_code or get_default_status_code(http_methods=self.http_methods) + +        super().__init__( +            path=path, +            dependencies=dependencies, +            dto=dto, +            exception_handlers=exception_handlers, +            guards=guards, +            middleware=middleware, +            name=name, +            opt=opt, +            return_dto=return_dto, +            signature_namespace=signature_namespace, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) + +        self.after_request = ensure_async_callable(after_request) if after_request else None  # pyright: ignore +        self.after_response = ensure_async_callable(after_response) if after_response else None +        self.background = background +        self.before_request = ensure_async_callable(before_request) if before_request else None +        self.cache = cache +        self.cache_control = cache_control +        self.cache_key_builder = cache_key_builder +        self.etag = etag +        self.media_type: MediaType | str = media_type or "" +        self.request_class = request_class +        self.response_class = response_class +        self.response_cookies: Sequence[Cookie] | None = narrow_response_cookies(response_cookies) +        self.response_headers: Sequence[ResponseHeader] | None = narrow_response_headers(response_headers) + +        self.sync_to_thread = sync_to_thread +        # OpenAPI related attributes +        self.content_encoding = content_encoding +        self.content_media_type = content_media_type +        self.deprecated = deprecated +        self.description = description +        self.include_in_schema = include_in_schema +        self.operation_class = operation_class +        self.operation_id = operation_id +        self.raises = raises +        self.response_description = response_description +        self.summary = summary +        self.tags = tags +        self.security = security +        self.responses = responses +        # memoized attributes, defaulted to Empty +        self._resolved_after_response: AsyncAnyCallable | None | EmptyType = Empty +        self._resolved_before_request: AsyncAnyCallable | None | EmptyType = Empty +        self._response_handler_mapping: ResponseHandlerMap = {"default_handler": Empty, "response_type_handler": Empty} +        self._resolved_include_in_schema: bool | EmptyType = Empty +        self._resolved_security: list[SecurityRequirement] | EmptyType = Empty +        self._resolved_tags: list[str] | EmptyType = Empty + +    def __call__(self, fn: AnyCallable) -> HTTPRouteHandler: +        """Replace a function with itself.""" +        if not is_async_callable(fn): +            if self.sync_to_thread is None: +                warn_implicit_sync_to_thread(fn, stacklevel=3) +        elif self.sync_to_thread is not None: +            warn_sync_to_thread_with_async_callable(fn, stacklevel=3) + +        super().__call__(fn) +        return self + +    def resolve_request_class(self) -> type[Request]: +        """Return the closest custom Request class in the owner graph or the default Request class. + +        This method is memoized so the computation occurs only once. + +        Returns: +            The default :class:`Request <.connection.Request>` class for the route handler. +        """ +        return next( +            (layer.request_class for layer in reversed(self.ownership_layers) if layer.request_class is not None), +            Request, +        ) + +    def resolve_response_class(self) -> type[Response]: +        """Return the closest custom Response class in the owner graph or the default Response class. + +        This method is memoized so the computation occurs only once. + +        Returns: +            The default :class:`Response <.response.Response>` class for the route handler. +        """ +        return next( +            (layer.response_class for layer in reversed(self.ownership_layers) if layer.response_class is not None), +            Response, +        ) + +    def resolve_response_headers(self) -> frozenset[ResponseHeader]: +        """Return all header parameters in the scope of the handler function. + +        Returns: +            A dictionary mapping keys to :class:`ResponseHeader <.datastructures.ResponseHeader>` instances. +        """ +        resolved_response_headers: dict[str, ResponseHeader] = {} + +        for layer in self.ownership_layers: +            if layer_response_headers := layer.response_headers: +                if isinstance(layer_response_headers, Mapping): +                    # this can't happen unless you manually set response_headers on an instance, which would result in a +                    # type-checking error on everything but the controller. We cover this case nevertheless +                    resolved_response_headers.update( +                        {name: ResponseHeader(name=name, value=value) for name, value in layer_response_headers.items()} +                    ) +                else: +                    resolved_response_headers.update({h.name: h for h in layer_response_headers}) +            for extra_header in ("cache_control", "etag"): +                if header_model := getattr(layer, extra_header, None): +                    resolved_response_headers[header_model.HEADER_NAME] = ResponseHeader( +                        name=header_model.HEADER_NAME, +                        value=header_model.to_header(), +                        documentation_only=header_model.documentation_only, +                    ) + +        return frozenset(resolved_response_headers.values()) + +    def resolve_response_cookies(self) -> frozenset[Cookie]: +        """Return a list of Cookie instances. Filters the list to ensure each cookie key is unique. + +        Returns: +            A list of :class:`Cookie <.datastructures.Cookie>` instances. +        """ +        response_cookies: set[Cookie] = set() +        for layer in reversed(self.ownership_layers): +            if layer_response_cookies := layer.response_cookies: +                if isinstance(layer_response_cookies, Mapping): +                    # this can't happen unless you manually set response_cookies on an instance, which would result in a +                    # type-checking error on everything but the controller. We cover this case nevertheless +                    response_cookies.update( +                        {Cookie(key=key, value=value) for key, value in layer_response_cookies.items()} +                    ) +                else: +                    response_cookies.update(cast("set[Cookie]", layer_response_cookies)) +        return frozenset(response_cookies) + +    def resolve_before_request(self) -> AsyncAnyCallable | None: +        """Resolve the before_handler handler by starting from the route handler and moving up. + +        If a handler is found it is returned, otherwise None is set. +        This method is memoized so the computation occurs only once. + +        Returns: +            An optional :class:`before request lifecycle hook handler <.types.BeforeRequestHookHandler>` +        """ +        if self._resolved_before_request is Empty: +            before_request_handlers = [layer.before_request for layer in self.ownership_layers if layer.before_request] +            self._resolved_before_request = before_request_handlers[-1] if before_request_handlers else None +        return cast("AsyncAnyCallable | None", self._resolved_before_request) + +    def resolve_after_response(self) -> AsyncAnyCallable | None: +        """Resolve the after_response handler by starting from the route handler and moving up. + +        If a handler is found it is returned, otherwise None is set. +        This method is memoized so the computation occurs only once. + +        Returns: +            An optional :class:`after response lifecycle hook handler <.types.AfterResponseHookHandler>` +        """ +        if self._resolved_after_response is Empty: +            after_response_handlers: list[AsyncAnyCallable] = [ +                layer.after_response  # type: ignore[misc] +                for layer in self.ownership_layers +                if layer.after_response +            ] +            self._resolved_after_response = after_response_handlers[-1] if after_response_handlers else None + +        return cast("AsyncAnyCallable | None", self._resolved_after_response) + +    def resolve_include_in_schema(self) -> bool: +        """Resolve the 'include_in_schema' property by starting from the route handler and moving up. + +        If 'include_in_schema' is found in any of the ownership layers, the last value found is returned. +        If not found in any layer, the default value ``True`` is returned. + +        Returns: +            bool: The resolved 'include_in_schema' property. +        """ +        if self._resolved_include_in_schema is Empty: +            include_in_schemas = [ +                i.include_in_schema for i in self.ownership_layers if isinstance(i.include_in_schema, bool) +            ] +            self._resolved_include_in_schema = include_in_schemas[-1] if include_in_schemas else True + +        return self._resolved_include_in_schema + +    def resolve_security(self) -> list[SecurityRequirement]: +        """Resolve the security property by starting from the route handler and moving up. + +        Security requirements are additive, so the security requirements of the route handler are the sum of all +        security requirements of the ownership layers. + +        Returns: +            list[SecurityRequirement]: The resolved security property. +        """ +        if self._resolved_security is Empty: +            self._resolved_security = [] +            for layer in self.ownership_layers: +                if isinstance(layer.security, Sequence): +                    self._resolved_security.extend(layer.security) + +        return self._resolved_security + +    def resolve_tags(self) -> list[str]: +        """Resolve the tags property by starting from the route handler and moving up. + +        Tags are additive, so the tags of the route handler are the sum of all tags of the ownership layers. + +        Returns: +            list[str]: A sorted list of unique tags. +        """ +        if self._resolved_tags is Empty: +            tag_set = set() +            for layer in self.ownership_layers: +                for tag in layer.tags or []: +                    tag_set.add(tag) +            self._resolved_tags = sorted(tag_set) + +        return self._resolved_tags + +    def get_response_handler(self, is_response_type_data: bool = False) -> Callable[[Any], Awaitable[ASGIApp]]: +        """Resolve the response_handler function for the route handler. + +        This method is memoized so the computation occurs only once. + +        Args: +            is_response_type_data: Whether to return a handler for 'Response' instances. + +        Returns: +            Async Callable to handle an HTTP Request +        """ +        if self._response_handler_mapping["default_handler"] is Empty: +            after_request_handlers: list[AsyncAnyCallable] = [ +                layer.after_request  # type: ignore[misc] +                for layer in self.ownership_layers +                if layer.after_request +            ] +            after_request = cast( +                "AfterRequestHookHandler | None", +                after_request_handlers[-1] if after_request_handlers else None, +            ) + +            media_type = self.media_type.value if isinstance(self.media_type, Enum) else self.media_type +            response_class = self.resolve_response_class() +            headers = self.resolve_response_headers() +            cookies = self.resolve_response_cookies() +            type_encoders = self.resolve_type_encoders() + +            return_type = self.parsed_fn_signature.return_type +            return_annotation = return_type.annotation + +            self._response_handler_mapping["response_type_handler"] = response_type_handler = create_response_handler( +                after_request=after_request, +                background=self.background, +                cookies=cookies, +                headers=headers, +                media_type=media_type, +                status_code=self.status_code, +                type_encoders=type_encoders, +            ) + +            if return_type.is_subclass_of(Response): +                self._response_handler_mapping["default_handler"] = response_type_handler +            elif is_async_callable(return_annotation) or return_annotation is ASGIApp: +                self._response_handler_mapping["default_handler"] = create_generic_asgi_response_handler( +                    after_request=after_request +                ) +            else: +                self._response_handler_mapping["default_handler"] = create_data_handler( +                    after_request=after_request, +                    background=self.background, +                    cookies=cookies, +                    headers=headers, +                    media_type=media_type, +                    response_class=response_class, +                    status_code=self.status_code, +                    type_encoders=type_encoders, +                ) + +        return cast( +            "Callable[[Any], Awaitable[ASGIApp]]", +            self._response_handler_mapping["response_type_handler"] +            if is_response_type_data +            else self._response_handler_mapping["default_handler"], +        ) + +    async def to_response(self, app: Litestar, data: Any, request: Request) -> ASGIApp: +        """Return a :class:`Response <.response.Response>` from the handler by resolving and calling it. + +        Args: +            app: The :class:`Litestar <litestar.app.Litestar>` app instance +            data: Either an instance of a :class:`Response <.response.Response>`, +                a Response instance or an arbitrary value. +            request: A :class:`Request <.connection.Request>` instance + +        Returns: +            A Response instance +        """ +        if return_dto_type := self.resolve_return_dto(): +            data = return_dto_type(request).data_to_encodable_type(data) + +        response_handler = self.get_response_handler(is_response_type_data=isinstance(data, Response)) +        return await response_handler(app=app, data=data, request=request)  # type: ignore[call-arg] + +    def on_registration(self, app: Litestar) -> None: +        super().on_registration(app) +        self.resolve_after_response() +        self.resolve_include_in_schema() +        self.has_sync_callable = not is_async_callable(self.fn) + +        if self.has_sync_callable and self.sync_to_thread: +            self._fn = ensure_async_callable(self.fn) +            self.has_sync_callable = False + +    def _validate_handler_function(self) -> None: +        """Validate the route handler function once it is set by inspecting its return annotations.""" +        super()._validate_handler_function() + +        return_type = self.parsed_fn_signature.return_type + +        if return_type.annotation is Empty: +            raise ImproperlyConfiguredException( +                "A return value of a route handler function should be type annotated. " +                "If your function doesn't return a value, annotate it as returning 'None'." +            ) + +        if ( +            self.status_code < 200 or self.status_code in {HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED} +        ) and not is_empty_response_annotation(return_type): +            raise ImproperlyConfiguredException( +                "A status code 204, 304 or in the range below 200 does not support a response body. " +                "If the function should return a value, change the route handler status code to an appropriate value.", +            ) + +        if not self.media_type: +            if return_type.is_subclass_of((str, bytes)) or return_type.annotation is AnyStr: +                self.media_type = MediaType.TEXT +            elif not return_type.is_subclass_of(Response): +                self.media_type = MediaType.JSON + +        if "socket" in self.parsed_fn_signature.parameters: +            raise ImproperlyConfiguredException("The 'socket' kwarg is not supported with http handlers") + +        if "data" in self.parsed_fn_signature.parameters and "GET" in self.http_methods: +            raise ImproperlyConfiguredException("'data' kwarg is unsupported for 'GET' request handlers") + + +route = HTTPRouteHandler diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/decorators.py b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/decorators.py new file mode 100644 index 0000000..1ae72e5 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/http_handlers/decorators.py @@ -0,0 +1,1096 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from litestar.enums import HttpMethod, MediaType +from litestar.exceptions import HTTPException, ImproperlyConfiguredException +from litestar.openapi.spec import Operation +from litestar.response.file import ASGIFileResponse, File +from litestar.types import Empty, TypeDecodersSequence +from litestar.types.builtin_types import NoneType +from litestar.utils import is_class_and_subclass + +from .base import HTTPRouteHandler + +if TYPE_CHECKING: +    from typing import Any, Mapping, Sequence + +    from litestar.background_tasks import BackgroundTask, BackgroundTasks +    from litestar.config.response_cache import CACHE_FOREVER +    from litestar.connection import Request +    from litestar.datastructures import CacheControlHeader, ETag +    from litestar.dto import AbstractDTO +    from litestar.openapi.datastructures import ResponseSpec +    from litestar.openapi.spec import SecurityRequirement +    from litestar.response import Response +    from litestar.types import ( +        AfterRequestHookHandler, +        AfterResponseHookHandler, +        BeforeRequestHookHandler, +        CacheKeyBuilder, +        Dependencies, +        EmptyType, +        ExceptionHandlersMap, +        Guard, +        Middleware, +        ResponseCookies, +        ResponseHeaders, +        TypeEncodersMap, +    ) +    from litestar.types.callable_types import OperationIDCreator + + +__all__ = ("get", "head", "post", "put", "patch", "delete") + +MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP = "semantic route handlers cannot define http_method" + + +class delete(HTTPRouteHandler): +    """DELETE Route Decorator. + +    Use this decorator to decorate an HTTP handler for DELETE requests. +    """ + +    def __init__( +        self, +        path: str | None | Sequence[str] = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``delete`` + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, +                bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number +                of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <litestar.enums.HttpMethod>` or a list of these that correlates to the methods the +                route handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a +                valid IANA Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` +                and ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``base64``. +            content_media_type: A string designating the media-type of the content, e.g. ``image/png``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if "http_method" in kwargs: +            raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) +        super().__init__( +            after_request=after_request, +            after_response=after_response, +            background=background, +            before_request=before_request, +            cache=cache, +            cache_control=cache_control, +            cache_key_builder=cache_key_builder, +            content_encoding=content_encoding, +            content_media_type=content_media_type, +            dependencies=dependencies, +            deprecated=deprecated, +            description=description, +            dto=dto, +            etag=etag, +            exception_handlers=exception_handlers, +            guards=guards, +            http_method=HttpMethod.DELETE, +            include_in_schema=include_in_schema, +            media_type=media_type, +            middleware=middleware, +            name=name, +            operation_class=operation_class, +            operation_id=operation_id, +            opt=opt, +            path=path, +            raises=raises, +            request_class=request_class, +            response_class=response_class, +            response_cookies=response_cookies, +            response_description=response_description, +            response_headers=response_headers, +            responses=responses, +            return_dto=return_dto, +            security=security, +            signature_namespace=signature_namespace, +            status_code=status_code, +            summary=summary, +            sync_to_thread=sync_to_thread, +            tags=tags, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) + + +class get(HTTPRouteHandler): +    """GET Route Decorator. + +    Use this decorator to decorate an HTTP handler for GET requests. +    """ + +    def __init__( +        self, +        path: str | None | Sequence[str] = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``get``. + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, +                bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number +                of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <litestar.enums.HttpMethod>` or a list of these that correlates to the methods the +                route handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a +                valid IANA Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and +                ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``base64``. +            content_media_type: A string designating the media-type of the content, e.g. ``image/png``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if "http_method" in kwargs: +            raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) + +        super().__init__( +            after_request=after_request, +            after_response=after_response, +            background=background, +            before_request=before_request, +            cache=cache, +            cache_control=cache_control, +            cache_key_builder=cache_key_builder, +            content_encoding=content_encoding, +            content_media_type=content_media_type, +            dependencies=dependencies, +            deprecated=deprecated, +            description=description, +            dto=dto, +            etag=etag, +            exception_handlers=exception_handlers, +            guards=guards, +            http_method=HttpMethod.GET, +            include_in_schema=include_in_schema, +            media_type=media_type, +            middleware=middleware, +            name=name, +            operation_class=operation_class, +            operation_id=operation_id, +            opt=opt, +            path=path, +            raises=raises, +            request_class=request_class, +            response_class=response_class, +            response_cookies=response_cookies, +            response_description=response_description, +            response_headers=response_headers, +            responses=responses, +            return_dto=return_dto, +            security=security, +            signature_namespace=signature_namespace, +            status_code=status_code, +            summary=summary, +            sync_to_thread=sync_to_thread, +            tags=tags, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) + + +class head(HTTPRouteHandler): +    """HEAD Route Decorator. + +    Use this decorator to decorate an HTTP handler for HEAD requests. +    """ + +    def __init__( +        self, +        path: str | None | Sequence[str] = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        signature_namespace: Mapping[str, Any] | None = None, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``head``. + +        Notes: +            - A response to a head request cannot include a body. +                See: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD). + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, +                bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number +                of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <litestar.enums.HttpMethod>` or a list of these that correlates to the methods the +                route handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a +                valid IANA Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and +                ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``base64``. +            content_media_type: A string designating the media-type of the content, e.g. ``image/png``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if "http_method" in kwargs: +            raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) + +        super().__init__( +            after_request=after_request, +            after_response=after_response, +            background=background, +            before_request=before_request, +            cache=cache, +            cache_control=cache_control, +            cache_key_builder=cache_key_builder, +            content_encoding=content_encoding, +            content_media_type=content_media_type, +            dependencies=dependencies, +            deprecated=deprecated, +            description=description, +            dto=dto, +            etag=etag, +            exception_handlers=exception_handlers, +            guards=guards, +            http_method=HttpMethod.HEAD, +            include_in_schema=include_in_schema, +            media_type=media_type, +            middleware=middleware, +            name=name, +            operation_class=operation_class, +            operation_id=operation_id, +            opt=opt, +            path=path, +            raises=raises, +            request_class=request_class, +            response_class=response_class, +            response_cookies=response_cookies, +            response_description=response_description, +            response_headers=response_headers, +            responses=responses, +            return_dto=return_dto, +            security=security, +            signature_namespace=signature_namespace, +            status_code=status_code, +            summary=summary, +            sync_to_thread=sync_to_thread, +            tags=tags, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) + +    def _validate_handler_function(self) -> None: +        """Validate the route handler function once it is set by inspecting its return annotations.""" +        super()._validate_handler_function() + +        # we allow here File and File because these have special setting for head responses +        return_annotation = self.parsed_fn_signature.return_type.annotation +        if not ( +            return_annotation in {NoneType, None} +            or is_class_and_subclass(return_annotation, File) +            or is_class_and_subclass(return_annotation, ASGIFileResponse) +        ): +            raise ImproperlyConfiguredException("A response to a head request should not have a body") + + +class patch(HTTPRouteHandler): +    """PATCH Route Decorator. + +    Use this decorator to decorate an HTTP handler for PATCH requests. +    """ + +    def __init__( +        self, +        path: str | None | Sequence[str] = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``patch``. + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, +                bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number +                of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <litestar.enums.HttpMethod>` or a list of these that correlates to the methods the +                route handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a +                valid IANA Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and +                ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``base64``. +            content_media_type: A string designating the media-type of the content, e.g. ``image/png``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if "http_method" in kwargs: +            raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) +        super().__init__( +            after_request=after_request, +            after_response=after_response, +            background=background, +            before_request=before_request, +            cache=cache, +            cache_control=cache_control, +            cache_key_builder=cache_key_builder, +            content_encoding=content_encoding, +            content_media_type=content_media_type, +            dependencies=dependencies, +            deprecated=deprecated, +            description=description, +            dto=dto, +            etag=etag, +            exception_handlers=exception_handlers, +            guards=guards, +            http_method=HttpMethod.PATCH, +            include_in_schema=include_in_schema, +            media_type=media_type, +            middleware=middleware, +            name=name, +            operation_class=operation_class, +            operation_id=operation_id, +            opt=opt, +            path=path, +            raises=raises, +            request_class=request_class, +            response_class=response_class, +            response_cookies=response_cookies, +            response_description=response_description, +            response_headers=response_headers, +            responses=responses, +            return_dto=return_dto, +            security=security, +            signature_namespace=signature_namespace, +            status_code=status_code, +            summary=summary, +            sync_to_thread=sync_to_thread, +            tags=tags, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) + + +class post(HTTPRouteHandler): +    """POST Route Decorator. + +    Use this decorator to decorate an HTTP handler for POST requests. +    """ + +    def __init__( +        self, +        path: str | None | Sequence[str] = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``post`` + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, +                bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number +                of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <litestar.enums.HttpMethod>` or a list of these that correlates to the methods the +                route handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a +                valid IANA Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and +                ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``base64``. +            content_media_type: A string designating the media-type of the content, e.g. ``image/png``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if "http_method" in kwargs: +            raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) +        super().__init__( +            after_request=after_request, +            after_response=after_response, +            background=background, +            before_request=before_request, +            cache=cache, +            cache_control=cache_control, +            cache_key_builder=cache_key_builder, +            content_encoding=content_encoding, +            content_media_type=content_media_type, +            dependencies=dependencies, +            deprecated=deprecated, +            description=description, +            dto=dto, +            exception_handlers=exception_handlers, +            etag=etag, +            guards=guards, +            http_method=HttpMethod.POST, +            include_in_schema=include_in_schema, +            media_type=media_type, +            middleware=middleware, +            name=name, +            operation_class=operation_class, +            operation_id=operation_id, +            opt=opt, +            path=path, +            raises=raises, +            request_class=request_class, +            response_class=response_class, +            response_cookies=response_cookies, +            response_description=response_description, +            response_headers=response_headers, +            responses=responses, +            return_dto=return_dto, +            signature_namespace=signature_namespace, +            security=security, +            status_code=status_code, +            summary=summary, +            sync_to_thread=sync_to_thread, +            tags=tags, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) + + +class put(HTTPRouteHandler): +    """PUT Route Decorator. + +    Use this decorator to decorate an HTTP handler for PUT requests. +    """ + +    def __init__( +        self, +        path: str | None | Sequence[str] = None, +        *, +        after_request: AfterRequestHookHandler | None = None, +        after_response: AfterResponseHookHandler | None = None, +        background: BackgroundTask | BackgroundTasks | None = None, +        before_request: BeforeRequestHookHandler | None = None, +        cache: bool | int | type[CACHE_FOREVER] = False, +        cache_control: CacheControlHeader | None = None, +        cache_key_builder: CacheKeyBuilder | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        etag: ETag | None = None, +        exception_handlers: ExceptionHandlersMap | None = None, +        guards: Sequence[Guard] | None = None, +        media_type: MediaType | str | None = None, +        middleware: Sequence[Middleware] | None = None, +        name: str | None = None, +        opt: Mapping[str, Any] | None = None, +        request_class: type[Request] | None = None, +        response_class: type[Response] | None = None, +        response_cookies: ResponseCookies | None = None, +        response_headers: ResponseHeaders | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        status_code: int | None = None, +        sync_to_thread: bool | None = None, +        # OpenAPI related attributes +        content_encoding: str | None = None, +        content_media_type: str | None = None, +        deprecated: bool = False, +        description: str | None = None, +        include_in_schema: bool | EmptyType = Empty, +        operation_class: type[Operation] = Operation, +        operation_id: str | OperationIDCreator | None = None, +        raises: Sequence[type[HTTPException]] | None = None, +        response_description: str | None = None, +        responses: Mapping[int, ResponseSpec] | None = None, +        security: Sequence[SecurityRequirement] | None = None, +        summary: str | None = None, +        tags: Sequence[str] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``put`` + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. +                If not given defaults to ``/`` +            after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed +                to any route handler. If this function returns a value, the request will not reach the route handler, +                and instead this value will be used. +            after_response: A sync or async function called after the response has been awaited. It receives the +                :class:`Request <.connection.Request>` object and should not return any values. +            background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or +                :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. +                Defaults to ``None``. +            before_request: A sync or async function called immediately before calling the route handler. Receives +                the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, +                bypassing the route handler. +            cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number +                of seconds (e.g. ``120``) to cache the response. +            cache_control: A ``cache-control`` header of type +                :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. +            cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization +                of the cache key if caching is configured on the application level. +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            http_method: An :class:`http method string <.types.Method>`, a member of the enum +                :class:`HttpMethod <litestar.enums.HttpMethod>` or a list of these that correlates to the methods the +                route handler function should handle. +            media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a +                valid IANA Media-Type. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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>`. +            request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's +                default request. +            response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's +                default response. +            response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. +            response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` +                instances. +            responses: A mapping of additional status codes and a description of their expected content. +                This information will be included in the OpenAPI schema +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. +            status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and +                ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. +            sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the +                main event loop. This has an effect only for sync handler functions. See using sync handler functions. +            content_encoding: A string describing the encoding of the content, e.g. ``base64``. +            content_media_type: A string designating the media-type of the content, e.g. ``image/png``. +            deprecated:  A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. +            description: Text used for the route's schema description section. +            include_in_schema: A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema. +            operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. +            operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. +            raises:  A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. +                This list should describe all exceptions raised within the route handler's function/method. The Litestar +                ValidationException will be added automatically for the schema if any validation is involved. +            response_description: Text used for the route's response schema description section. +            security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. +            summary: Text used for the route's schema summary section. +            tags: A sequence of string tags that will be appended to the OpenAPI schema. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +        """ +        if "http_method" in kwargs: +            raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) +        super().__init__( +            after_request=after_request, +            after_response=after_response, +            background=background, +            before_request=before_request, +            cache=cache, +            cache_control=cache_control, +            cache_key_builder=cache_key_builder, +            content_encoding=content_encoding, +            content_media_type=content_media_type, +            dependencies=dependencies, +            deprecated=deprecated, +            description=description, +            dto=dto, +            exception_handlers=exception_handlers, +            etag=etag, +            guards=guards, +            http_method=HttpMethod.PUT, +            include_in_schema=include_in_schema, +            media_type=media_type, +            middleware=middleware, +            name=name, +            operation_class=operation_class, +            operation_id=operation_id, +            opt=opt, +            path=path, +            raises=raises, +            request_class=request_class, +            response_class=response_class, +            response_cookies=response_cookies, +            response_description=response_description, +            response_headers=response_headers, +            responses=responses, +            return_dto=return_dto, +            security=security, +            signature_namespace=signature_namespace, +            status_code=status_code, +            summary=summary, +            sync_to_thread=sync_to_thread, +            tags=tags, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            **kwargs, +        ) diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__init__.py b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__init__.py new file mode 100644 index 0000000..5b24734 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__init__.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from litestar.handlers.websocket_handlers.listener import ( +    WebsocketListener, +    WebsocketListenerRouteHandler, +    websocket_listener, +) +from litestar.handlers.websocket_handlers.route_handler import WebsocketRouteHandler, websocket + +__all__ = ( +    "WebsocketListener", +    "WebsocketListenerRouteHandler", +    "WebsocketRouteHandler", +    "websocket", +    "websocket_listener", +) diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/__init__.cpython-311.pycBinary files differ new file mode 100644 index 0000000..f6d1115 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/_utils.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/_utils.cpython-311.pycBinary files differ new file mode 100644 index 0000000..c5ae4c8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/_utils.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/listener.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/listener.cpython-311.pycBinary files differ new file mode 100644 index 0000000..38b8219 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/listener.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/route_handler.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/route_handler.cpython-311.pycBinary files differ new file mode 100644 index 0000000..0e92ccd --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/__pycache__/route_handler.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/_utils.py b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/_utils.py new file mode 100644 index 0000000..bcd90ac --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/_utils.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from functools import wraps +from inspect import Parameter, Signature +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict + +from msgspec.json import Encoder as JsonEncoder + +from litestar.di import Provide +from litestar.serialization import decode_json +from litestar.types.builtin_types import NoneType +from litestar.utils import ensure_async_callable +from litestar.utils.helpers import unwrap_partial + +if TYPE_CHECKING: +    from litestar import WebSocket +    from litestar.handlers.websocket_handlers.listener import WebsocketListenerRouteHandler +    from litestar.types import AnyCallable +    from litestar.utils.signature import ParsedSignature + + +def create_handle_receive(listener: WebsocketListenerRouteHandler) -> Callable[[WebSocket], Coroutine[Any, None, None]]: +    if data_dto := listener.resolve_data_dto(): + +        async def handle_receive(socket: WebSocket) -> Any: +            received_data = await socket.receive_data(mode=listener._receive_mode) +            return data_dto(socket).decode_bytes( +                received_data.encode("utf-8") if isinstance(received_data, str) else received_data +            ) + +    elif listener.parsed_data_field and listener.parsed_data_field.annotation is str: + +        async def handle_receive(socket: WebSocket) -> Any: +            received_data = await socket.receive_data(mode=listener._receive_mode) +            return received_data.decode("utf-8") if isinstance(received_data, bytes) else received_data + +    elif listener.parsed_data_field and listener.parsed_data_field.annotation is bytes: + +        async def handle_receive(socket: WebSocket) -> Any: +            received_data = await socket.receive_data(mode=listener._receive_mode) +            return received_data.encode("utf-8") if isinstance(received_data, str) else received_data + +    else: + +        async def handle_receive(socket: WebSocket) -> Any: +            received_data = await socket.receive_data(mode=listener._receive_mode) +            return decode_json(value=received_data, type_decoders=socket.route_handler.resolve_type_decoders()) + +    return handle_receive + + +def create_handle_send( +    listener: WebsocketListenerRouteHandler, +) -> Callable[[WebSocket, Any], Coroutine[None, None, None]]: +    json_encoder = JsonEncoder(enc_hook=listener.default_serializer) + +    if return_dto := listener.resolve_return_dto(): + +        async def handle_send(socket: WebSocket, data: Any) -> None: +            encoded_data = return_dto(socket).data_to_encodable_type(data) +            data = json_encoder.encode(encoded_data) +            await socket.send_data(data=data, mode=listener._send_mode) + +    elif listener.parsed_return_field.is_subclass_of((str, bytes)) or ( +        listener.parsed_return_field.is_optional and listener.parsed_return_field.has_inner_subclass_of((str, bytes)) +    ): + +        async def handle_send(socket: WebSocket, data: Any) -> None: +            await socket.send_data(data=data, mode=listener._send_mode) + +    else: + +        async def handle_send(socket: WebSocket, data: Any) -> None: +            data = json_encoder.encode(data) +            await socket.send_data(data=data, mode=listener._send_mode) + +    return handle_send + + +class ListenerHandler: +    __slots__ = ("_can_send_data", "_fn", "_listener", "_pass_socket") + +    def __init__( +        self, +        listener: WebsocketListenerRouteHandler, +        fn: AnyCallable, +        parsed_signature: ParsedSignature, +        namespace: dict[str, Any], +    ) -> None: +        self._can_send_data = not parsed_signature.return_type.is_subclass_of(NoneType) +        self._fn = ensure_async_callable(fn) +        self._listener = listener +        self._pass_socket = "socket" in parsed_signature.parameters + +    async def __call__( +        self, +        *args: Any, +        socket: WebSocket, +        connection_lifespan_dependencies: Dict[str, Any],  # noqa: UP006 +        **kwargs: Any, +    ) -> None: +        lifespan_mananger = self._listener._connection_lifespan or self._listener.default_connection_lifespan +        handle_send = self._listener.resolve_send_handler() if self._can_send_data else None +        handle_receive = self._listener.resolve_receive_handler() + +        if self._pass_socket: +            kwargs["socket"] = socket + +        async with lifespan_mananger(**connection_lifespan_dependencies): +            while True: +                received_data = await handle_receive(socket) +                data = await self._fn(*args, data=received_data, **kwargs) +                if handle_send: +                    await handle_send(socket, data) + + +def create_handler_signature(callback_signature: Signature) -> Signature: +    """Creates a :class:`Signature` for the handler function for signature modelling. + +    This is required for two reasons: + +        1. the :class:`.handlers.WebsocketHandler` signature model cannot contain the ``data`` parameter, which is +            required for :class:`.handlers.websocket_listener` handlers. +        2. the :class;`.handlers.WebsocketHandler` signature model must include the ``socket`` parameter, which is +            optional for :class:`.handlers.websocket_listener` handlers. + +    Args: +        callback_signature: The :class:`Signature` of the listener callback. + +    Returns: +        The :class:`Signature` for the listener callback as required for signature modelling. +    """ +    new_params = [p for p in callback_signature.parameters.values() if p.name != "data"] +    if "socket" not in callback_signature.parameters: +        new_params.append(Parameter(name="socket", kind=Parameter.KEYWORD_ONLY, annotation="WebSocket")) + +    new_params.append( +        Parameter(name="connection_lifespan_dependencies", kind=Parameter.KEYWORD_ONLY, annotation="Dict[str, Any]") +    ) + +    return callback_signature.replace(parameters=new_params) + + +def create_stub_dependency(src: AnyCallable) -> Provide: +    """Create a stub dependency, accepting any kwargs defined in ``src``, and +    wrap it in ``Provide`` +    """ +    src = unwrap_partial(src) + +    @wraps(src) +    async def stub(**kwargs: Any) -> Dict[str, Any]:  # noqa: UP006 +        return kwargs + +    return Provide(stub) diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/listener.py b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/listener.py new file mode 100644 index 0000000..86fefc9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/listener.py @@ -0,0 +1,417 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from contextlib import AbstractAsyncContextManager, asynccontextmanager +from typing import ( +    TYPE_CHECKING, +    Any, +    AsyncGenerator, +    Callable, +    Dict, +    Mapping, +    Optional, +    cast, +    overload, +) + +from litestar._signature import SignatureModel +from litestar.connection import WebSocket +from litestar.exceptions import ImproperlyConfiguredException, WebSocketDisconnect +from litestar.types import ( +    AnyCallable, +    Dependencies, +    Empty, +    EmptyType, +    ExceptionHandler, +    Guard, +    Middleware, +    TypeEncodersMap, +) +from litestar.utils import ensure_async_callable +from litestar.utils.signature import ParsedSignature, get_fn_type_hints + +from ._utils import ( +    ListenerHandler, +    create_handle_receive, +    create_handle_send, +    create_handler_signature, +    create_stub_dependency, +) +from .route_handler import WebsocketRouteHandler + +if TYPE_CHECKING: +    from typing import Coroutine + +    from typing_extensions import Self + +    from litestar import Router +    from litestar.dto import AbstractDTO +    from litestar.types.asgi_types import WebSocketMode +    from litestar.types.composite_types import TypeDecodersSequence + +__all__ = ("WebsocketListener", "WebsocketListenerRouteHandler", "websocket_listener") + + +class WebsocketListenerRouteHandler(WebsocketRouteHandler): +    """A websocket listener that automatically accepts a connection, handles disconnects, +    invokes a callback function every time new data is received and sends any data +    returned +    """ + +    __slots__ = { +        "connection_accept_handler": "Callback to accept a WebSocket connection. By default, calls WebSocket.accept", +        "on_accept": "Callback invoked after a WebSocket connection has been accepted", +        "on_disconnect": "Callback invoked after a WebSocket connection has been closed", +        "weboscket_class": "WebSocket class", +        "_connection_lifespan": None, +        "_handle_receive": None, +        "_handle_send": None, +        "_receive_mode": None, +        "_send_mode": None, +    } + +    @overload +    def __init__( +        self, +        path: str | list[str] | None = None, +        *, +        connection_lifespan: Callable[..., AbstractAsyncContextManager[Any]] | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, +        guards: list[Guard] | None = None, +        middleware: list[Middleware] | None = None, +        receive_mode: WebSocketMode = "text", +        send_mode: WebSocketMode = "text", +        name: str | None = None, +        opt: dict[str, Any] | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        websocket_class: type[WebSocket] | None = None, +        **kwargs: Any, +    ) -> None: ... + +    @overload +    def __init__( +        self, +        path: str | list[str] | None = None, +        *, +        connection_accept_handler: Callable[[WebSocket], Coroutine[Any, Any, None]] = WebSocket.accept, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, +        guards: list[Guard] | None = None, +        middleware: list[Middleware] | None = None, +        receive_mode: WebSocketMode = "text", +        send_mode: WebSocketMode = "text", +        name: str | None = None, +        on_accept: AnyCallable | None = None, +        on_disconnect: AnyCallable | None = None, +        opt: dict[str, Any] | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        websocket_class: type[WebSocket] | None = None, +        **kwargs: Any, +    ) -> None: ... + +    def __init__( +        self, +        path: str | list[str] | None = None, +        *, +        connection_accept_handler: Callable[[WebSocket], Coroutine[Any, Any, None]] = WebSocket.accept, +        connection_lifespan: Callable[..., AbstractAsyncContextManager[Any]] | None = None, +        dependencies: Dependencies | None = None, +        dto: type[AbstractDTO] | None | EmptyType = Empty, +        exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, +        guards: list[Guard] | None = None, +        middleware: list[Middleware] | None = None, +        receive_mode: WebSocketMode = "text", +        send_mode: WebSocketMode = "text", +        name: str | None = None, +        on_accept: AnyCallable | None = None, +        on_disconnect: AnyCallable | None = None, +        opt: dict[str, Any] | None = None, +        return_dto: type[AbstractDTO] | None | EmptyType = Empty, +        signature_namespace: Mapping[str, Any] | None = None, +        type_decoders: TypeDecodersSequence | None = None, +        type_encoders: TypeEncodersMap | None = None, +        websocket_class: type[WebSocket] | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``WebsocketRouteHandler`` + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults +                to ``/`` +            connection_accept_handler: A callable that accepts a :class:`WebSocket <.connection.WebSocket>` instance +                and returns a coroutine that when awaited, will accept the connection. Defaults to ``WebSocket.accept``. +            connection_lifespan: An asynchronous context manager, handling the lifespan of the connection. By default, +                it calls the ``connection_accept_handler``, ``on_connect`` and ``on_disconnect``. Can request any +                dependencies, for example the :class:`WebSocket <.connection.WebSocket>` connection +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and +                validation of request data. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            receive_mode: Websocket mode to receive data in, either `text` or `binary`. +            send_mode: Websocket mode to receive data in, either `text` or `binary`. +            name: A string identifying the route handler. +            on_accept: Callback invoked after a connection has been accepted. Can request any dependencies, for example +                the :class:`WebSocket <.connection.WebSocket>` connection +            on_disconnect: Callback invoked after a connection has been closed. Can request any dependencies, for +                example the :class:`WebSocket <.connection.WebSocket>` connection +            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>`. +            return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing +                outbound response data. +            signature_namespace: A mapping of names to types for use in forward reference resolution during signature +                modelling. +            type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +                hook for deserialization. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +            websocket_class: A custom subclass of :class:`WebSocket <.connection.WebSocket>` to be used as route handler's +                default websocket class. +        """ +        if connection_lifespan and any([on_accept, on_disconnect, connection_accept_handler is not WebSocket.accept]): +            raise ImproperlyConfiguredException( +                "connection_lifespan can not be used with connection hooks " +                "(on_accept, on_disconnect, connection_accept_handler)", +            ) + +        self._receive_mode: WebSocketMode = receive_mode +        self._send_mode: WebSocketMode = send_mode +        self._connection_lifespan = connection_lifespan +        self._send_handler: Callable[[WebSocket, Any], Coroutine[None, None, None]] | EmptyType = Empty +        self._receive_handler: Callable[[WebSocket], Any] | EmptyType = Empty + +        self.connection_accept_handler = connection_accept_handler +        self.on_accept = ensure_async_callable(on_accept) if on_accept else None +        self.on_disconnect = ensure_async_callable(on_disconnect) if on_disconnect else None +        self.type_decoders = type_decoders +        self.type_encoders = type_encoders +        self.websocket_class = websocket_class + +        listener_dependencies = dict(dependencies or {}) + +        listener_dependencies["connection_lifespan_dependencies"] = create_stub_dependency( +            connection_lifespan or self.default_connection_lifespan +        ) + +        if self.on_accept: +            listener_dependencies["on_accept_dependencies"] = create_stub_dependency(self.on_accept) + +        if self.on_disconnect: +            listener_dependencies["on_disconnect_dependencies"] = create_stub_dependency(self.on_disconnect) + +        super().__init__( +            path=path, +            dependencies=listener_dependencies, +            exception_handlers=exception_handlers, +            guards=guards, +            middleware=middleware, +            name=name, +            opt=opt, +            signature_namespace=signature_namespace, +            dto=dto, +            return_dto=return_dto, +            type_decoders=type_decoders, +            type_encoders=type_encoders, +            websocket_class=websocket_class, +            **kwargs, +        ) + +    def __call__(self, fn: AnyCallable) -> Self: +        parsed_signature = ParsedSignature.from_fn(fn, self.resolve_signature_namespace()) + +        if "data" not in parsed_signature.parameters: +            raise ImproperlyConfiguredException("Websocket listeners must accept a 'data' parameter") + +        for param in ("request", "body"): +            if param in parsed_signature.parameters: +                raise ImproperlyConfiguredException(f"The {param} kwarg is not supported with websocket listeners") + +        # we are manipulating the signature of the decorated function below, so we must store the original values for +        # use elsewhere. +        self._parsed_return_field = parsed_signature.return_type +        self._parsed_data_field = parsed_signature.parameters.get("data") +        self._parsed_fn_signature = ParsedSignature.from_signature( +            create_handler_signature(parsed_signature.original_signature), +            fn_type_hints={ +                **get_fn_type_hints(fn, namespace=self.resolve_signature_namespace()), +                **get_fn_type_hints(ListenerHandler.__call__, namespace=self.resolve_signature_namespace()), +            }, +        ) + +        return super().__call__( +            ListenerHandler( +                listener=self, fn=fn, parsed_signature=parsed_signature, namespace=self.resolve_signature_namespace() +            ) +        ) + +    def _validate_handler_function(self) -> None: +        """Validate the route handler function once it's set by inspecting its return annotations.""" +        # validation occurs in the call method + +    @property +    def signature_model(self) -> type[SignatureModel]: +        """Get the signature model for the route handler. + +        Returns: +            A signature model for the route handler. + +        """ +        if self._signature_model is Empty: +            self._signature_model = SignatureModel.create( +                dependency_name_set=self.dependency_name_set, +                fn=cast("AnyCallable", self.fn), +                parsed_signature=self.parsed_fn_signature, +                type_decoders=self.resolve_type_decoders(), +            ) +        return self._signature_model + +    @asynccontextmanager +    async def default_connection_lifespan( +        self, +        socket: WebSocket, +        on_accept_dependencies: Optional[Dict[str, Any]] = None,  # noqa: UP006, UP007 +        on_disconnect_dependencies: Optional[Dict[str, Any]] = None,  # noqa: UP006, UP007 +    ) -> AsyncGenerator[None, None]: +        """Handle the connection lifespan of a :class:`WebSocket <.connection.WebSocket>`. + +        Args: +            socket: The :class:`WebSocket <.connection.WebSocket>` connection +            on_accept_dependencies: Dependencies requested by the :attr:`on_accept` hook +            on_disconnect_dependencies: Dependencies requested by the :attr:`on_disconnect` hook + +        By, default this will + +            - Call :attr:`connection_accept_handler` to accept a connection +            - Call :attr:`on_accept` if defined after a connection has been accepted +            - Call :attr:`on_disconnect` upon leaving the context +        """ +        await self.connection_accept_handler(socket) + +        if self.on_accept: +            await self.on_accept(**(on_accept_dependencies or {})) + +        try: +            yield +        except WebSocketDisconnect: +            pass +        finally: +            if self.on_disconnect: +                await self.on_disconnect(**(on_disconnect_dependencies or {})) + +    def resolve_receive_handler(self) -> Callable[[WebSocket], Any]: +        if self._receive_handler is Empty: +            self._receive_handler = create_handle_receive(self) +        return self._receive_handler + +    def resolve_send_handler(self) -> Callable[[WebSocket, Any], Coroutine[None, None, None]]: +        if self._send_handler is Empty: +            self._send_handler = create_handle_send(self) +        return self._send_handler + + +websocket_listener = WebsocketListenerRouteHandler + + +class WebsocketListener(ABC): +    path: str | list[str] | None = None +    """A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/``""" +    dependencies: Dependencies | None = None +    """A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances.""" +    dto: type[AbstractDTO] | None | EmptyType = Empty +    """:class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and validation of request data""" +    exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None +    """A mapping of status codes and/or exception types to handler functions.""" +    guards: list[Guard] | None = None +    """A sequence of :class:`Guard <.types.Guard>` callables.""" +    middleware: list[Middleware] | None = None +    """A sequence of :class:`Middleware <.types.Middleware>`.""" +    on_accept: AnyCallable | None = None +    """Called after a :class:`WebSocket <.connection.WebSocket>` connection has been accepted. Can receive any dependencies""" +    on_disconnect: AnyCallable | None = None +    """Called after a :class:`WebSocket <.connection.WebSocket>` connection has been disconnected. Can receive any dependencies""" +    receive_mode: WebSocketMode = "text" +    """:class:`WebSocket <.connection.WebSocket>` mode to receive data in, either ``text`` or ``binary``.""" +    send_mode: WebSocketMode = "text" +    """Websocket mode to send data in, either `text` or `binary`.""" +    name: str | None = None +    """A string identifying the route handler.""" +    opt: dict[str, Any] | None = None +    """ +    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>`. +    """ +    return_dto: type[AbstractDTO] | None | EmptyType = Empty +    """:class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing outbound response data.""" +    signature_namespace: Mapping[str, Any] | None = None +    """ +    A mapping of names to types for use in forward reference resolution during signature modelling. +    """ +    type_decoders: TypeDecodersSequence | None = None +    """ +    type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec +        hook for deserialization. +    """ +    type_encoders: TypeEncodersMap | None = None +    """ +    type_encoders: A mapping of types to callables that transform them into types supported for serialization. +    """ +    websocket_class: type[WebSocket] | None = None +    """ +    websocket_class: A custom subclass of :class:`WebSocket <.connection.WebSocket>` to be used as route handler's +    default websocket class. +    """ + +    def __init__(self, owner: Router) -> None: +        """Initialize a WebsocketListener instance. + +        Args: +            owner: The :class:`Router <.router.Router>` instance that owns this listener. +        """ +        self._owner = owner + +    def to_handler(self) -> WebsocketListenerRouteHandler: +        handler = WebsocketListenerRouteHandler( +            dependencies=self.dependencies, +            dto=self.dto, +            exception_handlers=self.exception_handlers, +            guards=self.guards, +            middleware=self.middleware, +            send_mode=self.send_mode, +            receive_mode=self.receive_mode, +            name=self.name, +            on_accept=self.on_accept, +            on_disconnect=self.on_disconnect, +            opt=self.opt, +            path=self.path, +            return_dto=self.return_dto, +            signature_namespace=self.signature_namespace, +            type_decoders=self.type_decoders, +            type_encoders=self.type_encoders, +            websocket_class=self.websocket_class, +        )(self.on_receive) +        handler.owner = self._owner +        return handler + +    @abstractmethod +    def on_receive(self, *args: Any, **kwargs: Any) -> Any: +        """Called after data has been received from the WebSocket. + +        This should take a ``data`` argument, receiving the processed WebSocket data, +        and can additionally include handler dependencies such as ``state``, or other +        regular dependencies. + +        Data returned from this function will be serialized and sent via the socket +        according to handler configuration. +        """ +        raise NotImplementedError diff --git a/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/route_handler.py b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/route_handler.py new file mode 100644 index 0000000..edb49c3 --- /dev/null +++ b/venv/lib/python3.11/site-packages/litestar/handlers/websocket_handlers/route_handler.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Mapping + +from litestar.connection import WebSocket +from litestar.exceptions import ImproperlyConfiguredException +from litestar.handlers import BaseRouteHandler +from litestar.types.builtin_types import NoneType +from litestar.utils.predicates import is_async_callable + +if TYPE_CHECKING: +    from litestar.types import Dependencies, ExceptionHandler, Guard, Middleware + + +class WebsocketRouteHandler(BaseRouteHandler): +    """Websocket route handler decorator. + +    Use this decorator to decorate websocket handler functions. +    """ + +    def __init__( +        self, +        path: str | list[str] | None = None, +        *, +        dependencies: Dependencies | None = None, +        exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, +        guards: list[Guard] | None = None, +        middleware: list[Middleware] | None = None, +        name: str | None = None, +        opt: dict[str, Any] | None = None, +        signature_namespace: Mapping[str, Any] | None = None, +        websocket_class: type[WebSocket] | None = None, +        **kwargs: Any, +    ) -> None: +        """Initialize ``WebsocketRouteHandler`` + +        Args: +            path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults +                to ``/`` +            dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. +            exception_handlers: A mapping of status codes and/or exception types to handler functions. +            guards: A sequence of :class:`Guard <.types.Guard>` callables. +            middleware: A sequence of :class:`Middleware <.types.Middleware>`. +            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. +            type_encoders: A mapping of types to callables that transform them into types supported for serialization. +            **kwargs: Any additional kwarg - will be set in the opt dictionary. +            websocket_class: A custom subclass of :class:`WebSocket <.connection.WebSocket>` to be used as route handler's +                default websocket class. +        """ +        self.websocket_class = websocket_class + +        super().__init__( +            path=path, +            dependencies=dependencies, +            exception_handlers=exception_handlers, +            guards=guards, +            middleware=middleware, +            name=name, +            opt=opt, +            signature_namespace=signature_namespace, +            **kwargs, +        ) + +    def resolve_websocket_class(self) -> type[WebSocket]: +        """Return the closest custom WebSocket class in the owner graph or the default Websocket class. + +        This method is memoized so the computation occurs only once. + +        Returns: +            The default :class:`WebSocket <.connection.WebSocket>` class for the route handler. +        """ +        return next( +            (layer.websocket_class for layer in reversed(self.ownership_layers) if layer.websocket_class is not None), +            WebSocket, +        ) + +    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("Websocket handler functions should return 'None'") + +        if "socket" not in self.parsed_fn_signature.parameters: +            raise ImproperlyConfiguredException("Websocket handlers must set a 'socket' kwarg") + +        for param in ("request", "body", "data"): +            if param in self.parsed_fn_signature.parameters: +                raise ImproperlyConfiguredException(f"The {param} kwarg is not supported with websocket handlers") + +        if not is_async_callable(self.fn): +            raise ImproperlyConfiguredException("Functions decorated with 'websocket' must be async functions") + + +websocket = WebsocketRouteHandler | 
