The implementation principle of web.py template

The template implementation of web.py takes advantage of the dynamic nature of Python’s executable objects: a function is created based on the template contents and parameters of the render function, which, when executed, returns an instance of the TemplateResult class, whose stringing is the template’s corresponding HTML content.

Experimental environment construction

To illustrate how the template for web.py is implemented, we need to add some print statements to the template implementation code for web.py to display the intermediate results. Python’s VirtualEnv utility does a good job of fulfilling this need. I also used IPython, but the standard Python command line is fine. The steps of setting up the environment are described as follows:

  • Create a VirtualEnv environment: VirtualEnv env

  • Activate the VirtualEnv environment: CD env and source bin/activate

  • Web.py: PIP install web.py

The web.py library will be installed in the VirtualEnv environment directory:

(env) ➜ ~ / programming/python/env/lib/python2.7 / site - packages/web $PWD / home/diabloneo/programming/python/env/lib/python2.7 / site - packages/web

Now you can modify the web.py code to see how the template is implemented.

Experimental code modification

The code we want to modify is in the web/template.py file. Go to the compile_template function of the template class (on line 900 of the template.py file) and add a print line:

def compile_template(self, template_string, filename): code = Template.generate_code(template_string, filename, parser=self.create_parser()) def get_source_line(filename, lineno): try: lines = open(filename).read().splitlines() return lines[lineno] except: Return None print code # This line is the debug statement we added to print out the dynamically generated function mentioned earlier. try: # compile the code first to report the errors, if any, with the filename compiled_code = compile(code, filename, 'exec') except SyntaxError, e: ...

What do template functions really look like?

Now we can take a look at what the template function looks like. Of course, first you have to create a template file. The following operations were carried out in our experimental environment:

(env)➜ ~/programming/python/env  $ ls
bin  include  lib  local
(env)➜ ~/programming/python/env  $ mkdir app
(env)➜ ~/programming/python/env  $ ls
app bin  include  lib  local
(env)➜ ~/programming/python/env  $ cd app
(env)➜ ~/programming/python/env/app  $ mkdir templates

Now, create the simplest template in the templates directory called hello.html with the following contents:

hello, world

Let’s start IPython or Python in an experimental environment and go to the app directory:

(env) ➜ ~ / programming/python/env/app $ipython WARNING: Attempting to work in a virtualenv. If you encounter problems, Please install IPython inside the virtualenv.python 2.7.8 (Default, OCT 20 2014, 15:05:19) Type "copyright", "Credits" or "License" for more information. IPython 1.0.0 -- An enhanced Interactive Python.? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object?? ' for extra details. In [1]:

To see the contents of the template function, execute the following code:

In [3]: hello = web.template.frender("templates/hello.html")
# coding: utf-8
def __template__():
    __lineoffset__ = -5
    loop = ForLoop()
    self = TemplateResult(); extend_ = self.extend
    extend_([u'hello, world\n'])

    return self

In [4]:

The function __template__() above is printed by the line of print code we added to the web.py library. From the definition of this function, we can see that:

  1. Functions use some predefined objects: ForLoop, templateResult, and so on.

  2. The result of the function is to return an instance of TemplateResult.

So, here’s the question:

  1. How is this function generated?

  2. Where do these predefined names come from?

  3. How does the string representation of the TemplateResult instance become HTML text?

This article starts by explaining how the TemplateResult instance produces HTML text.

TemplateResult

The TemplateResult class is also defined in the web/template.py file. The inherited class and implementation methods are as follows:

class TemplateResult(object, DictMixin):
   __delattr__ : function
   __delitem__ : function
   __getattr__ : function
   __getitem__ : function
   __init__ : function
   __repr__ : function
   __setattr__ : function
   __setitem__ : function
   __str__ : function
   __unicode__ : function
   _prepare_body : function
   keys : function

Dictmixin is a class that does most of the dictionary operations, and a subclass that extends from that class (in this case, TemplateResult) needs to implement: __setitem__ __getitem__ (), (), __delitem__ () and keys () method, so that the object can simulate operation complete dictionary. To be sure: DictMixin class is obsolete and should use the collections. Now MutableMapping class (the class the implementation of the use of ABC library – an abstract class).

First look at the __init__() function,

def __init__(self, *a, **kw):
    self.__dict__["_d"] = dict(*a, **kw)
    self._d.setdefault("__body__", u'')

    self.__dict__['_parts'] = []
    self.__dict__["extend"] = self._parts.extend

As you can see from the initialization function, most of the TemplateResult attributes are stored in the _d dictionary, which contains at least one element, body. So, when external code adds, deletes, changes, and looks up properties of the TemplateResult object, it is actually operating on the internal _d dictionary. The other two properties defined in the initialization function are _parts, a sequence; Extend, which refers to the extend() method of the _parts sequence, meaning that calling the extend method of the TemplateResult instance is actually calling the extend method of the instance attribute _parts. This extend method we’ve already seen in the previous __template__() function:

def __template__():
    __lineoffset__ = -5
    loop = ForLoop()
    self = TemplateResult(); extend_ = self.extend
    extend_([u'hello, world\n'])

    return self
    

This function defines extend_ = self.extend, and extend_ adds the contents of the template to the self._parts sequence.

Let’s take a look at how the templateResult generates HTML content, using the __str__() method:

def _prepare_body(self):
    """Prepare value of __body__ by joining parts.
    """
    if self._parts:
        value = u"".join(self._parts)
        self._parts[:] = []
        body = self._d.get('__body__')
        if body:
            self._d['__body__'] = body + value
        else:
            self._d['__body__'] = value

def __str__(self):
    self._prepare_body()
    return self["__body__"].encode('utf-8')

The main operation is in the _prepare_body() function, where the main operation is to concatenate the string in _parts and then concatenate it after the body content.

After looking at the implementation of the templateResult, we can see that the __template__() function generated by the template ends up adding a bunch of strings to the templateResult instance, which then generates an HTML string from the instance.

conclusion

By printing the intermediate results and analyzing the code, we’ve got a rough idea of how the template for web.py is converted into HTML content. The next article will explain the dynamic function content of the web.py template’s various syntaxes.