Skip to content

eliotwrobson/mystace

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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': '[email protected]'},
        {'name': 'Bob', 'email': '[email protected]'}
    ]
}

result = mystace.render_from_template(template, data)
# Output:
#   - Alice ([email protected])
#   - Bob ([email protected])

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

About

Rendering mustache templates in Python fast

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages