Source code for jinja2_fragments.starlette

"""Starlette integration for jinja2-fragments.

This module provides Starlette-compatible template rendering with support for
rendering individual Jinja2 blocks or multiple blocks from templates.
"""

from __future__ import annotations

import typing
import warnings

try:
    from starlette.background import BackgroundTask
    from starlette.requests import Request
    from starlette.responses import HTMLResponse, Response
    from starlette.templating import Jinja2Templates, _TemplateResponse
except ModuleNotFoundError as e:
    raise ModuleNotFoundError(
        "Install Starlette to use jinja2_fragments.starlette"
    ) from e

from . import render_block, render_blocks

__all__ = ["InvalidContextError", "Jinja2Blocks"]


[docs] class InvalidContextError(Exception): pass
[docs] class Jinja2Blocks(Jinja2Templates): @typing.overload def TemplateResponse( self, request: Request, name: str, context: dict[str, typing.Any] | None = None, status_code: int = 200, headers: typing.Mapping[str, str] | None = None, media_type: str | None = None, background: BackgroundTask | None = None, *, block_names: list[str] = [], ) -> Response: ... @typing.overload def TemplateResponse( self, request: Request, name: str, context: dict[str, typing.Any] | None = None, status_code: int = 200, headers: typing.Mapping[str, str] | None = None, media_type: str | None = None, background: BackgroundTask | None = None, *, block_name: str | None = None, ) -> Response: ... @typing.overload def TemplateResponse( self, name: str, context: dict[str, typing.Any], status_code: int = 200, headers: typing.Mapping[str, str] | None = None, media_type: str | None = None, background: BackgroundTask | None = None, *, block_names: list[str] = [], ) -> Response: # Deprecated, request should be given as first argument ... @typing.overload def TemplateResponse( self, name: str, context: dict[str, typing.Any], status_code: int = 200, headers: typing.Mapping[str, str] | None = None, media_type: str | None = None, background: BackgroundTask | None = None, *, block_name: str | None = None, ) -> Response: # Deprecated, request should be given as first argument ...
[docs] def TemplateResponse(self, *args: typing.Any, **kwargs: typing.Any) -> Response: """ Generate an HTTP response with a rendered template. This method renders a specified template with the given context and produces an HTTP response that includes the rendered content, as well as an appropriate status code. Args: template_name: The name of the template file to be rendered. context: A dictionary of variables to be made available in the template. status_code: The HTTP status code for the response. Defaults to 200 (OK). Returns: A Starlette-compatible HTTP `Response` object containing the rendered template. Raises: InvalidContextError: If the provided context is invalid or malformed. TemplateNotFound: If the specified template cannot be located. """ ( request, name, context, status_code, headers, media_type, background, ) = self._parse_template_response_args(*args, **kwargs) if not isinstance(context, dict): raise TypeError("context must be a dict") context.setdefault("request", request) for context_processor in self.context_processors: context.update(context_processor(request)) template = self.get_template(name) block_name: str | None = kwargs.get("block_name", None) block_names: list[str] = kwargs.get("block_names", []) if block_name: content = render_block( self.env, name, block_name, context, ) return HTMLResponse( content=content, status_code=status_code, headers=headers, media_type=media_type, background=background, ) if block_names: content = render_blocks( self.env, name, block_names, context, ) return HTMLResponse( content=content, status_code=status_code, headers=headers, media_type=media_type, background=background, ) return _TemplateResponse( template, context, status_code=status_code, headers=headers, media_type=media_type, background=background, )
def _parse_template_response_args( self, *args: typing.Any, **kwargs: typing.Any ) -> tuple[ Request, str, dict[str, typing.Any], int, typing.Mapping[str, str] | None, str | None, BackgroundTask | None, ]: """Parse arguments for the `TemplateResponse` method. Since the order of positional arguments has changed in starlette 0.29.0, parsing the argument list has become more complex. To remain backwards compatible, the scheme (< 0.29 or >= 0.29) must be detected. """ if args and isinstance(args[0], Request) or "request" in kwargs: return self._parse_new_style_response_args(*args, **kwargs) warnings.warn( "The `name` is not the first parameter anymore. " "The first parameter should be the `Request` instance.\n" 'Replace `TemplateResponse(name, {"request": request})` by ' "`TemplateResponse(request, name)`.", DeprecationWarning, stacklevel=3, ) return self._parse_old_style_response_args(*args, **kwargs) @staticmethod def _parse_old_style_response_args( name: str, context: dict[str, typing.Any], status_code: int = 200, headers: typing.Mapping[str, str] | None = None, media_type: str | None = None, background: BackgroundTask | None = None, **extra_kwargs: typing.Any, ) -> tuple[ Request, str, dict[str, typing.Any], int, typing.Mapping[str, str] | None, str | None, BackgroundTask | None, ]: if "request" not in context: raise ValueError('context must include a "request" key') request = context["request"] return request, name, context, status_code, headers, media_type, background @staticmethod def _parse_new_style_response_args( request: Request, name: str, context: dict[str, typing.Any] | None = None, status_code: int = 200, headers: typing.Mapping[str, str] | None = None, media_type: str | None = None, background: BackgroundTask | None = None, **extra_kwargs: typing.Any, ) -> tuple[ Request, str, dict[str, typing.Any], int, typing.Mapping[str, str] | None, str | None, BackgroundTask | None, ]: if context is None: context = {} return request, name, context, status_code, headers, media_type, background