Loading Templates

Template loaders and search paths

2 min read 400 words

Kida provides several ways to load templates.

FileSystemLoader

Load templates from filesystem directories:

from kida import Environment, FileSystemLoader

# Single directory
env = Environment(loader=FileSystemLoader("templates/"))

# Multiple directories (searched in order)
env = Environment(loader=FileSystemLoader([
    "templates/",
    "shared/templates/",
]))

# Load a template
template = env.get_template("page.html")
template = env.get_template("components/button.html")

Search Order

Directories are searched in order. First match wins:

loader = FileSystemLoader([
    "themes/custom/",    # Checked first
    "themes/default/",   # Fallback
])

List Templates

loader = FileSystemLoader("templates/")
templates = loader.list_templates()
# ['base.html', 'components/card.html', 'pages/home.html']

DictLoader

Load templates from an in-memory dictionary:

from kida import Environment, DictLoader

loader = DictLoader({
    "base.html": "<html>{% block content %}{% end %}</html>",
    "page.html": "{% extends 'base.html' %}{% block content %}Hi{% end %}",
})

env = Environment(loader=loader)
template = env.get_template("page.html")

Useful for:

  • Testing
  • Embedded templates
  • Dynamic template generation

from_string()

Compile a template from a string (not cached):

env = Environment()
template = env.from_string("Hello, {{ name }}!")
html = template.render(name="World")

Custom Loaders

Implement the Loader protocol:

from kida import Environment, TemplateNotFoundError

class DatabaseLoader:
    def __init__(self, connection):
        self.conn = connection

    def get_source(self, name: str) -> tuple[str, str | None]:
        """Return (source, filename) for template."""
        row = self.conn.execute(
            "SELECT source FROM templates WHERE name = ?", (name,)
        ).fetchone()

        if not row:
            raise TemplateNotFoundError(f"Template '{name}' not found")

        return row[0], f"db://{name}"

    def list_templates(self) -> list[str]:
        """Return all available template names."""
        rows = self.conn.execute("SELECT name FROM templates").fetchall()
        return sorted(row[0] for row in rows)

# Usage
env = Environment(loader=DatabaseLoader(db_connection))

Template Caching

Templates are compiled once and cached:

env = Environment(
    loader=FileSystemLoader("templates/"),
    cache_size=400,      # Max cached templates
    auto_reload=True,    # Check for source changes
)

# Check cache stats
info = env.cache_info()
print(info["template"])
# {'size': 5, 'max_size': 400, 'hits': 100, 'misses': 5}

Auto-Reload

Withauto_reload=True(default), Kida checks if template source changed:

# Development: auto-reload enabled
env = Environment(auto_reload=True)

# Production: disable for performance
env = Environment(auto_reload=False)

Clear Cache

# Clear all templates
env.clear_template_cache()

# Clear specific templates
env.clear_template_cache(["base.html", "page.html"])

Bytecode Cache

For cold-start performance, enable bytecode caching:

from kida import Environment, FileSystemLoader
from kida.bytecode_cache import BytecodeCache

env = Environment(
    loader=FileSystemLoader("templates/"),
    bytecode_cache=BytecodeCache("__pycache__/kida/"),
)

Compiled bytecode is persisted to disk and loaded on subsequent runs.

See Also