Ian Bicking: the old part of his blog

Templating via dict wrappers

James Tauber wrote a kind of narrative construction of a template language using simple string substitution. It's an interesting way of building up a template language, but I think unnecessarily complex because of some minor details.

The basic structure he uses is this:

class Template:

    _template = 'some string with %(sub)s'

    def __init__(self, dict):
        self.dict = dict

    def __str__(self):
        return self._template % self

    def __getitem__(self, key):
        # or some fancier processing...
        return self.dict[key].upper()

I think it's easier to not think so much in terms of templates as classes, and to put all the logic in __getitem__ mostly disassociated from templates.

The basic pattern is that of a wrapper:

class Wrapper(object):
    def __init__(self, dict):
        self.dict = dict
    def __getitem__(self, item):
        raise NotImplementedError

Used like this:

rendered = 'some string' % SomeWrapper(data_source)

For instance, his piping technique:

class Piper(Wrapper):

    def __getitem__(self, item):
        parts = item.split("|")
        value = self.dict[parts[0]]
        for func in parts[1:]:
            value = self.dict[func](value)
        return value

Then if you use a key of name|upper you will really mean dict['upper'](dict['name']). You actually use it like:

tmpl = "Are you %(name|upper)s?!?"
rendered = tmpl % Piper({'name': 'the keymaster',
                         'upper': lambda x: x.upper()})

Because of the way string.Template is defined you can't use it with complex key names, but this fixes it (from paste.script.copydir):

class LaxTemplate(string.Template):
    # This change of pattern allows for anything in braces, but
    # only identifiers outside of braces:
    pattern = re.compile(r"""
    \$(?:
      (?P<escaped>\$)             |   # Escape sequence of two delimiters
      (?P<named>[_a-z][_a-z0-9]*) |   # delimiter and a Python identifier
      {(?P<braced>.*?)}           |   # delimiter and a braced identifier
      (?P<invalid>)                   # Other ill-formed delimiter exprs
    )
    """, re.VERBOSE | re.IGNORECASE)

Then you can do:

tmpl = LaxTemplate('Are you ${name|upper}?!?').substitute(
    Piper({'name': 'the keymaster', 'upper': lambda x: x.upper()}))

So, what if you want to HTML quote everything by default?

class HTMLQuote(Wrapper):

    def __getitem__(self, item):
        value = self.dict[item]
        return cgi.escape(str(value), 1)

What if you want to allow arbitrary expressions for keys?

class Eval(Wrapper):

    def __getitem__(self, item):
        return eval(item, self.dict)

Now you can use a template like Name: %(user.name)s.

What if you want catch exceptions ZPT-style, where you use | to indicate alternatives?

class ZPTCatcher(Wrapper):

    def __getitem__(self, item):
        alternatives = item.split("|")
        for i in alternatives[:-1]:
            try:
                return self.dict[i]
            except (KeyError, AttributeError):
                pass
        return self.dict[alternatives[-1]]

What if, again ZPT-like, you want to allow different kinds of wrappers based on a prefix? E.g., python:expr for Python syntax, django:value|filter for Django syntax, string:a${b} for a literals with string.Template style substitution.

class Dispatcher(Wrapper):

    def __init__(self, dict, **prefix_wrappers):
        self.dict = dict
        self.prefix_wrappers = prefix_wrappers

    def __getitem__(self, item):
        if ':' not in item:
            wrapper = self.prefix_wrappers['default']
            expr = item
        else:
            prefix, expr = item.split(':', 1)
            wrapper = self.prefix_wrappers[prefix]
        return wrapper(self.dict)[expr]

class StringTemplate(Wrapper):

    def __getitem__(self, item):
        tmpl = string.Template(item)
        return tmpl.substitute(self.dict)

fancy_dict = Dispatcher(some_dict,
                        python=Eval,
                        string=StringTemplate,
                        django=Piper)

Note that these dictionaries can be used with % or string.Template or anything that uses __getitem__ (and only __getitem__).

This is one reason why any string substitution should allow passing in a dictionary-like object, not just use **kw for passing in dictionaries. This seems to still be a flaw of PEP 3101, which is a expanded string formatting proposal for Python 3. The reason **kw can't work is that these are only very lightly dictionary-like; there is no keys() method. Eval(locals()).keys() would have to enumerate all possible expressions; clearly not feasible. And **kw has to unpack the dictionary (enumerating it) before it can pass it into the function.

Created 19 May '06

Comments:

you may want to take a look at Templite: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496702

it just lets you embed python code directly into your template.

for example, this code:

tmpl = "Are you %(name|upper)s?!?"

rendered = tmpl % Piper({'name': 'the keymaster', 'upper': lambda x: x.upper()})

becomes:

tmpl = Templite("Are you ${emit(name.upper())}$?!?"

rendered = tmpl(name ="the keymaster")

-tomer

# tomer filiba

I had basically the same idea a few months ago and wrote up a generic object wrapper to help with template substitution. You've inspired me to add it to the Cookbook: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496730

# Costas