The Artima Developer Community
Sponsored Link

Python Buzz Forum
Templating via dict wrappers

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Ian Bicking

Posts: 900
Nickname: ianb
Registered: Apr, 2003

Ian Bicking is a freelance programmer
Templating via dict wrappers Posted: Jul 12, 2006 12:15 AM
Reply to this message Reply

This post originated from an RSS feed registered with Python Buzz by Ian Bicking.
Original Post: Templating via dict wrappers
Feed Title: Ian Bicking
Feed URL: http://www.ianbicking.org/feeds/atom.xml
Feed Description: Thoughts on Python and Programming.
Latest Python Buzz Posts
Latest Python Buzz Posts by Ian Bicking
Latest Posts From Ian Bicking

Advertisement

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.

Read: Templating via dict wrappers

Topic: Elisa, first alpha release! Previous Topic   Next Topic Topic: links for 2006-07-09

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use