Rendering mustache templates in Python fast.
Project description
mystace - A fast, pure Python {{mustache}} renderer
A fast, spec-compliant Python implementation of the {{mustache}} templating language. A spiritual successor to chevron, optimized for performance through caching and efficient rendering.
Why mystace?
mystace is fast
Mystace outperforms all other pure Python mustache implementations through its cached rendering approach. Pre-parsing templates into an optimized tree structure means subsequent renders are extremely fast. Included microbenchmarks demonstrate significant performance advantages, particularly for repeated renders of the same template.
mystace is fully spec compliant
Mystace passes all 163 required tests from the official {{mustache}} spec v1.4.3, providing complete support for:
- Variables (escaped and unescaped)
- Sections (normal and inverted)
- Partials with proper indentation handling
- Comments
- Delimiter changes (e.g.,
{{=<% %>=}})
The 66 skipped tests are for optional features not in the core spec:
- Lambda functions (
~lambdas.json- 22 tests) - Dynamic partial names (
~dynamic-names.json- 22 tests) - Template inheritance (
~inheritance.json- 34 tests)
These optional modules (prefixed with ~ in the spec) may be implemented in future versions. To see detailed test results, check the spec test file.
Installation
pip install mystace
Requires Python 3.10 or higher.
Usage
Basic rendering
import mystace
# Simple variable substitution
result = mystace.render_from_template(
'Hello, {{ name }}!',
{'name': 'World'}
)
# Output: 'Hello, World!'
Cached rendering (recommended for repeated use)
For templates you'll render multiple times, use MustacheRenderer to cache the parsed template:
import mystace
# Parse template once
renderer = mystace.MustacheRenderer.from_template('Hello, {{ name }}!')
# Render multiple times with different data
print(renderer.render({'name': 'World'})) # Hello, World!
print(renderer.render({'name': 'Alice'})) # Hello, Alice!
print(renderer.render({'name': 'Bob'})) # Hello, Bob!
Sections
import mystace
template = '''
{{#users}}
- {{ name }} ({{ email }})
{{/users}}
'''
data = {
'users': [
{'name': 'Alice', 'email': 'alice@example.com'},
{'name': 'Bob', 'email': 'bob@example.com'}
]
}
result = mystace.render_from_template(template, data)
# Output:
# - Alice (alice@example.com)
# - Bob (bob@example.com)
Inverted sections
import mystace
template = '{{^items}}No items found.{{/items}}'
# With empty list
result = mystace.render_from_template(template, {'items': []})
# Output: 'No items found.'
# With items
result = mystace.render_from_template(template, {'items': [1, 2, 3]})
# Output: ''
Partials
import mystace
template = '{{>header}}Content here{{>footer}}'
partials = {
'header': '<header>{{title}}</header>',
'footer': '<footer>© 2025</footer>'
}
result = mystace.render_from_template(
template,
{'title': 'My Page'},
partials=partials
)
# Output: '<header>My Page</header>Content here<footer>© 2025</footer>'
Delimiter changes
import mystace
# Change delimiters to avoid conflicts
template = '''
{{=<% %>=}}
<script>
const data = <% data %>;
</script>
'''
result = mystace.render_from_template(template, {'data': '{"key": "value"}'})
# Output:
# <script>
# const data = {"key": "value"};
# </script>
Custom escaping and stringification
import mystace
# Custom HTML escaping
def my_escape(text):
return text.replace('&', '&').replace('<', '<')
result = mystace.render_from_template(
'{{ html }}',
{'html': '<div>Hello</div>'},
html_escape_fn=my_escape
)
# Custom stringification for non-string values
def my_stringify(value):
if isinstance(value, bool):
return str(value).lower()
return str(value)
result = mystace.render_from_template(
'Value: {{ flag }}',
{'flag': True},
stringify=my_stringify
)
# Output: 'Value: true'
Development
This project uses uv for dependency management:
# Install dependencies
uv sync
# Run tests
uv run pytest tests/
# Run tests with coverage
uv run pytest tests/ --cov=src/mystace --cov-report=term --ignore=tests/test_speed.py
# Run benchmarks (Python 3.14 only)
uv run pytest tests/test_speed.py --benchmark-only
# Format code
uv run ruff format
# Type checking
uv run mypy src/
Performance
Mystace is designed for speed through:
- Pre-parsed templates: Parse once, render many times with
MustacheRenderer - Efficient tree structure: Optimized internal representation
- Minimal overhead: Pure Python with no unnecessary allocations
Benchmark results show mystace as the fastest pure Python mustache implementation, particularly excelling at repeated renders of the same template.
Contributing
Contributions are welcome! Areas for improvement:
- Lambda function support
- Dynamic partial names
- Template inheritance
- Additional optimization
TODO
- Implement remaining spec features (lambdas, dynamic names, inheritance)
- Further performance optimizations
- Additional documentation and examples
Project details
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 mystace-1.0.1.tar.gz.
File metadata
- Download URL: mystace-1.0.1.tar.gz
- Upload date:
- Size: 65.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16327365db587e10f443991f3db19783a0d1c64c9ee0e4605bef8ff1b70c5f3d
|
|
| MD5 |
6c85dcbf688f4d4b87a9d8f7fb165c48
|
|
| BLAKE2b-256 |
dc5f851b70cc27ea102c824888f820443150af47f287d098c5cbe85d6f16b6b3
|
File details
Details for the file mystace-1.0.1-py3-none-any.whl.
File metadata
- Download URL: mystace-1.0.1-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8abb5c72aab676798c8a96f1892b3c7e9f40ea5e63c4551981716be14f0cafb5
|
|
| MD5 |
12fbc0a886f1201053f41d91d2d1d5f1
|
|
| BLAKE2b-256 |
e71c49f77d16fc4146c1e93bdbfacb7373d70565ef8352e8440a1cfab5d81b67
|