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
Metadata
Release files for mystace 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mystace-1.0.1.tar.gz | 65.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mystace-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.8 kB
Release files / mystace-1.0.1.tar.gz
| Download URL | mystace-1.0.1.tar.gz |
|---|---|
| Size | 65.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
16327365db587e10f443991f3db19783a0d1c64c9ee0e4605bef8ff1b70c5f3d
|
|
BLAKE2b-256 checksum How to use checksums |
dc5f851b70cc27ea102c824888f820443150af47f287d098c5cbe85d6f16b6b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / mystace-1.0.1-py3-none-any.whl
| Download URL | mystace-1.0.1-py3-none-any.whl |
|---|---|
| Size | 12.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8abb5c72aab676798c8a96f1892b3c7e9f40ea5e63c4551981716be14f0cafb5
|
|
BLAKE2b-256 checksum How to use checksums |
e71c49f77d16fc4146c1e93bdbfacb7373d70565ef8352e8440a1cfab5d81b67
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Security for mystace 1.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.