Skip to content

htmy.jinja

JinjaContextFactory = Callable[[Context], Mapping[str, Any]] module-attribute

A function that builds additional Jinja context entries from an htmy rendering context.

DefaultSlots

Bases: ContextAware

Context-aware holder of default named slots for JinjaTemplate.

The JinjaTemplate component looks up the DefaultSlots instance from the htmy rendering context. If found, JinjaTemplate renders each slot and passes the rendered values as markupsafe.Markup strings to the Jinja template.

Source code in htmy/jinja.py
class DefaultSlots(ContextAware):
    """
    Context-aware holder of default named slots for `JinjaTemplate`.

    The `JinjaTemplate` component looks up the `DefaultSlots` instance from the `htmy` rendering
    context. If found, `JinjaTemplate` renders each slot and passes the rendered values as
    `markupsafe.Markup` strings to the Jinja template.
    """

    __slots__ = ("_slots",)

    def __init__(self, slots: Mapping[str, Component] | None = None) -> None:
        """
        Initialization.

        Arguments:
            slots: Optional mapping of slot names to `htmy` components.
        """
        self._slots = {} if slots is None else slots

    @property
    def slots(self) -> Mapping[str, Component]:
        """Mapping of slot names to `htmy` components."""
        return self._slots

slots property

Mapping of slot names to htmy components.

__init__(slots=None)

Initialization.

Parameters:

Name Type Description Default
slots Mapping[str, Component] | None

Optional mapping of slot names to htmy components.

None
Source code in htmy/jinja.py
def __init__(self, slots: Mapping[str, Component] | None = None) -> None:
    """
    Initialization.

    Arguments:
        slots: Optional mapping of slot names to `htmy` components.
    """
    self._slots = {} if slots is None else slots

JinjaTemplate

Component that renders a Jinja2 template.

The template source (a JinjaTemplates instance) must be in the htmy rendering context. The looked up template source is used to load and render the template.

If default slots rendering is enabled (see use_default_slots) and DefaultSlots is found in the htmy rendering context, its slots are rendered and included in the slots context variable passed to Jinja. You can customize or filter default slots by overriding _get_default_slots(). slots passed directly to this component take precedence over DefaultSlots slots when both contain a slot with the same name.

Jinja context creation precedence: jinja_context, _build_context(), make_context, and finally the reserved slots key.

Source code in htmy/jinja.py
class JinjaTemplate:
    """
    Component that renders a Jinja2 template.

    The template source (a `JinjaTemplates` instance) must be in the `htmy` rendering context.
    The looked up template source is used to load and render the template.

    If default slots rendering is enabled (see `use_default_slots`) and `DefaultSlots` is found
    in the `htmy` rendering context, its slots are rendered and included in the `slots` context
    variable passed to Jinja. You can customize or filter default slots by overriding
    `_get_default_slots()`. `slots` passed directly to this component take precedence over
    `DefaultSlots` slots when both contain a slot with the same name.

    Jinja context creation precedence: `jinja_context`, `_build_context()`, `make_context`, and
    finally the reserved `slots` key.
    """

    __slots__ = ("_jinja_context", "_make_context", "_slots", "_template_name", "_use_default_slots")

    def __init__(
        self,
        template_name: str,
        *,
        jinja_context: Mapping[str, Any] | None = None,
        make_context: JinjaContextFactory | None = None,
        slots: Mapping[str, Component] | None = None,
        use_default_slots: bool = False,
    ) -> None:
        """
        Initialization.

        Arguments:
            template_name: The name of the template to render.
            jinja_context: Optional mapping passed to Jinja as the template context.
            make_context: Optional callable that receives the `htmy` rendering context and
                returns additional Jinja context entries. It runs after `_build_context()`,
                but before slot rendering, so it can not override the reserved `slots` key.
            slots: Optional named slots to be rendered in the template. Values are `htmy` components,
                they are rendered before being passed to Jinja as `markupsafe.Markup` to avoid
                double-escaping, content is not re-escaped by Jinja's autoescape. Slots are available
                through the `slots` key in the Jinja context and accessed as `{{ slots.<name> }}`.
            use_default_slots: Whether to render `DefaultSlots` from the `htmy` rendering context.
                Defaults to `False`, so default slots are only rendered when explicitly requested.
                Slots passed directly to this component take precedence over default slots with the
                same name. For finer control over default slots, override `_get_default_slots()`.
        """
        self._template_name = template_name
        self._jinja_context = jinja_context
        self._make_context = make_context
        self._slots: Mapping[str, Component] = {} if slots is None else slots
        self._use_default_slots = use_default_slots

    def _build_context(self, htmy_context: Context, /) -> dict[str, Any]:
        """
        Returns the Jinja rendering context for this component.

        The returned dictionary is owned by the caller, so it can be modified in place.

        Arguments:
            htmy_context: The `htmy` rendering context.
        """
        return {} if self._jinja_context is None else dict(self._jinja_context)

    def _get_default_slots(self, context: Context, /) -> Mapping[str, Component]:
        """
        Returns the default slots to render for this component.

        Override this method to narrow, extend, or suppress default slot rendering without
        relying solely on the `use_default_slots` flag. The default implementation looks up
        `DefaultSlots` from the `htmy` rendering context and returns all its slots if it
        exists, otherwise an empty mapping.

        Arguments:
            context: The `htmy` rendering context.
        """
        default_slots = DefaultSlots.from_context(context, None)
        return {} if default_slots is None else default_slots.slots

    async def htmy(self, context: Context) -> Component:
        """Renders the component."""
        templates = JinjaTemplates.from_context(context)
        renderer = RendererContext.from_context(context)

        template = templates.get_template(self._template_name)
        slots: dict[str, Markup] = {}

        jinja_context = self._build_context(context)
        if self._make_context is not None:
            jinja_context.update(self._make_context(context))

        if self._use_default_slots:
            for name, component in self._get_default_slots(context).items():
                if name in self._slots:
                    continue  # Do not waste time rendering a slot that would be overwritten

                slots[name] = Markup(await renderer.render(component, context))  # noqa: S704

        for name, component in self._slots.items():
            slots[name] = Markup(await renderer.render(component, context))  # noqa: S704

        jinja_context["slots"] = slots

        if template.environment.is_async:
            rendered = await template.render_async(**jinja_context)
        else:
            rendered = await anyio.to_thread.run_sync(lambda: template.render(**jinja_context))

        return SafeStr(rendered)

__init__(template_name, *, jinja_context=None, make_context=None, slots=None, use_default_slots=False)

Initialization.

Parameters:

Name Type Description Default
template_name str

The name of the template to render.

required
jinja_context Mapping[str, Any] | None

Optional mapping passed to Jinja as the template context.

None
make_context JinjaContextFactory | None

Optional callable that receives the htmy rendering context and returns additional Jinja context entries. It runs after _build_context(), but before slot rendering, so it can not override the reserved slots key.

None
slots Mapping[str, Component] | None

Optional named slots to be rendered in the template. Values are htmy components, they are rendered before being passed to Jinja as markupsafe.Markup to avoid double-escaping, content is not re-escaped by Jinja's autoescape. Slots are available through the slots key in the Jinja context and accessed as {{ slots.<name> }}.

None
use_default_slots bool

Whether to render DefaultSlots from the htmy rendering context. Defaults to False, so default slots are only rendered when explicitly requested. Slots passed directly to this component take precedence over default slots with the same name. For finer control over default slots, override _get_default_slots().

False
Source code in htmy/jinja.py
def __init__(
    self,
    template_name: str,
    *,
    jinja_context: Mapping[str, Any] | None = None,
    make_context: JinjaContextFactory | None = None,
    slots: Mapping[str, Component] | None = None,
    use_default_slots: bool = False,
) -> None:
    """
    Initialization.

    Arguments:
        template_name: The name of the template to render.
        jinja_context: Optional mapping passed to Jinja as the template context.
        make_context: Optional callable that receives the `htmy` rendering context and
            returns additional Jinja context entries. It runs after `_build_context()`,
            but before slot rendering, so it can not override the reserved `slots` key.
        slots: Optional named slots to be rendered in the template. Values are `htmy` components,
            they are rendered before being passed to Jinja as `markupsafe.Markup` to avoid
            double-escaping, content is not re-escaped by Jinja's autoescape. Slots are available
            through the `slots` key in the Jinja context and accessed as `{{ slots.<name> }}`.
        use_default_slots: Whether to render `DefaultSlots` from the `htmy` rendering context.
            Defaults to `False`, so default slots are only rendered when explicitly requested.
            Slots passed directly to this component take precedence over default slots with the
            same name. For finer control over default slots, override `_get_default_slots()`.
    """
    self._template_name = template_name
    self._jinja_context = jinja_context
    self._make_context = make_context
    self._slots: Mapping[str, Component] = {} if slots is None else slots
    self._use_default_slots = use_default_slots

htmy(context) async

Renders the component.

Source code in htmy/jinja.py
async def htmy(self, context: Context) -> Component:
    """Renders the component."""
    templates = JinjaTemplates.from_context(context)
    renderer = RendererContext.from_context(context)

    template = templates.get_template(self._template_name)
    slots: dict[str, Markup] = {}

    jinja_context = self._build_context(context)
    if self._make_context is not None:
        jinja_context.update(self._make_context(context))

    if self._use_default_slots:
        for name, component in self._get_default_slots(context).items():
            if name in self._slots:
                continue  # Do not waste time rendering a slot that would be overwritten

            slots[name] = Markup(await renderer.render(component, context))  # noqa: S704

    for name, component in self._slots.items():
        slots[name] = Markup(await renderer.render(component, context))  # noqa: S704

    jinja_context["slots"] = slots

    if template.environment.is_async:
        rendered = await template.render_async(**jinja_context)
    else:
        rendered = await anyio.to_thread.run_sync(lambda: template.render(**jinja_context))

    return SafeStr(rendered)

JinjaTemplateSource

Bases: Protocol

Protocol for Jinja2 template sources such as jinja2.Environment.

Source code in htmy/jinja.py
class JinjaTemplateSource(Protocol):
    """Protocol for Jinja2 template sources such as `jinja2.Environment`."""

    def get_template(self, name: str) -> Template:
        """Returns the Jinja2 template with the given name."""
        ...

get_template(name)

Returns the Jinja2 template with the given name.

Source code in htmy/jinja.py
def get_template(self, name: str) -> Template:
    """Returns the Jinja2 template with the given name."""
    ...

JinjaTemplates

Bases: ContextAware

Context-aware Jinja2 template source that can be placed in the htmy rendering context.

The JinjaTemplate component looks up the JinjaTemplates instance from the htmy rendering context to find and render templates.

Source code in htmy/jinja.py
class JinjaTemplates(ContextAware):
    """
    Context-aware Jinja2 template source that can be placed in the `htmy` rendering context.

    The `JinjaTemplate` component looks up the `JinjaTemplates` instance from the `htmy` rendering
    context to find and render templates.
    """

    __slots__ = ("_source", "_source_factory")

    def __init__(self, source: JinjaTemplateSource | Callable[[], JinjaTemplateSource]) -> None:
        """
        Initialization.

        Arguments:
            source: A Jinja2 template source (e.g. a `jinja2.Environment` instance) or a
                zero-argument factory that returns one. When a factory is provided, the
                source is created once, when first needed.
        """
        is_factory = callable(source) and not hasattr(source, "get_template")
        self._source: JinjaTemplateSource | None = None if is_factory else source  # type: ignore[assignment]
        self._source_factory: Callable[[], JinjaTemplateSource] | None = source if is_factory else None  # type: ignore[assignment]

    @property
    def source(self) -> JinjaTemplateSource:
        """
        The wrapped Jinja2 template source.

        For instances constructed with a factory, the source is built once, when first accessed.
        """
        if self._source is None:
            # self._source_factory must be a callable in this case
            self._source = self._source_factory()  # type: ignore[misc]
        return self._source

    def get_template(self, name: str) -> Template:
        """Returns the Jinja2 template with the given name."""
        return self.source.get_template(name)

source property

The wrapped Jinja2 template source.

For instances constructed with a factory, the source is built once, when first accessed.

__init__(source)

Initialization.

Parameters:

Name Type Description Default
source JinjaTemplateSource | Callable[[], JinjaTemplateSource]

A Jinja2 template source (e.g. a jinja2.Environment instance) or a zero-argument factory that returns one. When a factory is provided, the source is created once, when first needed.

required
Source code in htmy/jinja.py
def __init__(self, source: JinjaTemplateSource | Callable[[], JinjaTemplateSource]) -> None:
    """
    Initialization.

    Arguments:
        source: A Jinja2 template source (e.g. a `jinja2.Environment` instance) or a
            zero-argument factory that returns one. When a factory is provided, the
            source is created once, when first needed.
    """
    is_factory = callable(source) and not hasattr(source, "get_template")
    self._source: JinjaTemplateSource | None = None if is_factory else source  # type: ignore[assignment]
    self._source_factory: Callable[[], JinjaTemplateSource] | None = source if is_factory else None  # type: ignore[assignment]

get_template(name)

Returns the Jinja2 template with the given name.

Source code in htmy/jinja.py
def get_template(self, name: str) -> Template:
    """Returns the Jinja2 template with the given name."""
    return self.source.get_template(name)