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: typing.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, # prefixed to avoid collisions
},
),
)
# 2021.03.25
Virtual properties¶
Sometimes lambda functions are not enough to get the job done, and you might find yourself recursively patching your entire rendering scope with new keys or properties.
While in JavaScript would be way easier to temporarily patch the Object
prototype instead, using a similar approach on Python is a very bad idea.
To address this issue mstache.default_getter() exposes a virtuals
argument you can use to include your custom virtual property implementations
(just remember to include those already defined by
mstache.default_virtuals as well).
You can either use a custom getter wrapper or functools.partial()
to pass a custom mstache.default_getter() including your virtuals to
mstache.render().
import functools
import mstache
def word_count(text):
return len(text.split())
print(
mstache.render(
'{{obj.word_count}} words',
{'obj': 'virtual properties are cool'},
getter=functools.partial(
mstache.default_getter,
virtuals={
**mstache.default_virtuals,
'word_count': word_count,
},
),
),
)
# 4 words
Please note both AttributeError and TypeError
exceptions raised from virtual property functions are appropriately handled
by mstache.default_getter().
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)
)
# Serialize envelope with content placeholder
placeholder = '%CONTENT%'
envelope = json.dumps({'results': [placeholder]})
# Create mustache template by replacing the placeholder
template = envelope.replace(
json.dumps(placeholder),
'{{#rows}}{{#0}},{{/0}}{{&1}}{{/rows}}', # row mustache template
)
# Stream enveloped JSON rows
stream = mstache.stream(
template,
{'rows': enumerate(map(json.dumps, rows))},
)
for chunk in stream:
print(chunk)
# {"results": [
# [1000, "a", "b", "c"]
# ,
# ...
# ,
# [9999, "a", "b", "c"]
# ]}