A template engine that compiles to Python AST, renders to HTML/terminal/markdown, and scales across cores on free-threaded Python.
from kida import Environment
env = Environment()
template = env.from_string("Hello, {{ name }}!")
print(template.render(name="World"))
# Hello, World!Most template engines generate Python source code as strings, then exec() it. Kida compiles directly to ast.Module — the same structured representation Python itself uses. This unlocks compile-time optimization, precise error mapping, and safe concurrent rendering that string-based engines can't achieve.
| Kida | Jinja2 | |
|---|---|---|
| Compilation target | Python AST (ast.Module) |
Python source strings |
| Free-threading (PEP 703) | Safe under PYTHON_GIL=0 |
Not tested/supported |
| Rendering modes | render(), render_stream(), render_block(), async variants |
render(), generate() |
| Compile-time optimization | Constant folding, dead branch elimination, filter eval, component inlining | None |
| Terminal rendering | Built-in (autoescape="terminal") with 30+ ANSI-aware filters |
None |
| Pattern matching | {% match %} / {% case %} |
None |
| Null safety | ??, ?., `? |
>, ? |
| Components | {% def %} + {% slot %} + named slots |
Macros (no slots) |
| Regions | {% region name(params) %} — parameterized blocks |
None |
| Block rendering | render_block(), render_with_blocks() |
None (third-party) |
| Fragment caching | {% cache "key" %} built-in |
Extension required |
| Template complexity | Kida | vs Jinja2 |
|---|---|---|
| Minimal (~4µs) | Baseline | ~1x |
| Small loop + filter (~7µs) | Baseline | ~1x |
| Medium ~100 vars (~0.2ms) | Baseline | ~1.3x faster |
| Large 1000-item loop (~1.6ms) | Baseline | 2.5x faster |
| Complex 3-level inheritance (~19µs) | Baseline | 1.5x faster |
With static_context (compile-time folding) |
1.5-2x additional speedup | N/A |
Concurrent (2-4 cores, PYTHON_GIL=0) |
Linear scaling | Not supported |
pip install kida-templatesRequires Python 3.14+. Zero runtime dependencies.
Kida renders to three surfaces from the same template syntax.
from kida import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates/"))
template = env.get_template("page.html")
html = template.render(title="Hello")from kida.terminal import terminal_env
env = terminal_env()
template = env.from_string("""
{{ "Deploy Status" | bold | cyan }}
{{ hr(40) }}
{% for svc in services %}
{{ svc.name | pad(20) }}{{ svc.status | badge }}
{% end %}
""")
print(template.render(services=[
{"name": "api", "status": "pass"},
{"name": "worker", "status": "fail"},
]))30+ ANSI-aware filters (bold, fg(), pad, badge, bar, kv, table, tree, diff), built-in panel/box components, and LiveRenderer for in-place re-rendering with spinners.
from kida.markdown import markdown_env
env = markdown_env()
template = env.from_string("# {{ title }}\n\n{{ body }}")
md = template.render(title="Report", body="All tests passed.")Template Inheritance
base.html:
<!DOCTYPE html>
<html>
<body>
{% block content %}{% end %}
</body>
</html>
page.html:
{% extends "base.html" %}
{% block content %}
<h1>{{ title }}</h1>
{% end %}
Components & Named Slots
{% def card(title) %}
<article class="card">
<h2>{{ title }}</h2>
<div class="actions">{% slot header_actions %}</div>
<div class="body">{% slot %}</div>
</article>
{% end %}
{% call card("Settings") %}
{% slot header_actions %}<button>Save</button>{% end %}
<p>Body content.</p>
{% end %}
Pattern Matching & Null Safety
{% match status %}
{% case "active" %}
Active user
{% case "pending" %}
Pending verification
{% case _ %}
Unknown status
{% end %}
{# Null coalescing #}
{{ user.nickname ?? user.name ?? "Anonymous" }}
{# Optional chaining #}
{{ config?.database?.host }}
{# Safe pipeline — stops on None #}
{{ data ?|> parse ?|> validate ?|> render }}
Streaming & Block Rendering
# Stream chunks as they render (chunked HTTP, SSE)
for chunk in template.render_stream(items=large_list):
response.write(chunk)
# Render a single block (HTMX partials)
html = template.render_block("content", title="Hello")
# Compose layouts with pre-rendered blocks
html = layout.render_with_blocks({"content": inner_html}, title="Page")Regions — Parameterized Blocks
{% region sidebar(current_path="/") %}
<nav>{{ current_path }}</nav>
{% end %}
{{ sidebar(current_path="/about") }}
Regions are blocks (for render_block()) and callables (for inline use). Ideal for HTMX OOB swaps and framework integration.
Compile-Time Optimization
# Pass static data at compile time — kida folds constants,
# eliminates dead branches, and evaluates pure filters
template = env.from_string(source, static_context={
"site": site_config,
"settings": app_settings,
})
# Only dynamic data needed at render time
html = template.render(page_title="Home", items=page_items)67 pure filters evaluated at compile time. Dead {% if debug %} branches removed entirely. Component inlining for small defs with constant args.
Use kida render template.html --explain to see which optimizations are active.
Free-Threading
All public APIs are safe under PYTHON_GIL=0 (Python 3.14t, PEP 703):
- Templates compile to immutable AST — no shared mutable state
- Rendering uses thread-local StringBuilder — no contention
- Environment uses copy-on-write for configuration changes
LiveRenderer.update()is thread-safe with internal locking
Module declares GIL independence via _Py_mod_gil = 0. Rendering scales linearly with cores.
Framework Integration
Drop-in adapters for Flask, Starlette/FastAPI, and Django:
# Flask
from kida.contrib.flask import KidaFlask
kida = KidaFlask(app)
# Starlette / FastAPI
from kida.contrib.starlette import KidaStarlette
templates = KidaStarlette(directory="templates")
# Django
TEMPLATES = [{"BACKEND": "kida.contrib.django.KidaDjango", ...}]Turn pytest, coverage, ruff, and other tool output into formatted step summaries and PR comments.
- name: Run tests
run: pytest --junitxml=results.xml
- name: Post test report
uses: lbliii/kida@v0.3.2
with:
template: pytest
data: results.xml
data-format: junit-xml| Template | Data format | Tool |
|---|---|---|
pytest |
junit-xml | pytest --junitxml |
coverage |
json | coverage.py --json or lcov |
ruff |
json | ruff --output-format json |
ty |
junit-xml | ty --output-format junit |
jest |
json | jest --json |
gotest |
junit-xml | go-junit-report |
sarif |
sarif | CodeQL, Semgrep, Trivy, ESLint |
- name: Post coverage to PR
uses: lbliii/kida@v0.3.2
with:
template: coverage
data: coverage.json
post-to: step-summary,pr-comment- uses: lbliii/kida@v0.3.2
with:
template: .github/kida-templates/my-report.md
data: output.json| Input | Default | Description |
|---|---|---|
template |
(required) | Built-in name or path to template file |
data |
(required) | Path to data file |
data-format |
json |
json, junit-xml, sarif, or lcov |
post-to |
step-summary |
step-summary, pr-comment, or both |
comment-header |
template name | Marker for PR comment deduplication |
token |
github.token |
GitHub token (needed for pr-comment) |
python-version |
3.14 |
Python version (skip to use existing) |
install |
true |
Whether to pip install kida-templates |
# Render a template
kida render template.txt --data context.json
kida render dashboard.txt --mode terminal --width 80 --color truecolor
# Show which compiler optimizations are active
kida render template.html --explain
# Check all templates for syntax errors
kida check templates/
# Strict mode: require explicit end tags ({% endif %} not {% end %})
kida check templates/ --strict
# Validate macro call sites against signatures
kida check templates/ --validate-calls
# Accessibility and type checking
kida check templates/ --a11y --typed
# Auto-format templates
kida fmt templates/| Function | Description |
|---|---|
Environment() |
Create a template environment |
env.from_string(src) |
Compile template from string |
env.get_template(name) |
Load template from filesystem |
template.render(**ctx) |
Full render (StringBuilder, fastest) |
template.render_block(name, **ctx) |
Single block (HTMX partials) |
template.render_stream(**ctx) |
Generator (chunked HTTP, SSE) |
template.render_async(**ctx) |
Async buffered output |
template.render_stream_async(**ctx) |
Async streaming |
template.render_with_blocks(overrides, **ctx) |
Compose layout with pre-rendered blocks |
template.list_blocks() |
Block names for validation |
template.template_metadata() |
Full analysis (blocks, regions, deps) |
Full documentation: lbliii.github.io/kida. See also: Kida vs Jinja2
| Section | |
|---|---|
| Get Started | Installation, quickstart, coming from Jinja2 |
| Syntax | Template language reference |
| Usage | Loading, rendering, escaping, terminal mode |
| Framework Integration | Flask, Starlette, Django adapters |
| Advanced | Compiler, profiling, coverage, security |
| Reference | Complete API docs |
Template Source → Lexer → Parser → Kida AST → Compiler → Python AST → exec()
Kida generates ast.Module objects directly — no intermediate source strings. This enables compile-time optimization (constant folding, dead branch elimination, filter evaluation), precise error source mapping (exact line:column in template source), and safe concurrent execution (immutable AST, no shared mutable state).
Rendering uses two modes from a single compilation:
render()— StringBuilder pattern (_out.append(...)) for maximum throughputrender_stream()— Python generator (yield ...) for statement-level streaming
git clone https://github.com/lbliii/kida.git
cd kida
uv sync --group dev --python 3.14t
PYTHON_GIL=0 uv run --python 3.14t pytestKida is part of a pure-Python stack built for 3.14t free-threading.
| ᓚᘏᗢ | Bengal | Static site generator | Docs |
| ∿∿ | Purr | Content runtime | — |
| ⌁⌁ | Chirp | Web framework | Docs |
| =^..^= | Pounce | ASGI server | Docs |
| )彡 | Kida | Template engine | Docs |
| ฅᨐฅ | Patitas | Markdown parser | Docs |
| ⌾⌾⌾ | Rosettes | Syntax highlighter | Docs |
| ᓃ‿ᓃ | Milo | Terminal UI framework | Docs |
MIT License — see LICENSE for details.