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 | Callable |
True |
HTML auto-escaping |
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 |
False |
Raise on missing attribute access |
jinja2_compat_warnings |
bool |
False |
Warn on{% set %}scoping differences |
validate_calls |
bool |
False |
Validate{% def %}call sites at compile time |
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 a
bytecode_cacheconfigured, passname=to enable it. Without a name, there's no stable cache key, so the bytecode cache is bypassed. AUserWarningis emitted if you callfrom_string()withoutname=when a bytecode cache is active.
Partial evaluation: Pass
static_context={...}to evaluate expressions at compile time. Overrides Environment'sstatic_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
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 |
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: Calling
render()orrender_stream()on a template whereis_asyncisTrueraisesTemplateRuntimeError. Userender_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 matchlist_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 loaderlist_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 resourceslist_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]— Callsload_funcand normalizes resultlist_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"
With strict_undefined=True, attribute access errors also include component context:
env = Environment(strict_undefined=True)
# "Undefined attribute 'typo' on User object in page.html:5"
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" | float → 0.0) |
MigrationWarning |
K-WARN-002 | {% set %}scoping differs from Jinja2 |
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>")
# <script>
# 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
- Filters Reference — All built-in filters
- Tests Reference — All built-in tests
- Configuration — All options