Welcome, guest | Sign In | My Account | Store | Cart

Using string templates to separate views from models and controllers is fine, but passing data from controllers to views is often tiresome. Using frame inspection can make things a lot more straightforward, saving you the hassle of explicitely passing each and every bit of data the template needs through boring lines of code like {'name':name}. Here is a sample with a fake templating system.

Python, 54 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import inspect

test_template = '''Hello, %(first_name)s %(last_name)s'''

def call_template(template,context=None,**kw):
    """ Calls a template and returns the generated content.
        this is pretty much braindead, it's just meant to give you
        the general idea.
    """

    # efficiency is not the problem here
    if context is not None:
        d = dict(context)
        d.update(kw)
    else:
        d = kw
    
    return template%d

def call_contextual_template(template):
    # this is the magic line    
    frame = inspect.currentframe().f_back
    
    # again, we don't care about efficiency, it's not the point here
    d = dict(frame.f_globals)
    d.update(frame.f_locals)
    
    return call_template(template,d)

def test_1():
    first_name = "Foo" # imagine it is fetched from database
    last_name = "Bar"
    print call_template(test_template,{
        "first_name":first_name,
        "last_name":last_name,
    }) # this is ugly !

def test_2():
    first_name = "Foo" # imagine it is fetched from database
    last_name = "Bar"
    print call_template(test_template,
        first_name=first_name,
        last_name=last_name,
    ) # this is ugly !
    
def test_3():
    first_name = "Foo" # imagine it is fetched from database
    last_name = "Bar"
    print call_contextual_template(test_template) # this is much better
    
if __name__=='__main__':
    test_1()
    test_2()
    test_3()

Using frame inspection can save you a lot of boilerplate coding such as {"name":name} or name=name in the above code. This saves you from having to explicitely pass data from the controller to the view ; every local or global variable reachable from the controller code is available in the template.

Of course, this may pose security issues (the template could access unwanted data) or separation issues (it could be tempting for the template coder to call some code reachable from the controller). In my context, this is not really a problem because we have some discipline about this.

The concept of 'contextual template' (as opposed to traditional templates) helps remembering the 'magic bit' about the template, which extracts its parameters from the calling code.

4 comments

Tim Delaney 18 years, 6 months ago  # | flag

Just use locals().

def test_4():
    first_name = "Foo" # imagine it is fetched from database
    last_name = "Bar"
    print call_template(locals())
Tim Delaney 18 years, 6 months ago  # | flag

Of course, that should have been ...

def test_4():
    first_name = "Foo" # imagine it is fetched from database
    last_name = "Bar"
    print call_template(test_template, locals())
Nicolas Lehuen (author) 18 years, 6 months ago  # | flag

Right you are... ... but with locals() the caller has to do the trick. Frame inspection saves the caller from writing locals(). locals() may be more portable to other implementations than CPython, though.

Cliff Wells 18 years, 3 months ago  # | flag

What if there are additional layers between the controller and the view? This would be much more typical (and in fact, looking for a solution to this problem is what led me here). If there's more than one layer, you'll need to search up the frame stack to find the controller rather than just assuming it called the view directly.

Created by Nicolas Lehuen on Mon, 10 Oct 2005 (PSF)
Python recipes (4591)
Nicolas Lehuen's recipes (7)

Required Modules

Other Information and Tasks