I'm using the very nice Flask framework for a small project of mine (I'll blog about in few weeks) and started using the very handy Jinja templating engine.

At some point, I needed to have a kind of enum type in the engine, sometihng I can test with

{% if status == WARNING %}
...
{% elif status == ERROR %}
...
{% elif status == CRITICAL %}
...
{% endif %}

Even if there are many recipes for enum in Python, none of which made its way into the language, obvious solutions as

class Status:
    WARNING, ERROR, CRITICAL = range( 3 )
...
status = Status.WARNING

seems not to work here because, even if it is easy to put status in the template context, there seems to be no straightforward way to take there also the constants WARNING, ERROR, and CRITICAL.

So I came up with the idea of using an outer class (as in the above example), with a class attribute for every constant but instead of initializing it to an integer, to use an inner class with class attributes named is_WARNING, is_ERROR, and is_CRITICAL all initialized to False except the one corresponding to the outer attribute.

To cut a long story short (I'll give you my implementation at the end of the post), one can use it as follows in Python

>>> Status = enum4jinja( 'Stauts', 'WARNING ERROR CRITICAL' )
>>> Status.WARNING
Stauts.WARNING
>>> status = Status.ERROR
>>> status.is_ERROR
True
>>> status.is_CRITICAL
False

and, as simple as that, in a template

{% if status.is_WARNING %}
...
{% elif status.is_ERROR %}
...
{% elif status.is_CRITICAL %}
...
{% endif %}

Probably not the best way, but enough Pythonic and easy to use for me. Here is my rough implementation of such idea

def enum4jinja( name, labels ):
    labels = labels.split()
    outer = type( name, (object,), {    '__slots__': labels, '__repr__': lambda self : name } )()
    for label in labels:
        def repr( label ):
            def _( self ): return name + '.' + label
            return _
        inner = type( name + "_" + label, (object,), { '__slots__': [ 'is_' + _ for _ in labels ], '__repr__': repr( label ) } )()
        for l in labels: setattr( inner, 'is_' + l, False )
        setattr( inner, 'is_' + label, True )
        setattr( outer, label, inner )
    return outer

Courteous comments welcome…

Comments