Skip to main content

Rendering mustache templates in Python fast.

Project description

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

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('&', '&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

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

mystace-1.0.1.tar.gz (65.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mystace-1.0.1-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

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

Hashes for mystace-1.0.1.tar.gz
Algorithm Hash digest
SHA256 16327365db587e10f443991f3db19783a0d1c64c9ee0e4605bef8ff1b70c5f3d
MD5 6c85dcbf688f4d4b87a9d8f7fb165c48
BLAKE2b-256 dc5f851b70cc27ea102c824888f820443150af47f287d098c5cbe85d6f16b6b3

See more details on using hashes here.

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

Hashes for mystace-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8abb5c72aab676798c8a96f1892b3c7e9f40ea5e63c4551981716be14f0cafb5
MD5 12fbc0a886f1201053f41d91d2d1d5f1
BLAKE2b-256 e71c49f77d16fc4146c1e93bdbfacb7373d70565ef8352e8440a1cfab5d81b67

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page