top of page

Python - I Mustache You a Question

Updated: Nov 1, 2023

Do you separate your data from its presentation?

I don't officially have a Computer Science background (I got where I am via ECE), so certain concepts or terms aren't always in the forefront of my mind. One of those things is "Separation of Concerns" which is something that I like. Because many times in my career, I've inherited code that "grew organically" which is a polite way of saying that it ended up needing some marinara. It is often "fetch, process, and present the data" - all interleaved in one function / method.

Most recently, it was some code that sent a daily status email to the users. And then there was a second method for a detailed email for the developers. You can already see one problem without the source. But I'll show another anyway:

msg += "Header"
# Process some data and create info_list
for info in info_list:
    msg += info.field1 ...

Everything here is being shoved into a single string, which is bad form (I believe it's since Python strings are immutable, so it's really creating more strings to just garbage collect later, but I'm no expert so I may be wrong here in the details).

If this was it, honestly it would probably be acceptable, or at least a lower priority than other refactoring. But there's a lot of digging to do when the customer says "also include this in the email too." Or when you're trying to tweak the formatting and can't find where it actually happens. Or you want to drop a copy into a log file.

Enter The Template

Templates have been around for a long long time. I remember using Genshi with Trac over a decade ago! And in Python, there's a plethora available: Jinja2, Django's native backend, etc. For the simplest, there's even a built-in string.Template. But what about that forced pun in the title? There's a nice little templating language with implementations in over forty languages called Mustache.

How Easy Is It?

Here's some of the new code:

template = """
{{field1}}   {{field2}}

That pairing of "#" and "/" replaces the "for" loop in the first example, and makes it a lot clearer what is going into the email that is to be sent. There seem to be a few python engines available, and I chose chevron, which is easily installed via pip/PyPI. There's also braces, pystache, etc., but chevron claimed to be faster and had updates this year. To make the magic happen with the above template you call the "render" function:

msg = chevron.render(template, 
    {'info_list': info_list}

And that's it!

As an added bonus, chevron doesn't care if you throw extra data at it, so I really did this:

def generate_email(self, style='detailed'):
    template = {}
    template['basic'] = """ ... """
    template['detailed'] = """ ... """
    return chevron.render(template[style], ...)

Where the "basic" email just ignored any additional data, allowing me to replace the original method with a call to the new one:

def generate_basic_email(self):
    return self.generate_email(style='basic')

What I Didn't Like

One problem I had is that I was trying to maintain 1:1 parity with the original code, and mustache is a little too simple - it doesn't have any justification! So I was converting strings full of things like %8s and %4d and needed to add whitespace. To work around that, my final code wasn't as clean as the example above:

justify = lambda x, y: ("{{:>{}}}".format(y)).format(str(x))

    {'param': justify(val, 8),  # %8d
     'dict_param': {k: justify(v, 3) for k, v in val_dict.items()},  # %3s

That's not the fault of chevron; it's the mustache language specification's shortcoming. Maybe some day somebody will extend the spec to allow it.


Read more at:

Aaron D. Marasco started programming by reading Turbo Pascal manuals in his front lawn during a mid '80s summer, has been using Unix and Linux since 1993 and 1999, and hates long walks on the beach. He has been a Jack-of-All-Trades and Engineering Catalyst at Innoplex since 2019.

741 views0 comments


bottom of page