Skip to content

0.9.0 to 0.10.0

The 0.10.0 release replaces the old layout.html (Python format string) layout support with first-class Jinja layout support. holm now ships its own JinjaTemplate component and registers a Jinja2Templates instance for you when it owns the htmy renderer, so Jinja layouts and templates work with zero configuration.

Summary of changes

  • layout.html files are no longer recognized. Rename them to layout.jinja.
  • The str_to_layout argument of App() and the holm.utils module (including snippet_to_layout) have been removed.
  • Default slots are now configured with the layout_slots argument of App(), not through a custom string-to-layout converter.
  • holm now exports JinjaTemplate for rendering Jinja templates as htmy components anywhere in your application.

Rename and rewrite layouts

For each layout.html, rename the file to layout.jinja and translate the template syntax:

Old (layout.html) New (layout.jinja)
{metadata[title]} {{ metadata.title }}
{request.method} {{ request.method }}
<!-- slot[children] --> {{ slots.children }}
<!-- slot[details] --> {{ slots.details }}

The default slot is still children. The wrapped page or layout is automatically placed in {{ slots.children }} unless it returns a mapping, in which case the mapping keys become the slot names. This behavior did not change.

metadata and request are still available. route_params is newly available and exposes resolved FastAPI dependencies, e.g. {{ route_params.current_user }}.

layout.py continues to take precedence when both layout.py and layout.jinja exist in the same package.

Drop custom str_to_layout

Previously you could plug tools like Jinja2 into HTML layouts by passing str_to_layout to App(). This is gone. Jinja is now the built-in and only string-based layout format.

If your custom converter only translated the template syntax, you can delete it: layout.jinja files are picked up automatically.

If your converter provided default slots (e.g. a navbar on every page) using snippet_to_layout(..., default_slot_mapping=...), switch to layout_slots:

from holm import App

from .navbar import navbar

app = App(layout_slots={"navbar": navbar})

Slots configured this way are injected into the htmy rendering context using htmy.jinja.DefaultSlots and are available in every Jinja layout as {{ slots.navbar }}. Slots returned explicitly by a page or layout take precedence over layout_slots for the same name.

Auto-escaping

layout.html used str.format(), which does no HTML escaping by default.

When holm creates the htmy renderer, it enables Jinja autoescaping for the jinja2.select_autoescape() default extensions (html, htm, xml) plus the .jinja extension.

  • layout.jinja files and any .jinja template rendered via holm.JinjaTemplate are auto-escaped.
  • Slots (e.g. {{ slots.children }}) are pre-rendered by htmy and are marked as safe, so they are never double-escaped.

If you provide your own htmy renderer, configure your Jinja2Templates with the same autoescape setting (jinja2.select_autoescape(["html", "htm", "xml", "jinja"])) to get the same behavior.

Custom htmy renderer

When holm creates the htmy renderer (the htmy argument to App() is None), it automatically registers a htmy.jinja.JinjaTemplates instance in the default rendering. Jinja layouts and JinjaTemplate components then work out of the box.

If you pass your own htmy to App(), holm does not modify it. You must add a pre-configured htmy.jinja.JinjaTemplates to its default context yourself, and ensure the templates root directory is the Python import root directory (the directory containing your app package), so template names resolve the same way as for holm-owned renderers.

Rendering Jinja anywhere

To render a Jinja template as an htmy component outside a layout, use holm.JinjaTemplate:

from htmy import Component

from holm import JinjaTemplate


def page() -> Component:
    return JinjaTemplate(
        "my_app/components/card.html",
        jinja_context={"title": "Hello"},
    )

You can of course keep Jinja templates outside of the app package, for example in a templates directory next to your app package, in which case the template name would be for example templates/card.html.

JinjaTemplate looks up the template source from the htmy rendering context, so it works whenever a JinjaTemplates instance is available.