Using a decorator for generic html templates

Carlos Ribeiro carribeiro at gmail.com
Wed Sep 15 18:11:56 EDT 2004


Hello all,

While studying some of the templating systems available for Python,
I've come up with an idea to implement templates using decorators. The
following code snippet runs in Python 2.3; a few 2.4 only extensions
are shown as comments. Please note that I have shamelessly copied the
trim function from PEP 257, because it's needed to have the strings
properly formatted.

============================
#
# HTML templating decorator
# Carlos Ribeiro
# carribeiro at gmail.com
# http://pythonnotes.blogspot.com
#

import sys

def trim(docstring):
    # shamelessly taken from PEP 257:
    # http://www.python.org/peps/pep-0257.html
    if not docstring:
        return ''
    # Convert tabs to spaces (following the normal Python rules)
    # and split into a list of lines:
    lines = docstring.expandtabs().splitlines()
    # Determine minimum indentation (first line doesn't count):
    indent = sys.maxint
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))
    # Remove indentation (first line is special):
    trimmed = [lines[0].strip()]
    if indent < sys.maxint:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())
    # Strip off trailing and leading blank lines:
    while trimmed and not trimmed[-1]:
        trimmed.pop()
    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    # Return a single string:
    return '\n'.join(trimmed)

def raw_html_template(func):
    """
    Transforms a simple raw html template function into a function that
    returns the __doc__ string. Non-significant spaces are removed,
    making the resulting output indent correctly.
    """
    def new_func(args):
        return func.__doc__
   
    #new_func.func_name = func.func_name   # 2.4 only
    return new_func

class Root:
    #@raw_html_template   # 2.4 only
    def index(self):
        """
        <html>
          <body>
            <h1>Hello World!</h1>
          </body>
        </html>
        """ 
        
    index = raw_html_template(index)   #2.3 only

A simple test run is:

>>> r = Root()
>>> r.index()
'\n        <html>\n          <body>\n            <h1>Hello
World!</h1>\n          </body>\n        </html>\n        '
>>> 

============================
Comments about this technique:

1) It abuses the doc string to obtain the desired effect. While some
may not like it as a hack, it's actually very clean; the resulting
code looks really nice without extra clutter.

2) More advanced decorators can be supported. For example, embedded
tags can be pre-processed by the decorator, either at "decorating
time" (when the decorator function is run) or at runtime (by means of
special code in the decorated function).

3) Methods that interleave Python code and raw HTML code are *much*
more difficult to handle. I don't have enough knowledge to deem them
impossible, but that's my best guess now. *If* someone is brave enough
to try it, my guess is that it has to rely very heavily on
introspection and low-level disassembly hacks. Not for the faint of
heart... and too [asm|C|INTERCAL]-ish for my taste :-)

4) Another approach that could be possibly taken is to use a metaclass
to control the decoration of all methods that have only docstrings and
no code. Using introspection, the constructor could check all methods
and decorate the ones that need it, building the new object
accordingly.

5) In real world applications, performance may be a concern. I don't
know if the decorator is applied every time a new object instance is
created, or if a caching mechanism of some type is used. If the
decorator is run everytime a new instance is about to be created, then
a different approach is needed, because many web application
frameworks rely on short lived objects to represent stuff such as
actual page handlers. That's where a metaclass approach could be more
useful.

6) Last, I don't know if the trim() function is available through some
of the standard libraries, and I have no idea upon where to look after
it. But it would be a good idea to call the standard implementation
instead of repeating the code here. (btw, the implementation has a
small bug: it leaves some extra space at the last line that should be
removed).


-- 
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: carribeiro at gmail.com
mail: carribeiro at yahoo.com



More information about the Python-list mailing list