API Reference

Core classes and methods

16 min read 3155 words

Contract Status

The framework-author surface below is treated as the documented integration contract:

  • imports exported fromkida.__all__
  • ErrorCodenames and values
  • Environment(...)constructor parameters
  • Templaterender, block-render, streaming, and metadata methods
  • metadata dataclass fields forBlockMetadata, DefParamInfo, DefMetadata, TemplateMetadata, and TemplateStructureManifest
  • loader constructor behavior documented on this page
  • sandbox, render context, capture, and manifest objects exported fromkida

Snapshot tests guard those contracts. Any change to the stable surface should be deliberate and land with docs and changelog updates when behavior changes.

Internals remain internal even when they are visible to Python: underscored attributes, generated template namespace entries, parser/compiler node shapes, cache implementation details, and helper functions outsidekida.__all__.

Environment

Central configuration and template management hub.

from kida import Environment, FileSystemLoader

env = Environment(
    loader=FileSystemLoader("templates/"),
    autoescape=True,
)

Constructor Parameters

Parameter Type Default Description
loader Loader None Template source provider
autoescape bool | "html" | "terminal" | "markdown" | Callable True Output escaping mode
auto_reload bool True Check for source changes
cache_size int 400 Max cached templates
fragment_cache_size int 1000 Max cached fragments
fragment_ttl float 300.0 Fragment TTL (seconds)
static_context dict | None None Values for compile-time partial evaluation
strict_undefined bool True RaiseUndefinedError on missing variable or attribute access (set to Falsefor lenient empty-string fallback)
jinja2_compat_warnings bool True Warn when nested{% set %} shadows a {% let %}/{% export %}name
validate_calls bool False Validate{% def %}call sites at compile time

Constructor contract note: the generated dataclass constructor is part of the snapshot gate. Parameters not documented here are compatibility details rather than recommended framework integration points.

Methods

get_template(name)

Load and cache a template by name.

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

Raises: TemplateNotFoundError, TemplateSyntaxError

from_string(source, name=None)

Compile a template from string (not cached in the template cache).

template = env.from_string("Hello, {{ name }}!")

Bytecode caching: If you have abytecode_cache configured, pass name= to enable it. Without a name, there's no stable cache key, so the bytecode cache is bypassed. A UserWarning is emitted if you call from_string() without name=when a bytecode cache is active.

Partial evaluation: Passstatic_context={...} to evaluate expressions at compile time. Overrides Environment's static_contextfor this call.

render(template_name, **context)

Load and render in one step.

html = env.render("page.html", title="Hello", items=items)

render_string(source, **context)

Compile and render string in one step.

html = env.render_string("{{ x * 2 }}", x=21)

add_filter(name, func)

Register a custom filter.

env.add_filter("double", lambda x: x * 2)

add_test(name, func)

Register a custom test.

env.add_test("even", lambda x: x % 2 == 0)

add_global(name, value)

Add a global variable.

env.add_global("site_name", "My Site")

filter() (decorator)

Decorator to register a filter.

@env.filter()
def double(value):
    return value * 2

test() (decorator)

Decorator to register a test.

@env.test()
def is_even(value):
    return value % 2 == 0

cache_info()

Get cache statistics.

info = env.cache_info()
# {'template': {...}, 'fragment': {...}}

clear_cache(include_bytecode=False)

Clear all caches.

env.clear_cache()

Template

Compiled template with render interface.

Rendering (all users)

render(**context)

Render template with context.

html = template.render(name="World", items=[1, 2, 3])

render_async(**context)

Render template asynchronously (thread-pool wrapper for sync templates).

html = await template.render_async(items=async_generator())

render_stream(**context)

Render template as a sync generator of string chunks. Yields at statement boundaries for chunked HTTP and streaming.

for chunk in template.render_stream(items=data):
    send_to_client(chunk)

render_stream_async(**context)

Render template as an async stream. Supports native{% async for %} and {{ await }}constructs. Also works on sync templates (wraps the sync stream).

async for chunk in template.render_stream_async(items=async_iterable):
    send_to_client(chunk)

Raises: RuntimeErrorif no render function is available.

Block rendering (fragments, frameworks)

render_block(block_name, **context)

Render a single block from the template. Supports inherited blocks: when the template extends a parent, you can render parent-only blocks by name (e.g.render_block("content") on a child that extends a base defining content). Child overrides still win; super()is not supported.

html = template.render_block("content", title="Hello")

Raises: KeyErrorif the block does not exist in the template or any parent.

render_with_blocks(block_overrides, **context)

Render template with pre-rendered HTML injected into blocks. Enables programmatic layout composition without{% extends %}in the template source.

layout = env.get_template("_layout.html")
html = layout.render_with_blocks({"content": inner_html}, title="Page")

Each key in block_overrides names a block; the value is a pre-rendered HTML string. Unknown block names now raise TemplateRuntimeErrorwith did-you-mean suggestions.

render_block_stream_async(block_name, **context)

Render a single block as an async stream. Supports inherited blocks likerender_block(). Falls back to wrapping the sync block stream if no async variant exists.

async for chunk in template.render_block_stream_async("content", items=data):
    send_to_client(chunk)

Raises: KeyErrorif the block does not exist.

list_blocks()

List all blocks available forrender_block(), including inherited blocks.

blocks = template.list_blocks()
# ['title', 'nav', 'content', 'footer']

Component Introspection

list_defs()

List all{% def %}component names in the template.

names = template.list_defs()
# ['card', 'nav_link', 'badge']

def_metadata()

Return metadata for all{% def %} components in the template. Returns a dict[str, DefMetadata].

meta = template.def_metadata()
card = meta["card"]
print(card.name)              # "card"
print(card.template_name)     # "components/card.html"
print(card.lineno)            # 3
print(card.params)            # (DefParamInfo(name='title', annotation='str', ...), ...)
print(card.slots)             # ('actions', 'footer')
print(card.has_default_slot)  # True
print(card.depends_on)        # frozenset({'site.title'})
print(card.vararg)            # None or '*args' parameter name
print(card.kwarg)             # None or '**kwargs' parameter name

DefMetadata fields:

Field Type Description
name str Component name
template_name str | None Source template
lineno int Line number of{% def %}
params tuple[DefParamInfo, ...] Parameter metadata
slots tuple[str, ...] Named slot names
has_default_slot bool Whether{% slot %}(unnamed) exists
depends_on frozenset[str] Context paths read by the def body outside declared params and locals
vararg str | None Name of*argsparameter, if present
kwarg str | None Name of**kwargsparameter, if present

DefParamInfo fields:

Field Type Description
name str Parameter name
annotation str | None Type annotation (e.g."str", "int")
has_default bool Whether a default value is defined
is_required bool Trueif no default value

warnings

Compile-time warnings collected during template compilation.

for w in template.warnings:
    print(w.code, w.message, w.lineno)

Template Introspection (frameworks, build systems)

template_metadata()

Return full template analysis (blocks, extends, dependencies). ReturnsNoneif AST was not preserved.

meta = template.template_metadata()
if meta:
    print(meta.extends, meta.blocks.keys())
    regions = meta.regions()  # Only {% region %} blocks (for OOB discovery)

block_metadata()

Return per-block analysis (purity, cache scope, inferred role).

blocks = template.block_metadata()
nav = blocks.get("nav")
if nav and nav.cache_scope == "site":
    ...

depends_on()

Return all context paths this template may access.

deps = template.depends_on()
# frozenset({'page.title', 'site.pages'})

required_context()

Return top-level variable names the template needs.

names = template.required_context()
# frozenset({'page', 'site'})

validate_context(context)

Check a context dict for missing variables. Returns list of missing names.

missing = template.validate_context(user_context)
if missing:
    raise ValueError(f"Missing: {missing}")

is_cacheable(block_name=None)

Check if a block (or all blocks) can be safely cached.

template.is_cacheable("nav")   # True if nav is cacheable
template.is_cacheable()       # True only if all blocks cacheable

Properties

Property Type Description
name str | None Template name
filename str | None Source filename
is_async bool True if template uses {% async for %} or {{ await }}
warnings list[TemplateWarning] Compile-time warnings (precedence, coercion, migration)

Note: Callingrender() or render_stream() on a template where is_async is True raises TemplateRuntimeError. Use render_stream_async()instead.


Loaders

FileSystemLoader

Load templates from filesystem directories.

from kida import FileSystemLoader

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

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

Constructor Parameters

Parameter Type Default Description
paths str | Path | list Required Search paths
encoding str "utf-8" File encoding

Methods

  • get_source(name)tuple[str, str]
  • list_templates()list[str]

DictLoader

Load templates from a dictionary.

from kida import DictLoader

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

ChoiceLoader

Try multiple loaders in order, returning the first match.

from kida import ChoiceLoader, FileSystemLoader

loader = ChoiceLoader([
    FileSystemLoader("themes/custom/"),
    FileSystemLoader("themes/default/"),
])

Constructor Parameters

Parameter Type Description
loaders list[Loader] Loaders to try in order

Methods

  • get_source(name)tuple[str, str | None]— Returns first successful match
  • list_templates()list[str]— Merged, deduplicated, sorted list from all loaders

PrefixLoader

Namespace templates by prefix, delegating to per-prefix loaders.

from kida import PrefixLoader, FileSystemLoader

loader = PrefixLoader({
    "app": FileSystemLoader("templates/app/"),
    "admin": FileSystemLoader("templates/admin/"),
})

# env.get_template("app/index.html") → templates/app/index.html

Constructor Parameters

Parameter Type Default Description
mapping dict[str, Loader] Required Prefix → loader mapping
delimiter str "/" Prefix delimiter

Methods

  • get_source(name)tuple[str, str | None]— Splits on delimiter, delegates to prefix loader
  • list_templates()list[str]— All templates with prefix prepended

PackageLoader

Load templates from an installed Python package viaimportlib.resources.

from kida import PackageLoader

loader = PackageLoader("my_app", "templates")
# env.get_template("pages/index.html") → my_app/templates/pages/index.html

Constructor Parameters

Parameter Type Default Description
package_name str Required Dotted Python package name
package_path str "templates" Subdirectory within the package
encoding str "utf-8" File encoding

Methods

  • get_source(name)tuple[str, str | None]— Loads from package resources
  • list_templates()list[str]— All templates in the package directory (recursive)

FunctionLoader

Wrap a callable as a loader.

from kida import FunctionLoader

loader = FunctionLoader(lambda name: templates.get(name))

Constructor Parameters

Parameter Type Description
load_func Callable[[str], str | tuple[str, str | None] | None] Returns source,(source, filename), or None

Methods

  • get_source(name)tuple[str, str | None] — Calls load_funcand normalizes result
  • list_templates()list[str] — Always returns [](cannot enumerate)

Composition Helpers

Validation and structure helpers for frameworks. See Framework Integration for full usage.

from kida.composition import (
    validate_block_exists,
    validate_template_block,
    get_structure,
    block_role_for_framework,
)

validate_block_exists(env, template_name, block_name) → bool

Check if a block exists in a template (including inherited blocks). ReturnsFalseif template not found or block missing.

if validate_block_exists(env, "skills/page.html", "page_content"):
    html = env.get_template("skills/page.html").render_block("page_content", ...)

validate_template_block(template, block_name) → bool

Check if a block exists in a loaded Template instance.

get_structure(env, template_name) → TemplateStructureManifest | None

Get lightweight structure manifest (block names, extends, dependencies). Cached by Environment.

struct = get_structure(env, "page.html")
if struct and "page_root" in struct.block_names:
    ...

TemplateStructureManifest fields:

Field Type Description
name str | None Template name
extends str | None Parent template from{% extends %}
block_names tuple[str, ...] Ordered block names
block_hashes dict[str, str] Per-block structural hashes
dependencies frozenset[str] Context paths accessed

block_role_for_framework(block_metadata, ...) → str | None

Classify block metadata into framework roles:"fragment", "page_root", or None.


Exceptions

TemplateError

Base class for all template errors. All Kida exceptions carry anErrorCode accessible via exc.code.

TemplateSyntaxError

Invalid template syntax.

from kida import TemplateSyntaxError

try:
    env.from_string("{% if x %}")  # Missing end
except TemplateSyntaxError as e:
    print(e)

TemplateRuntimeError

Error during template rendering. Includescomponent_stack for errors inside {% def %}components:

from kida import TemplateRuntimeError

try:
    template.render(items=None)
except TemplateRuntimeError as e:
    print(e.code)             # ErrorCode enum value
    print(e.component_stack)  # [(def_name, lineno, template_name), ...]
    print(e.format_compact()) # Formatted error with source snippet

TemplateNotFoundError

Template file not found. Includes caller context (requesting template and line number).

from kida import TemplateNotFoundError

try:
    env.get_template("nonexistent.html")
except TemplateNotFoundError as e:
    print(e)

UndefinedError

Accessing undefined variable or attribute. Thekindfield distinguishes between variable, attribute, and key lookups.

from kida import UndefinedError

try:
    env.from_string("{{ missing }}").render()
except UndefinedError as e:
    print(e.kind)  # "variable", "attribute", or "key"
    print(e)       # "Undefined variable 'missing' in <string>:1"

Under the default strict mode, attribute access errors also include component context:

env = Environment()  # strict_undefined=True by default
# "Undefined attribute 'typo' on User object in page.html:5"

Opt out per-Environment with strict_undefined=Falseif you need empty-string fallback for missing attributes.

SecurityError

Raised bySandboxedEnvironmentwhen a template violates the security policy. Carries error codes K-SEC-001 through K-SEC-005.

Warning Classes

Compile-time warnings emitted during template compilation:

Class Code Description
PrecedenceWarning K-WARN-001 | binds tighter than ??
CoercionWarning Silent type coercion in filters (e.g."abc" | float0.0)
MigrationWarning K-WARN-002 Nested{% set %} shadows a {% let %}/{% export %}name (Jinja2 scoping trap)

These are standard Python warnings and can be filtered withwarnings.filterwarnings.


Markup

HTML-safe string wrapper.

from kida import Markup

# Create safe HTML
safe = Markup("<b>Bold</b>")

# Escape unsafe content
escaped = Markup.escape("<script>")
# &lt;script&gt;

# Format with escaping
result = Markup("<p>{}</p>").format(user_input)

Class Methods

Method Description
escape(s) Escape string and return Markup

Operations

Operation Behavior
Markup + str str is escaped
Markup + Markup Concatenated as-is
Markup.format(...) Arguments are escaped

LoopContext

Available asloop variable inside {% for %}loops.

Property Type Description
index int 1-based index
index0 int 0-based index
first bool True on first iteration
last bool True on last iteration
length int Total items
revindex int Reverse 1-based index
revindex0 int Reverse 0-based index
{% for item in items %}
    {{ loop.index }}/{{ loop.length }}
{% end %}

AsyncLoopContext

Available asloop variable inside {% async for %} loops. Provides index-forward properties only — properties that require knowing the total size raise TemplateRuntimeErrorsince async iterables have no known length.

Property Type Description
index int 1-based index
index0 int 0-based index
first bool True on first iteration
previtem Any | None Previous item (Noneon first)
cycle(*values) method Cycle through values
last RaisesTemplateRuntimeError
length RaisesTemplateRuntimeError
revindex RaisesTemplateRuntimeError
revindex0 RaisesTemplateRuntimeError
nextitem RaisesTemplateRuntimeError
{% async for user in fetch_users() %}
    {{ loop.index }}: {{ user.name }}
    {% if loop.first %}(first!){% end %}
{% end %}

RenderContext

Per-render state management via ContextVar.

from kida.render_context import (
    RenderContext,
    render_context,
    async_render_context,
    get_render_context,
    get_render_context_required,
)

RenderContext Dataclass

Attribute Type Description
template_name str | None Current template name
filename str | None Source file path
line int Current line (for errors)
include_depth int Include nesting depth
max_include_depth int Max depth (default: 50)
cached_blocks dict[str, str] Site-scoped block cache

Methods

Method Description
check_include_depth(name) Raise if depth exceeded
child_context(template_name=None, *, source=None) Create child for include/embed with incremented depth
child_context_for_extends(parent_name, *, source=None) Create child for extends with incremented extends_depth
get_meta(key, default=None) Get framework metadata (HTMX, CSRF, etc.)
set_meta(key, value) Set framework metadata before rendering

Functions

Function Description
get_render_context() Get current context (None if not rendering)
get_render_context_required() Get context or raise RuntimeError
render_context(...) Context manager for render scope
async_render_context(...) Async context manager for render scope

Low-Level APIs

For cases where the context manager isn't suitable (e.g. nested include/embed that need manual restore):

Function Description
set_render_context(ctx) Set a RenderContext, returns reset token
reset_render_context(token) Restore previous context using token fromset_render_context()

RenderAccumulator

Opt-in profiling for template rendering. When enabled viaprofiled_render(), the compiler-emitted instrumentation automatically tracks:

  • Blocks — render timing (milliseconds) and call counts
  • Filters — call counts per filter name
  • Macros — call counts per{% def %}name
  • Includes — counts per included template

Zero overhead when profiling is disabled — the instrumentation gates on a falsy check.

from kida.render_accumulator import (
    RenderAccumulator,
    profiled_render,
    get_accumulator,
)

Usage

with profiled_render() as metrics:
    html = template.render(page=page)

summary = metrics.summary()
# {
#     "total_ms": 12.5,
#     "blocks": {"content": {"ms": 8.2, "calls": 1}, "nav": {"ms": 1.1, "calls": 1}},
#     "filters": {"upper": 3, "truncate": 2},
#     "macros": {"card": 5},
#     "includes": {"header.html": 1},
# }

RenderAccumulator Properties

Property Type Description
block_timings dict[str, BlockTiming] Block render times
macro_calls dict[str, int] Macro call counts
include_counts dict[str, int] Include counts
filter_calls dict[str, int] Filter usage counts
total_duration_ms float Total render time

Methods

Method Description
record_block(name, ms) Record block timing
record_macro(name) Record macro call
record_include(name) Record include
record_filter(name) Record filter usage
summary() Get metrics dict

See Also