Schema-validated Jinja2 templates — typed contracts that catch bad data before render.
Project description
templane-python
Python implementation of Templane — typed template contracts for Jinja2.
Templane adds compile-time schema validation to your templates. Define what data your template expects in a small .schema.yaml file next to your .jinja, and Templane catches missing fields, typos, and wrong types before Jinja renders, not at 2am in production.
- Conformance: 40/40 fixtures across the Templane protocol · 75 unit tests
- Engine binding: Jinja2 (
jinja_templane) - Also ships: breaking-change detector, schema hash
- Runtime: Python 3.12+
- License: Apache 2.0
Install
pip install templane-python
# or with uv:
uv add templane-python
Quick start
Create email.jinja (plain Jinja2 — not modified by Templane):
Hi {{ user.name }}! Your order #{{ order_id }} total is ${{ amount }}.
Create email.schema.yaml next to it (declares the data contract):
body: ./email.jinja
engine: jinja
user:
type: object
required: true
fields:
name: { type: string, required: true }
order_id:
type: string
required: true
amount:
type: number
required: true
Use from Python:
from jinja_templane import TemplaneEnvironment, TemplaneTemplateError
env = TemplaneEnvironment("templates")
tmpl = env.get_template("email.schema.yaml")
try:
output = tmpl.render(
user={"name": "Alice"},
order_id="INV-042",
amount=99.00,
)
print(output)
# → "Hi Alice! Your order #INV-042 total is $99.0."
except TemplaneTemplateError as exc:
for err in exc.errors:
print(f"[{err.code}] {err.field}: {err.message}")
Validation errors — caught before rendering
# Missing field + wrong type both trip at once
try:
tmpl.render(
# user missing entirely
order_id=42, # wrong type
amount="free", # wrong type
)
except TemplaneTemplateError as exc:
for err in exc.errors:
print(f"[{err.code}] {err.field}")
# [missing_required_field] user
# [type_mismatch] order_id
# [type_mismatch] amount
All errors are collected — never short-circuits at the first.
Breaking-change detection
Detect schema evolution issues before they break downstream data:
from templane_core.schema_parser import parse as parse_schema
from templane_core.models import typed_schema_from_dict
from templane_core.breaking_change import detect
def load(yaml_str: str, name: str):
result = parse_schema(yaml_str, name)
return typed_schema_from_dict(result["schema"])
old = load(open("schema-v1.yaml").read(), "v1")
new = load(open("schema-v2.yaml").read(), "v2")
changes = detect(old, new)
for c in changes:
print(f"[{c.category}] {c.field_path}: {c.old} → {c.new}")
# Four categories:
# removed_field — schema had the field; now doesn't
# required_change — optional → required
# type_change — field type changed
# enum_value_removed — an enum value was removed
Safe changes (new optional field, added enum value, required → optional) are NOT reported.
API
jinja_templane — the engine binding
from jinja_templane import (
TemplaneEnvironment,
TemplaneTemplate,
TemplaneTemplateError,
)
# Like jinja2.Environment, but get_template returns a TemplaneTemplate
env = TemplaneEnvironment(search_path: str | Path)
env.get_template(name: str) -> TemplaneTemplate
# Handles both .schema.yaml (with body: reference) and legacy inline-body .templane files
tmpl.render(**data) -> str # raises TemplaneTemplateError on validation failure
tmpl.schema # the parsed TypedSchema
templane_core — the protocol primitives
from templane_core.schema_parser import parse, load_from_path
from templane_core.type_checker import check
from templane_core.ir_generator import generate
from templane_core.breaking_change import detect
from templane_core.hash import schema_hash
# parse(yaml_str, schema_id) → dict with {schema, body?, body_path?, engine?, error?}
# load_from_path(path) → same shape, with body resolved from disk when sidecar
# check(schema, data) → list[TypeCheckError]
# generate(ast, data, schema_id, template_id) → TIRResult
# detect(old_schema, new_schema) → list[BreakingChange]
# schema_hash(schema) → "sha256:..." stable across equivalent schemas
Why Templane
Templates are untyped contracts. They accept a bag of values, look up names by string, and render something — even when the data has a typo, a missing field, or a wrong type. The failure is silent: the render succeeds, the customer gets a broken email, and you find out four days later.
Templane fixes this at the boundary. A schema next to your template declares what the template expects; the binding refuses to render when the data doesn't match. See the main README for the full pitch.
Adoption pattern
You don't migrate templates. Your existing .jinja files stay as-is. You drop one .schema.yaml beside each one:
templates/
welcome.jinja ← untouched
welcome.schema.yaml ← NEW
invoice.jinja ← untouched
invoice.schema.yaml ← NEW
Your code switches from jinja2.Environment to jinja_templane.TemplaneEnvironment. That's the migration.
Examples
Six worked examples under the repo's templane-python/examples/: hello, validation errors, nested objects and lists, Jinja features (filters, if/for), breaking-change detection, and a full password-reset email demo.
Building from source
git clone https://github.com/ereshzealous/Templane.git
cd Templane/templane-python
uv sync --extra dev
.venv/bin/pytest # 75 tests
Links
- Repo: https://github.com/ereshzealous/Templane
- Full spec: SPEC.md
- Architecture & cross-language conformance: main README
- Issues: GitHub Issues
- PyPI:
templane-python
License
Apache License 2.0
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 templane_python-0.2.0.tar.gz.
File metadata
- Download URL: templane_python-0.2.0.tar.gz
- Upload date:
- Size: 36.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
767f2f102470e9fa0bbbbff9242348562dc1f3e2f3666839af1b314cb995b92f
|
|
| MD5 |
774db118eaaa6c09987ec94f61c42edd
|
|
| BLAKE2b-256 |
decd4034e8f7ecfa3f7738c0f37fb44d1d5b552694ba3173471d99ae46aa829c
|
Provenance
The following attestation bundles were made for templane_python-0.2.0.tar.gz:
Publisher:
release-templane-python.yml on ereshzealous/Templane
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
templane_python-0.2.0.tar.gz -
Subject digest:
767f2f102470e9fa0bbbbff9242348562dc1f3e2f3666839af1b314cb995b92f - Sigstore transparency entry: 1393526394
- Sigstore integration time:
-
Permalink:
ereshzealous/Templane@bc9b24e9c6a7852185a5763f80b7deac1e3378b3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ereshzealous
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-templane-python.yml@bc9b24e9c6a7852185a5763f80b7deac1e3378b3 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file templane_python-0.2.0-py3-none-any.whl.
File metadata
- Download URL: templane_python-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
855916c682db6b7c338c76d19846858004f76a37c838b6d1d25079416a92d5cd
|
|
| MD5 |
a3e6c2fb0b706842e21d95c649566068
|
|
| BLAKE2b-256 |
d38e8089c1a730f5b0cad6cbd7ff0b14a1d5007e67686ba2066ca611bb202dd8
|
Provenance
The following attestation bundles were made for templane_python-0.2.0-py3-none-any.whl:
Publisher:
release-templane-python.yml on ereshzealous/Templane
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
templane_python-0.2.0-py3-none-any.whl -
Subject digest:
855916c682db6b7c338c76d19846858004f76a37c838b6d1d25079416a92d5cd - Sigstore transparency entry: 1393526459
- Sigstore integration time:
-
Permalink:
ereshzealous/Templane@bc9b24e9c6a7852185a5763f80b7deac1e3378b3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ereshzealous
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-templane-python.yml@bc9b24e9c6a7852185a5763f80b7deac1e3378b3 -
Trigger Event:
workflow_dispatch
-
Statement type: