Skip to content

Loading templates

A template loader is a class inheriting from BaseLoader. It is responsible for finding template source text given a name or identifier, and will be called upon whenever you or a tag call Environment.get_template() or await Environment.get_template_async().

To use one of the template loaders described here, pass an instance of your chosen loader as the loader argument when constructing a Liquid Environment.

Built-in loaders

Dictionary loader

DictLoader is a template loader that stores template source text in memory using a dictionary. If you're experimenting with Liquid or if all your templates are known at application startup time and they all fit in RAM, then DictLoader is a good choice.

Simply pass a dictionary mapping template names to template source text to the DictLoader constructor.

from liquid2 import DictLoader
from liquid2 import Environment

env = Environment(
    loader=DictLoader(
        {
            "index": "{% render 'header' %}\nbody\n{% render 'footer' %}",
            "header": "some header",
            "footer": "a footer",
        }
    )
)

template = env.get_template("index")
print(template.render())
output
some header
body
a footer

Caching dictionary loader

CachingDictLoader is a dictionary loader that maintains an in-memory LRU cache of parsed templates, so as to avoid parsing the same source text multiple times unnecessarily.

As well as a dictionary mapping template names to template source text, the CachingDictLoader constructor takes an optional capacity argument to control the maximum size of the cache. The default capacity is 300 templates.

from liquid2 import CachingDictLoader
from liquid2 import Environment

env = Environment(
    loader=CachingDictLoader(
        {
            "index": "{% render 'header' %}\nbody\n{% render 'footer' %}",
            "header": "some header",
            "footer": "a footer",
        }
    )
)

template = env.get_template("index")
assert env.get_template("index") is template

File system loader

FileSystemLoader is a template loader that reads source text from files on a file system. Its first argument, search_path, is a path to a folder containing Liquid templates, either as a string or pathlib.Path. search_path can also be a list of paths to search in order.

In this example, calls to Environment.get_template() will look for templates in a folder called templates relative to the current directory.

from liquid2 import Environment
from liquid2 import FileSystemLoader

env = Environment(loader=FileSystemLoader("templates/"))

If a file called index.html exists in ./templates, we could render it with {% render 'index.html' %}. To avoid having to include .html in every render tag, we can give FileSystemLoader a default file extension. It should include a leading ..

from liquid2 import Environment
from liquid2 import FileSystemLoader

env = Environment(loader=FileSystemLoader("templates/", ext=".html"))

If your templates are organized in sub folders of ./templates, you can include the relative path to a template in a render tag, {% render 'snippets/footer' %}, but you won't be allowed to escape out of ./templates. This would raise a TemplateNotFoundError.

{% render '../../path/to/private/file' %}

Caching file system loader

CachingFileSystemLoader is a file system loader that maintains an in-memory LRU cache of parsed templates, so as to avoid reading and parsing the same source text multiple times unnecessarily.

As well as search_path and ext arguments covered in the file system loader section above, CachingFileSystemLoader takes optional auto_reload and capacity arguments.

capacity is the maximum number of templates that can fit in the cache and defaults to 300 templates.

auto_reload is a flag to indicate if the template loader should check to see if each cached template has been modified since it was last loaded. If True and template source text has been modified on-disk, that source text will automatically be read and parsed again. auto_reload defaults to True.

from liquid2 import Environment
from liquid2 import CachingFileSystemLoader

loader = CachingFileSystemLoader(
    "/var/www/templates/",
    ext=".liquid",
    auto_reload=True,
    capacity=1000,
)

env = Environment(loader=loader)

Package loader

PackageLoader is a template loader that reads template source text from Python packages installed in your Python environment. You should pass the name of the package and, optionally, one or more paths to directories containing template source text within the package. The default package_path is templates.

Just like FileSystemLoader, PackageLoader accepts a default file extension, ext. This time it defaults to .liquid.

from liquid2 import Environment
from liquid2 import PackageLoader

loader = PackageLoader(
    "awesome_templates",
    package_path="path/to/templates",
    ext=".liquid",
)

env = Environment(loader=loader)

Choice loader

ChoiceLoader and CachingChoiceLoader are template loaders that delegate to a list of other template loaders. Each one is tried in turn until a template is found.

When using CachingChoiceLoader, you should probably avoid delegating to other caching loaders.

from liquid2 import CachingFileSystemLoader
from liquid2 import ChoiceLoader
from liquid2 import DictLoader
from liquid2 import Environment

base_loader = DictLoader({"foo": "some template source text"})
overlay_loader = CachingFileSystemLoader("templates/")
loader = ChoiceLoader([overlay_loader, base_loader])

env = Environment(loader=loader)

Custom loaders

If you want to load templates from a database or over a network, you'll need to write your own template loader. Simply inherit from BaseLoader and implement get_source() and, possibly, get_source_async().

get_source() should return an instance of TemplateSource, which is a named tuple containing source text, the template's name, an optional uptodate callable and any extra data you want bound to the template.

Take a look at source code for the built-in loaders for examples.

Load context

get_source() takes an optional context argument. When Environment.get_template() is called from a tag, like render, the active render context will be passed along to get_source(). Loaders can then choose to use render context data to dynamically refine template names and loader search paths.

Arbitrary keyword arguments can also be passed to Environment.get_template(). These too are passed along to get_source(), and can also be used by custom template loaders to refine the template source search space.

For example, all built-in tags that call Environment.get_template() pass a keyword argument called tag set to the name of the calling tag. We can use the tag name to mimic Shopify's snippets convention, where {% include %} and {% render %} automatically load templates from a subfolder call snippets.

from pathlib import Path

from liquid2 import CachingFileSystemLoader
from liquid2 import Environment
from liquid2 import RenderContext
from liquid2 import TemplateSource


class SnippetsFileSystemLoader(CachingFileSystemLoader):
    def get_source(
        self,
        env: Environment,
        template_name: str,
        *,
        context: RenderContext | None = None,
        **kwargs: object,
    ) -> TemplateSource:
        if kwargs.get("tag") in ("include", "render"):
            snippet = Path("snippets").joinpath(template_name)
            return super().get_source(
                env, template_name=str(snippet), context=context, **kwargs
            )
        return super().get_source(
            env, template_name=template_name, context=context, **kwargs
        )

Matter

Sometimes template source text comes with associated data. This could be meta data read from a database or front matter read from the top of the file containing template source text. The TemplateSource object returned from get_source() facilitates these cases with matter, a dictionary mapping strings to arbitrary objects that will be merged with environment and template globals and bound to the resulting Template instance.

Here's an example of a template loader that reads front matter in YAML format.

import re

import yaml

from liquid2 import CachingFileSystemLoader
from liquid2 import Environment
from liquid2 import RenderContext
from liquid2 import TemplateSource

RE_FRONT_MATTER = re.compile(r"\s*---\s*(.*?)\s*---\s*", re.MULTILINE | re.DOTALL)


class FrontMatterLoader(CachingFileSystemLoader):
    def get_source(
        self,
        env: Environment,
        template_name: str,
        *,
        context: RenderContext | None = None,
        **kwargs: object,
    ) -> TemplateSource:
        source, filename, uptodate, matter = super().get_source(env, template_name)
        match = RE_FRONT_MATTER.search(source)

        if match:
            # TODO: add some yaml error handling here.
            matter = yaml.load(match.group(1), Loader=yaml.SafeLoader)
            source = source[match.end() :]

        return TemplateSource(
            source,
            filename,
            uptodate,
            matter,
        )

Caching mixin

Use CachingLoaderMixin to add in-memory LRU caching to your custom template loaders. For example, here's the definition of CachingDictLoader.

from liquid2 import CachingLoaderMixin
from liquid2 import DictLoader

class CachingDictLoader(CachingLoaderMixin, DictLoader):
    """A `DictLoader` that caches parsed templates in memory."""

    def __init__(
        self,
        templates: dict[str, str],
        *,
        auto_reload: bool = True,
        namespace_key: str = "",
        capacity: int = 300,
    ):
        super().__init__(
            auto_reload=auto_reload,
            namespace_key=namespace_key,
            capacity=capacity,
        )

        DictLoader.__init__(self, templates)