PyTera

A fast, Python-native templating engine powered by Rust's Tera library. PyTera brings the power and performance of Tera templates to Python applications through PyO3 bindings.
Features
- 🚀 High Performance: Rust-powered templating with zero-copy operations
- 🐍 Python Native: Seamless integration with Python data types and workflows
- 📝 Tera Compatible: Full support for Tera template syntax and features
- 🔧 Easy Integration: Simple API that works with Flask, FastAPI, and other web frameworks
- 🛡️ Type Safe: Comprehensive type hints and error handling
- 📚 Rich Features: Variables, conditionals, loops, filters, inheritance, and more
Installation
Install PyTera from PyPI:
pip install pytera
Or using uv:
uv add pytera
Requirements
- Python 3.8+
- Rust toolchain (for building from source)
Quick Start
import os
from pytera import PyTera
template_dir = os.path.join(os.path.dirname(__file__), "templates")
tera = PyTera(f"{template_dir}/*.html")
result = tera.render_template("basic_variables.html", name="Alice", age=30)
print(result)
ℹ️ Glob pattern tips: The current templates live directly under templates/, so PyTera(f"{template_dir}/*.html") works. If you reorganize templates into nested subdirectories, switch to PyTera(f"{template_dir}/**/*.html") to load them recursively.
Usage Examples
Basic Variables
tera = PyTera("templates/*.html")
result = tera.render_template(
"basic_variables.html",
name="Alice",
age=30,
)
print(result)
ℹ️ Glob pattern tips: Current templates live directly under templates/, so PyTera(f"{template_dir}/*.html") works. If you organize templates into nested subdirectories later, switch to PyTera(f"{template_dir}/**/*.html") to load them recursively.
Hello {{ name }}! You are {{ age }} years old.
Conditionals
user = {"name": "Bob", "is_admin": True}
result = tera.render_template("conditionals.html", user=user)
print(result)
{% if user.is_admin %}
Welcome, Administrator {{ user.name }}!
{% else %}
Hello, {{ user.name }}!
{% endif %}
Loops
items = [
{"name": "Apple", "price": 1.50},
{"name": "Banana", "price": 0.75},
{"name": "Cherry", "price": 2.25},
]
result = tera.render_template("loops.html", items=items)
print(result)
<ul>
{% for item in items %}
<li>{{ item.name }}: {{ item.price | round(precision=2) }}</li>
{% endfor %}
</ul>
Filters
data = {
"text": "hello world",
"missing": None,
"list": ["apple", "banana", "cherry", "date"],
}
result = tera.render_template("filters.html", **data)
print(result)
<p>Uppercase: {{ text | upper }}</p>
<p>Length: {{ text | length }}</p>
<p>Default: {{ missing | default(value="N/A") }}</p>
<p>Slice: {{ list | slice(start=1, end=3) | join(sep=", ") }}</p>
Template Inheritance
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<header>
<h1>My Website</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2023</p>
</footer>
</body>
</html>
{% extends "base.html" %}
{% block title %}Home Page{% endblock %}
{% block content %}
<h2>Welcome to {{ site_name }}</h2>
<p>This is the home page content.</p>
{% if user %}
<p>Hello, {{ user.name }}!</p>
{% endif %}
{% endblock %}
Flask Integration
import os
from flask import Flask, render_template
from pytera import PyTera
template_dir = os.path.join(os.path.dirname(__file__), "..", "templates")
tera = PyTera(f"{template_dir}/*.html")
app = Flask(__name__, template_folder=os.path.abspath(template_dir))
@app.route("/")
def index():
return tera.render_template(
"child.html",
site_name="example",
user={"name": "David"},
)
@app.route("/child")
def child():
return render_template(
"child.html",
site_name="example",
user={"name": "David"},
)
For a complete working example with additional routes (/basic_variables, /conditionals, /filters, /loops), see examples/app.py.
Template Syntax
The templates rendered in examples/app.py cover the core pieces of Tera syntax:
Variables
Hello {{ name }}! You are {{ age }} years old.
Conditionals
{% if user.is_admin %}
Welcome, Administrator {{ user.name }}!
{% else %}
Hello, {{ user.name }}!
{% endif %}
Loops
<ul>
{% for item in items %}
<li>{{ item.name }}: {{ item.price | round(precision=2) }}</li>
{% endfor %}
</ul>
Filters
<p>Uppercase: {{ text | upper }}</p>
<p>Length: {{ text | length }}</p>
<p>Default: {{ missing | default(value="N/A") }}</p>
<p>Slice: {{ list | slice(start=1, end=3) | join(sep=", ") }}</p>
Template Inheritance
{% extends "base.html" %}
{% block title %}Home Page{% endblock %}
{% block content %}
<h2>Welcome to {{ site_name }}</h2>
{% if user %}
<p>Hello, {{ user.name }}!</p>
{% endif %}
{% endblock %}
For more template features—such as macros, tests, and custom filters—consult the Tera documentation.
Error Handling
PyTera provides detailed error messages for common issues:
- Template Not Found: When requesting a non-existent template
- Invalid Context: When context keys aren't strings
- Parsing Errors: Syntax errors in templates
- Inheritance Issues: Circular dependencies or missing parents
Development
Building from Source
git clone https://github.com/un4gt/pytera.git
cd pytera
uv sync --dev
maturin develop
pytest
Running Tests
pytest
pytest --cov=pytera --cov-report=html
Code Quality
cargo fmt
black src/
cargo clippy
flake8 src/
Contributing
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
Development Setup
uv sync --dev
pre-commit install
maturin develop
pytest
License
PyTera is licensed under the MIT License. See LICENSE for details.
Acknowledgments
- Tera - The Rust templating engine
- PyO3 - Python bindings for Rust
- Maturin - Build tool for Python extensions
Changelog
See CHANGELOG.md for version history.