Tips and tricks

This module contains some mstache integration tips alongside general Mustache syntax tricks which can be useful for other implementations too (as long they’re spec compliant).

Lambda examples

Lambda functions are not always straightforward, especially when requiring context data, as they’re unable to directly access the current scope.

The standard solution is using the render function, usually multiple times, to retrieve the required data from context, as string, and then performing some parsing.

Datetime strformat

In this example we implement the common-case-scenario of formatting a datetime.datetime object using a lambda forwarding the custom format to datetime.datetime.strftime().

import datetime
import typing

import mstache

def strftime(text: str,
             render: collections.abc.Callable[[str], str]) -> str:
    """Render date/datetime scope with given strftime format."""
    iso = render('{{.}}').replace(' ', 'T')  # __str__ is stable
    try:
        dt = (
            datetime.datetime.fromisoformat(iso) if 'T' in iso else
            datetime.date.fromisoformat(iso)
            )
    except ValueError:
        return ''
    return dt.strftime(render(text))

print(mstache.render(
    '{{#dt}}{{#strftime}}%Y.%m.%d{{/strftime}}{{/dt}}',
    {
        'dt': datetime.datetime.now(),
        'strftime': strftime,
        },
    ))
# 2021.03.25

Streaming patterns

Streaming is a first-class citizen for mstache, enabling powerful patterns which are not only memory-efficient but more responsive than their buffered counterparts, especially over networks.

You can easily integrate mstache.stream() on most common scenarios with just a tiny bit of preprocessing, here are some examples.

JSON streaming

In this example we stream enveloped JSON by preprocessing that envelope as our template and then serializing every generator item individually (using enumerate() to enable comma insertion logic) while rendering.

import json
import mstache

# Given any row generator (like a database row cursor)
rows = (
    (i, 'a', 'b', 'c')
    for i in range(1000, 10000)
    )

# Streaming JSON template render
stream = mstache.stream(template,
    '{"results": [{{#rows}}{{&.}}{{/rows}}]}',  # enveloped JSON template
    {
        'rows': (
            # generator of partial JSON strings
            f',{json.dumps(row)}' if i else json.dumps(row)
            for i, row in enumerate(rows)
            ),
        },
    )
for chunk in stream:
    print(chunk)

# {"results": [
# [1000, "a", "b", "c"]
# ,[1001, "a", "b", "c"]
# ...
# ,[9999, "a", "b", "c"]
# ]}