Python template engine and Jinja2 alternative for HTML templates, streaming, and framework integration
Project description
)彡 Kida
A Python template engine and Jinja2 alternative for HTML templates, streaming, and framework integration
from kida import Environment
env = Environment()
template = env.from_string("Hello, {{ name }}!")
print(template.render(name="World"))
# Output: Hello, World!
What is Kida?
Kida is a modern template engine for Python 3.14t. It works for static site generation (Bengal), dynamic web apps (Chirp), and anywhere you need templates — same syntax, same engine. It compiles templates to Python AST directly (no string generation), supports streaming and block rendering, and is built for free-threading.
Why people pick it:
- AST-native — Compiles to Python AST directly. Structured code manipulation, compile-time optimization, precise error source mapping.
- Free-threading ready — Safe for Python 3.14t concurrent execution (PEP 703). All public APIs are thread-safe.
- Dual-mode rendering —
render()uses StringBuilder for maximum throughput.render_stream()yields chunks for streaming HTTP and SSE. - Modern syntax — Pattern matching, pipeline operator, unified
{% end %}, null coalescing, optional chaining. - Explicit block closers — Prefer
{% endif %},{% endcall %},{% endfor %},{% endblock %},{% enddef %}over bare{% end %}when nesting depth is 3+ (both styles are valid; explicit tags match Jinja2 muscle memory and ease code review). - Zero dependencies — Pure Python, includes native
Markupimplementation.
Use Kida For
- HTML template rendering — Pages, partials, emails, and reusable components
- Jinja2-style migration paths — Familiar syntax with new features and different internals
- Streaming interfaces — Chunked HTML, SSE, and progressive rendering
- Framework integration — Block rendering, introspection, and template analysis for app frameworks
- Python 3.14+ template stacks — Async rendering and free-threading-friendly execution
Installation
pip install kida-templates
Requires Python 3.14+
Quick Start
| 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 page (StringBuilder, fastest) |
template.render_block(name, **ctx) |
Single block (fragments, HTMX) |
template.render_stream(**ctx) |
Generator (chunked HTTP, SSE) |
template.render_async(**ctx) |
Async buffered output |
template.render_stream_async(**ctx) |
Async streaming (for {% async for %}) |
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, dependencies) |
validate_block_exists(env, template, block) |
Check block exists before render_block |
RenderedTemplate(template, ctx) |
Lazy iterable wrapper for streaming |
Features
| Feature | Description | Docs |
|---|---|---|
| Jinja2 Migration | Learn where syntax matches, what changes, and how to switch safely | Migrate from Jinja2 → |
| Framework Integration | Block rendering, metadata, and adapters for web frameworks | Framework Integration → |
| Template Syntax | Variables, filters, control flow, pattern matching | Syntax → |
| Inheritance | Template extends, blocks, includes | Inheritance → |
| Filters & Tests | 50+ built-in filters, custom filter registration | Filters → |
| Streaming | Statement-level generator rendering via render_stream() |
Streaming → |
| Async Support | Native async for, await in templates |
Async → |
| Caching | Fragment caching with TTL support | Caching → |
| Components & Slots | {% def %}, {% call %}, {% slot %}, {% yield %} |
Functions → |
| Regions | {% region name(params) %}...{% end %} — parameterized blocks for render_block |
Functions → |
| Block Rendering | render_block(), render_with_blocks() for fragments and layout composition |
Framework Integration → |
| Introspection | template_metadata(), block_metadata(), validate_context() for frameworks |
Analysis → |
| Partial Evaluation | Compile-time evaluation via static_context |
Advanced → |
| Block Recompilation | Recompile only changed blocks in live templates | Advanced → |
| Extensibility | Custom filters, tests, globals, loaders | Extending → |
| T-Strings (PEP 750) | k() auto-escaping, r() composable regex (Python 3.14+) |
T-Strings → |
| HTMX Helpers | hx_request(), hx_target(), csrf_token() for partials |
Custom Globals → |
| Worker Auto-Tuning | get_optimal_workers(), should_parallelize() for parallel render |
Workers → |
📚 Full documentation: lbliii.github.io/kida
Usage
File-based Templates — Load from filesystem
from kida import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates/"))
template = env.get_template("page.html")
print(template.render(title="Hello", content="World"))
Template Inheritance — Extend base templates
base.html:
<!DOCTYPE html>
<html>
<body>
{% block content %}{% end %}
</body>
</html>
page.html:
{% extends "base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<p>{{ content }}</p>
{% end %}
Control Flow — Conditionals, loops, pattern matching
{% if user.is_active %}
<p>Welcome, {{ user.name }}!</p>
{% end %}
{% for item in items %}
<li>{{ item.name }}</li>
{% end %}
{% match status %}
{% case "active" %}
Active user
{% case "pending" %}
Pending verification
{% case _ %}
Unknown status
{% end %}
You may close blocks with explicit end keywords instead of {% end %} — for example {% endif %}, {% endfor %}, {% endblock %}, {% endcall %}, {% enddef %}. Deeply nested templates (roughly three or more levels) are easier to read and review with explicit closers.
{% if user.is_admin %}
<span class="badge">Admin</span>
{% endif %}
Run kida check <template_dir> to parse every *.html file under a directory (CI-friendly syntax and import resolution checks).
Components & Named Slots — Reusable UI composition
{% 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 %}
{% slot %} is the default slot. Named slot blocks inside {% call %} map to
matching placeholders in {% def %}.
Regions — Parameterized blocks for render_block
{% region sidebar(current_path="/") %}
<nav>{{ current_path }}</nav>
{% end %}
{{ sidebar(current_path="/about") }}
Regions are both blocks (for render_block()) and callables (for {{ name(args) }}).
They can read outer-context variables. Use for HTMX OOB, layout composition, and
framework integration. See Functions → Regions.
Filters & Pipelines — Transform values
{# Traditional syntax #}
{{ title | escape | capitalize | truncate(50) }}
{# Pipeline operator #}
{{ title |> escape |> capitalize |> truncate(50) }}
{# Custom filters #}
{{ items | sort(attribute="name") | first }}
Streaming Rendering — Yield chunks as they're ready
from kida import Environment
env = Environment()
template = env.from_string("""
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% end %}
</ul>
""")
# Generator: yields each statement as a string chunk
for chunk in template.render_stream(items=["a", "b", "c"]):
print(chunk, end="")
# RenderedTemplate: lazy iterable wrapper
from kida import RenderedTemplate
rendered = RenderedTemplate(template, {"items": ["a", "b", "c"]})
for chunk in rendered:
send_to_client(chunk)
Works with inheritance ({% extends %}), includes, and all control flow. Blocks like {% capture %} and {% spaceless %} buffer internally and yield the processed result.
Async Templates — Await in templates
{% async for item in fetch_items() %}
{{ item }}
{% end %}
{{ await get_user() }}
Fragment Caching — Cache expensive blocks
{% cache "navigation" %}
{% for item in nav_items %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% end %}
{% end %}
Block Rendering — Fragments and layout composition
# Render a single block (HTMX partials, cached nav)
html = template.render_block("content", title="Hello")
# Compose layout with pre-rendered blocks
layout = env.get_template("_layout.html")
html = layout.render_with_blocks({"content": inner_html}, title="Page")
Use Cases
| Use case | Key APIs | Example |
|---|---|---|
| Static sites | render(), fragment cache, bytecode cache |
Bengal |
| Dynamic web | render_block(), render_stream(), render_with_blocks() |
Chirp |
| Streaming / SSE | render_stream(), render_stream_async() |
Chunked HTTP, LLM streaming |
| Framework integration | template_metadata(), validate_block_exists(), get_structure() |
Build adapters, validate routes |
Architecture
Compilation Pipeline — AST-native
Template Source → Lexer → Parser → Kida AST → Compiler → Python AST → exec()
Kida generates ast.Module objects directly. This enables:
- Structured code manipulation — Transform and optimize AST nodes
- Compile-time optimization — Dead code elimination, constant folding
- Precise error source mapping — Exact line/column in template source
Dual-Mode Rendering — StringBuilder + streaming generator
# render() — StringBuilder (fastest, default)
_out.append(...)
return "".join(_out)
# render_stream() — Generator (streaming, chunked HTTP)
yield ...
The compiler generates both modes from a single template. render() uses StringBuilder for maximum throughput. render_stream() uses Python generators for statement-level streaming — ideal for chunked HTTP responses and Server-Sent Events.
Thread Safety — Free-threading ready
All public APIs are thread-safe by design:
- Template compilation — Idempotent (same input → same output)
- Rendering — Uses only local state (StringBuilder pattern)
- Environment — Copy-on-write for filters/tests/globals
- LRU caches — Atomic operations
Module declares itself GIL-independent via _Py_mod_gil = 0 (PEP 703).
Performance
- Minimal — ~4.0µs (file-based)
- Small — ~6.8µs (loop + filter)
- Medium — ~0.2ms (~100 vars)
- Large — ~1.6ms (1000 loop items) — 2.5x faster than Jinja2
- Complex — ~19µs (3-level inheritance) — 1.5x faster than Jinja2
- Concurrent (2–4 workers) — scales with worker count under Python 3.14t free-threading
See benchmarks/README.md and benchmarks/RESULTS.md for full Kida vs Jinja2 comparison.
Documentation
| Section | Description |
|---|---|
| Get Started | Installation and quickstart |
| Syntax | Template language reference |
| Usage | Loading, rendering, escaping |
| Framework Integration | Block rendering, introspection, adapters |
| Extending | Custom filters, tests, loaders |
| Reference | Complete API documentation |
| Tutorials | Jinja2 migration, Flask integration |
Development
git clone https://github.com/lbliii/kida.git
cd kida
# Uses Python 3.14t by default (.python-version)
uv sync --group dev --python 3.14t
PYTHON_GIL=0 uv run --python 3.14t pytest
The Bengal Ecosystem
A structured reactive stack — every layer written in pure Python for 3.14t free-threading.
| ᓚᘏᗢ | Bengal | Static site generator | Docs |
| ∿∿ | Purr | Content runtime | — |
| ⌁⌁ | Chirp | Web framework | Docs |
| =^..^= | Pounce | ASGI server | Docs |
| )彡 | Kida | Template engine ← You are here | Docs |
| ฅᨐฅ | Patitas | Markdown parser | Docs |
| ⌾⌾⌾ | Rosettes | Syntax highlighter | Docs |
Python-native. Free-threading ready. No npm required.
License
MIT License — see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kida_templates-0.2.9.tar.gz.
File metadata
- Download URL: kida_templates-0.2.9.tar.gz
- Upload date:
- Size: 380.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6bec9814926ed311d0b28b974f369bb40066f483b9287bc9cfb991177b9d8d3
|
|
| MD5 |
0b2b78fd37dca50ec347fd5bf5c7d927
|
|
| BLAKE2b-256 |
e1b46f0d560341ee647255a654983964b9cab292c2297af71c10397e907564b2
|
Provenance
The following attestation bundles were made for kida_templates-0.2.9.tar.gz:
Publisher:
python-publish.yml on lbliii/kida
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kida_templates-0.2.9.tar.gz -
Subject digest:
c6bec9814926ed311d0b28b974f369bb40066f483b9287bc9cfb991177b9d8d3 - Sigstore transparency entry: 1164598093
- Sigstore integration time:
-
Permalink:
lbliii/kida@5d0a5f5690ae2dafe93398feb9a8f25329a88b5b -
Branch / Tag:
refs/tags/v0.2.9 - Owner: https://github.com/lbliii
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@5d0a5f5690ae2dafe93398feb9a8f25329a88b5b -
Trigger Event:
release
-
Statement type:
File details
Details for the file kida_templates-0.2.9-py3-none-any.whl.
File metadata
- Download URL: kida_templates-0.2.9-py3-none-any.whl
- Upload date:
- Size: 265.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7244eaff10c9465c0debe7fce79a26c37b6c7b2eb9faa058951dfbc1354380c5
|
|
| MD5 |
15b98a68a6d130b0c088f944af21831c
|
|
| BLAKE2b-256 |
f3fc1288087da032c0d85782d201d26dbab532c979a4f915acf471aca7f0f844
|
Provenance
The following attestation bundles were made for kida_templates-0.2.9-py3-none-any.whl:
Publisher:
python-publish.yml on lbliii/kida
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kida_templates-0.2.9-py3-none-any.whl -
Subject digest:
7244eaff10c9465c0debe7fce79a26c37b6c7b2eb9faa058951dfbc1354380c5 - Sigstore transparency entry: 1164598147
- Sigstore integration time:
-
Permalink:
lbliii/kida@5d0a5f5690ae2dafe93398feb9a8f25329a88b5b -
Branch / Tag:
refs/tags/v0.2.9 - Owner: https://github.com/lbliii
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@5d0a5f5690ae2dafe93398feb9a8f25329a88b5b -
Trigger Event:
release
-
Statement type: