Source code for litestar.response.template

from __future__ import annotations

import itertools
from mimetypes import guess_type
from pathlib import PurePath
from typing import TYPE_CHECKING, Any, cast

from litestar.enums import MediaType
from litestar.exceptions import ImproperlyConfiguredException
from litestar.response.base import ASGIResponse, Response
from litestar.status_codes import HTTP_200_OK
from litestar.utils.empty import value_or_default
from litestar.utils.scope.state import ScopeState

if TYPE_CHECKING:
    from collections.abc import Iterable

    from litestar.background_tasks import BackgroundTask, BackgroundTasks
    from litestar.connection import Request
    from litestar.datastructures import Cookie
    from litestar.types import ResponseCookies, TypeEncodersMap

__all__ = ("Template",)


[docs] class Template(Response[bytes]): """Template-based response, rendering a given template into a bytes string.""" __slots__ = ( "context", "template_name", "template_str", )
[docs] def __init__( self, template_name: str | None = None, *, template_str: str | None = None, background: BackgroundTask | BackgroundTasks | None = None, context: dict[str, Any] | None = None, cookies: ResponseCookies | None = None, encoding: str = "utf-8", headers: dict[str, Any] | None = None, media_type: MediaType | str | None = None, status_code: int = HTTP_200_OK, ) -> None: """Handle the rendering of a given template into a bytes string. Args: template_name: Path-like name for the template to be rendered, e.g. ``index.html``. template_str: A string representing the template, e.g. ``tmpl = "Hello <strong>World</strong>"``. background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. Defaults to ``None``. context: A dictionary of key/value pairs to be passed to the temple engine's render method. cookies: A list of :class:`Cookie <.datastructures.Cookie>` instances to be set under the response ``Set-Cookie`` header. encoding: Content encoding headers: A string keyed dictionary of response headers. Header keys are insensitive. media_type: A string or member of the :class:`MediaType <.enums.MediaType>` enum. If not set, try to infer the media type based on the template name. If this fails, fall back to ``text/plain``. status_code: A value for the response HTTP status code. """ if not (template_name or template_str): raise ValueError("Either template_name or template_str must be provided.") if template_name and template_str: raise ValueError("Either template_name or template_str must be provided, not both.") super().__init__( background=background, content=b"", cookies=cookies, encoding=encoding, headers=headers, media_type=media_type, status_code=status_code, ) self.context = context or {} self.template_name = template_name self.template_str = template_str
[docs] def create_template_context(self, request: Request) -> dict[str, Any]: """Create a context object for the template. Args: request: A :class:`Request <.connection.Request>` instance. Returns: A dictionary holding the template context """ csrf_token = value_or_default(ScopeState.from_scope(request.scope).csrf_token, "") return { **self.context, "request": request, "csrf_input": f'<input type="hidden" name="_csrf_token" value="{csrf_token}" />', }
[docs] def to_asgi_response( self, request: Request, *, background: BackgroundTask | BackgroundTasks | None = None, cookies: Iterable[Cookie] | None = None, headers: dict[str, str] | None = None, is_head_response: bool = False, media_type: MediaType | str | None = None, status_code: int | None = None, type_encoders: TypeEncodersMap | None = None, ) -> ASGIResponse: if not (template_engine := request.app.template_engine): raise ImproperlyConfiguredException("Template engine is not configured") headers = {**headers, **self.headers} if headers is not None else self.headers cookies = self.cookies if cookies is None else itertools.chain(self.cookies, cookies) media_type = self.media_type or media_type if not media_type: if self.template_name: suffixes = PurePath(self.template_name).suffixes for suffix in suffixes: if _type := guess_type(f"name{suffix}")[0]: media_type = _type break else: media_type = MediaType.TEXT else: media_type = MediaType.HTML context = self.create_template_context(request) if self.template_str is not None: body = template_engine.render_string(self.template_str, context) else: # cast to str b/c we know that either template_name cannot be None if template_str is None template = template_engine.get_template(cast("str", self.template_name)) body = template.render(**context).encode(self.encoding) return ASGIResponse( background=self.background or background, body=body, content_length=None, cookies=cookies, encoding=self.encoding, headers=headers, is_head_response=is_head_response, media_type=media_type, status_code=self.status_code or status_code, )