Skip to main content

mystace - A fast, pure Python {{mustache}} renderer

PyPI version Project Status: Active – The project has reached a stable, usable state and is being actively developed. tests lint License: MIT Ruff Checked with mypy

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!'

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('&', '&amp;').replace('<', '&lt;')

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)

Source distribution for mystace 1.0.1
File Size Uploaded Provenance
mystace-1.0.1.tar.gz 65.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mystace 1.0.1
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page