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.0.tar.gz (66.1 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.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file mystace-1.0.0.tar.gz.

File metadata

  • Download URL: mystace-1.0.0.tar.gz
  • Upload date:
  • Size: 66.1 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.0.tar.gz
Algorithm Hash digest
SHA256 3dde38020fcd296a08a8ccb542dd5952687d9aad7ba4d9dccadc8cc4c46634bc
MD5 7712fb556ee731d10dd8a9df18474906
BLAKE2b-256 598035602b93fc1a3d6e736f98326a2d279bc58908fd2c40a72d3f5e0d1378e0

See more details on using hashes here.

File details

Details for the file mystace-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: mystace-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 13.2 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3ede75c15a4251accd4324bdd700f54a6ef2f127af33c50c514a4a2b91a48357
MD5 24fbffe86ad34a89951eee685212700f
BLAKE2b-256 5434597c2e66e5cd8dc44eae4499bad8507d24154f90643242d8bf830784a145

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