Kida provides block-level rendering, introspection, and composition helpers for frameworks that need fragments (HTMX, Turbo), layout assembly, or template validation. Use these APIs when building adapters, validating routes, or composing layouts programmatically.
Overview
| API | Purpose |
|---|---|
render_block(name, **ctx) |
Render a single block — HTMX partials, cached nav |
render_with_blocks(overrides, **ctx) |
Compose layout with pre-rendered HTML in blocks |
list_blocks() |
Discover block names for validation |
template_metadata() |
Full analysis — blocks, extends, dependencies |
validate_block_exists() |
Check block exists beforerender_block |
get_structure() |
Lightweight manifest for composition planning |
Block Rendering
render_block
Render a single block from a template. Supports inherited blocks: when the template extends a parent, you can render parent-only blocks by name (e.g.render_block("sidebar") on a child that extends a base defining sidebar).
template = env.get_template("page.html")
# Render content block (HTMX partial response)
html = template.render_block("content", title="Hello", items=items)
# Render parent-only block from descendant
html = template.render_block("sidebar", site=site)
Raises KeyErrorif the block does not exist in the template or any parent.
render_with_blocks
Render a template with pre-rendered HTML injected into blocks. Enables programmatic layout composition without{% extends %}in the template source.
layout = env.get_template("_layout.html")
inner_html = "<h1>Hello</h1><p>Content here.</p>"
# Inject inner_html as the "content" block
html = layout.render_with_blocks({"content": inner_html}, title="Page Title")
Each key in block_overridesnames a block; the value is a pre-rendered HTML string that replaces that block's default content.
list_blocks
List all blocks available forrender_block(), including inherited blocks.
blocks = template.list_blocks()
# ['title', 'nav', 'content', 'footer']
Introspection
template_metadata
Get full template analysis including inheritance info, block metadata, and dependencies. ReturnsNone if AST was not preserved (preserve_ast=Falseor loaded from bytecode cache without source).
meta = template.template_metadata()
if meta:
print(meta.extends) # Parent template name
print(list(meta.blocks.keys())) # Block names
print(meta.all_dependencies()) # Context paths accessed
block_metadata
Get per-block analysis: purity, cache scope, inferred role.
blocks = template.block_metadata()
nav = blocks.get("nav")
if nav and nav.cache_scope == "site":
# Safe to cache nav across all pages
html = cache.get_or_render("nav", ...)
validate_context
Check a context dict for missing variables before rendering.
missing = template.validate_context(user_context)
if missing:
raise ValueError(f"Missing template variables: {missing}")
Composition Module
Thekida.compositionmodule provides validation helpers for frameworks:
from kida import Environment, FileSystemLoader
from kida.composition import validate_block_exists, get_structure
env = Environment(loader=FileSystemLoader("templates/"))
validate_block_exists
Check if a block exists before callingrender_block:
if validate_block_exists(env, "skills/page.html", "page_content"):
html = env.get_template("skills/page.html").render_block("page_content", ...)
else:
# Handle missing block
...
Returns Falseif the template is not found or the block is missing.
get_structure
Get a lightweight structure manifest (block names, extends parent, dependencies). Cached by Environment for reuse.
struct = get_structure(env, "page.html")
if struct and "page_root" in struct.block_names:
# Template has page_root block — suitable for layout composition
...
block_role_for_framework
Classify block metadata into framework-relevant roles ("fragment", "page_root", or None). Useful for frameworks that need to distinguish content blocks from layout roots.
from kida.composition import block_role_for_framework
meta = template.template_metadata()
for name, block in meta.blocks.items():
role = block_role_for_framework(block)
if role == "fragment":
# Suitable for HTMX partial
...
Adapter Pattern
A minimal template adapter wraps Kida's APIs:
from kida import Environment
from typing import Any
class KidaAdapter:
"""TemplateAdapter implementation using Kida's block/layout APIs."""
def __init__(self, env: Environment) -> None:
self._env = env
def render_template(self, template: str, context: dict[str, Any]) -> str:
return self._env.get_template(template).render(context)
def render_block(self, template: str, block: str, context: dict[str, Any]) -> str:
return self._env.get_template(template).render_block(block, context)
def compose_layout(
self,
template: str,
block_overrides: dict[str, str],
context: dict[str, Any],
) -> str:
return self._env.get_template(template).render_with_blocks(
block_overrides, **context
)
def template_metadata(self, template: str) -> object | None:
from kida.environment.exceptions import (
TemplateNotFoundError,
TemplateSyntaxError,
)
try:
return self._env.get_template(template).template_metadata()
except (TemplateNotFoundError, TemplateSyntaxError):
return None
Chirp uses this pattern in KidaAdapter.
Case Studies
Bengal (Static Site Generator)
- Full render —
render()for page output - Bytecode cache — Persistent
.bengal/cache/kida/for cold-start - Fragment cache —
{% cache %}with site-scoped TTL - Analysis —
block_metadata(),is_cacheable()for incremental builds
Chirp (Web Framework)
- Full render —
render()for full-page responses - Block render —
render_block()for HTMX fragments, partial updates - Layout composition —
render_with_blocks()for programmatic layout assembly - Streaming —
render_stream(),render_stream_async()for chunked HTTP - Introspection —
template_metadata()for composition planning,validate_block_exists()beforerender_block - Adapter —
KidaAdapterimplements Chirp'sTemplateAdapterinterface
See Also
- Inheritance —
render_blockwith{% extends %} - Static Analysis — Full introspection API
- render_block and Def Scope — Def scope in blocks