Python Templates

by James Tauber. See blog entry to comment.

Originally posted to the Python Web SIG back in 2003. Published here May 2006 with minor changes.

Step 1

Simple string substitution can be achieved with the % operator:

name = "Guido"

print "Hello %s!" % name

Step 2

...which can be used with a dictionary:

dict = {"name": "Guido"}

print "Hello %(name)s!" % dict

Step 3

The template can be a class, where the dictionary is passed into the constructor and __str__ is overridden to make the substitution:

class Template:

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

    def __str__(self):
        return "Hello %(name)s!" % self.dict


print Template({"name": "Guido"})

Step 4

__getitem__ can be overridden to perform additional processing on values:

class Template:

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

    def __str__(self):
        return "Hello %(name)s!" % self

    def __getitem__(self, key):
        return self.dict[key].upper()


print Template({"name": "Guido"})

Step 5

Processing can even be driven from the template itself, with %(...)s referencing a function to apply to the value:

class Template:

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

    def __str__(self):
        return "Hello %(name)s. Hello %(name|upper)s!" % self

    def __getitem__(self, key):
        l = key.split("|")
        if len(l) == 1:
            return self.dict[key]
        else:
            return getattr(self, l[1])(self.dict[l[0]])

    def upper(self, s):
        return s.upper()


print Template({"name": "Guido"})

Step 6

Values in the dictionary can even be lists whose items are processed individually:

class Template:

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

    def __str__(self):
        return "<ul>\n%(list|li)s\n</ul>" % self

    def __getitem__(self, key):
        l = key.split("|")
        if len(l) == 1:
            return self.dict[key]
        else:
            return getattr(self, l[1])(self.dict[l[0]])

    def li(self, l):
        return "\n".join(["\t<li>%s</li>" % x for x in l])


print Template({"list": ["foo", "bar", "baz"]})

Step 7

The template can be moved into a class attribute:

class Template:

    _template = """<ul>\n%(list|li)s\n</ul>"""

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

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

    def __getitem__(self, key):
        l = key.split("|")
        if len(l) == 1:
            return self.dict[key]
        else:
            return getattr(self, l[1])(self.dict[l[0]])

    def li(self, l):
        return "\n".join(["\t<li>%s</li>" % x for x in l])


print Template({"list": ["foo", "bar", "baz"]})

Step 8

In some cases, you may want a value to come from a method rather than the dictionary:

class Template:

    _template = """<ul>\n%(lst|li)s\n</ul>"""

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

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

    def __getitem__(self, key):
        return self._process(key.split("|"))

    def _process(self, l):
        arg = l[0]
        if len(l) == 1:
            if arg in self.dict:
                return self.dict[arg]
            elif hasattr(self, arg) and callable(getattr(self, arg)):
                return getattr(self, arg)()
            else:
                raise KeyError(arg)
        else:
            func = l[1]
            return getattr(self, func)(self._process([arg]))

    def lst(self):
        return ["foo", "bar", "baz"]

    def li(self, l):
        return "\n".join(["\t<li>%s</li>" % x for x in l])


print Template()

Step 9

Now let's define a base template class and try multiple instances where we delegate formatting of the items to a different template than the overall list itself:

# the base template taken from previous example

class DictionaryTemplate:

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

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

    def __getitem__(self, key):
        return self._process(key.split("|"))

    def _process(self, l):
        arg = l[0]
        if len(l) == 1:
            if arg in self.dict:
                return self.dict[arg]
            elif hasattr(self, arg) and callable(getattr(self, arg)):
                return getattr(self, arg)()
            else:
                raise KeyError(arg)
        else:
            func = l[1]
            return getattr(self, func)(self._process([arg]))


# template for individual items       

class LI_Template:

    _template = """\t<li>%s</li>"""

    def __init__(self, input_list=[]):
        self.input_list = input_list

    def __str__(self):
        return "\n".join([self._template % x for x in self.input_list])


# template for overall list

class UL_Template(DictionaryTemplate):

    _template = """<ul>\n%(lst|li)s\n</ul>"""

    def li(self, input_list):
        return LI_Template(input_list)


print UL_Template({"lst": ["foo", "bar", "baz"]})

Step 10

Much of the LI_Template can be refactored into a base class that does for lists what DictionaryTemplate does for dictionaries:

# assume class DictionaryTemplate exactly as before
from step9 import DictionaryTemplate

class ListTemplate:

    def __init__(self, input_list=[]):
        self.input_list = input_list

    def __str__(self):
        return "\n".join([self._template % x for x in self.input_list])


class LI_Template(ListTemplate):

    _template = """\t<li>%s</li>"""


class UL_Template(DictionaryTemplate):

    _template = """<ul>\n%(lst|li)s\n</ul>"""

    def li(self, input_list):
        return LI_Template(input_list)


print UL_Template({"lst": ["foo", "bar"]})

Step 11

We can make at least two more improvements to DictionaryTemplate. One is to allow keyword args to the constructor. The other is to change __process to support references to functions that are passed in (rather than being defined as methods):

class DictionaryTemplate:

    def __init__(self, dict={}, **keywords):
        self.dict = dict
        self.dict.update(keywords)

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

    def __getitem__(self, key):
            return self._process(key.split("|"))

    def _process(self, l):
        arg = l[0]
        if len(l) == 1:
            if arg in self.dict:
                return self.dict[arg]
            elif hasattr(self, arg) and callable(getattr(self, arg)):
                return getattr(self, arg)()
            else:
                raise KeyError(arg)
        else:
            func_name = l[1]
            if func_name in self.dict:
                func = self.dict[func_name]
            else:
                func = getattr(self, func_name)
            return func(self._process([arg]))


# assume ListTemplate as before
from step10 import ListTemplate


class LI_Template(ListTemplate):

    _template = """\t<li>%s</li>"""


class UL_Template(DictionaryTemplate):

    _template = """<ul>\n%(lst|li)s\n</ul>"""


print UL_Template(lst=["foo", "bar", "baz", "biz"], li=LI_Template)

Step 12

Here is an example which starts to show a slightly more involved template.

from step11 import DictionaryTemplate, ListTemplate


# a list with no wrapper elements

class ArticleList_Template(DictionaryTemplate):

    _template = """
      <div>
        <h1>Articles</h1>
        %(lst|li)s
      </div>"""


# a template for an article

class Article_Template(ListTemplate):

    _template = """
        <div>
          <h2>%(heading)s</h2>
          <p class="date">%(date)s</p>
          <p>%(abstract)s</p>
          <p><a href="%(link)s">Link</a></p>
        </div>"""


# the actual data
articles = [
    {"heading": "Article 1",
     "date": "2003-02-10",
     "abstract": "This is the first article.",
     "link": "http://example.com/article/1"},
    {"heading": "Article 2",
     "date": "2003-02-13",
     "abstract": "This is the second article.",
     "link": "http://example.com/article/2"}]


print ArticleList_Template(lst=articles, li=Article_Template)