Mike Orr with assistance from Tavis Rudd
iron@mso.oz.net
©Copyright 2002, Mike Orr. This document may be copied and modified under the terms of the Open Publication License http://www.opencontent.org/openpub/
The Cheetah Developers' Guide is for those who want to learn how Cheetah works internally, or wish to modify or extend Cheetah. It is assumed that you've read the Cheetah Users' Guide and have an intermediate knowledge of Python.
This Guide takes a behaviorist approach. First we'll look at what the Cheetah compiler generates when it compiles a template definition, and how it compiles the various $placeholder features and #directives. Then we'll stroll through the files in the Cheetah source distribution and show how each file contributes to the compilation and/or filling of templates. Then we'll list every method/attribute inherited by a template object. Finally, we'll describe how to submit bugfixes/enhancements to Cheetah, and how to add to the documentation.
Appendix A will contain a BNF syntax of the Cheetah template language.
This chapter examines the structure of a .py template module. The following few chapters will then show how each placeholder and directive affects the generated Python code.
Our first template follows a long noble tradition in computer tutorials. It produces a familiar, friendly greeting. Here's the template:
Hello, world!
... the output:
Hello, world!
... and the .py template module cheetah-compile produced, with line numbers added:
1 #!/usr/bin/env python
2 """
3 Autogenerated by CHEETAH: The Python-Powered Template Engine
4 CHEETAH VERSION: 0.9.12
5 Generation time: Sat Apr 20 14:27:47 2002
6 Source file: x.tmpl
7 Source file last modified: Wed Apr 17 22:10:59 2002
8 """
9 __CHEETAH_genTime__ = 'Sat Apr 20 14:27:47 2002'
10 __CHEETAH_src__ = 'x.tmpl'
11 __CHEETAH_version__ = '0.9.12'
12 ##################################################
13 ## DEPENDENCIES
14 import sys
15 import os
16 import os.path
17 from os.path import getmtime, exists
18 import time
19 import types
20 from Cheetah.Template import Template
21 from Cheetah.DummyTransaction import DummyTransaction
22 from Cheetah.NameMapper import NotFound, valueForName,
valueFromSearchList
23 import Cheetah.Filters as Filters
24 import Cheetah.ErrorCatchers as ErrorCatchers
25 ##################################################
26 ## MODULE CONSTANTS
27 try:
28 True, False
29 except NameError:
30 True, False = (1==1), (1==0)
31 ##################################################
32 ## CLASSES
33 class x(Template):
34 """
35
36 Autogenerated by CHEETAH: The Python-Powered Template Engine
37 """
38 ##################################################
39 ## GENERATED METHODS
40 def __init__(self, *args, **KWs):
41 """
42
43 """
44 Template.__init__(self, *args, **KWs)
45 self._filePath = 'x.tmpl'
46 self._fileMtime = 1019106659
47 def respond(self,
48 trans=None,
49 dummyTrans=False,
50 VFS=valueFromSearchList,
51 VFN=valueForName,
52 getmtime=getmtime,
53 currentTime=time.time):
54 """
55 This is the main method generated by Cheetah
56 """
57 if not trans:
58 trans = DummyTransaction()
59 dummyTrans = True
60 write = trans.response().write
61 SL = self._searchList
62 filter = self._currentFilter
63 globalSetVars = self._globalSetVars
64
65 ########################################
66 ## START - generated method body
67
68 if exists(self._filePath) and getmtime(self._filePath) > \
self._fileMtime:
69 self.compile(file=self._filePath)
70 write(getattr(self, self._mainCheetahMethod_for_x)
(trans=trans))
71 if dummyTrans:
72 return trans.response().getvalue()
73 else:
74 return ""
75 write('Hello, world!\n')
76
77 ########################################
78 ## END - generated method body
79
80 if dummyTrans:
81 return trans.response().getvalue()
82 else:
83 return ""
84
85 ##################################################
86 ## GENERATED ATTRIBUTES
87 __str__ = respond
88 _mainCheetahMethod_for_x= 'respond'
89 # CHEETAH was developed by Tavis Rudd, Chuck Esterbrook, Ian Bicking
# and Mike Orr;
90 # with code, advice and input from many other volunteers.
91 # For more information visit http://www.CheetahTemplate.org
92 ##################################################
93 ## if run from command line:
94 if __name__ == '__main__':
95 x().runAsMainProgram()
(I added the line numbers for this Guide, and split a few lines to fit the page width. The continuation lines don't have line numbers, and I added indentation, backslashes and '#' as necessary to make the result a valid Python program.)
The examples were generated from CVS versions of Cheetah between 0.9.12 and 0.9.14.
Lines 20-24 are the Cheetah-specific imports. Line 33 introduces our generated
class, x, a subclass of Template. It's called x because the
source file was x.tmpl.
Lines 40-46 are the .__init__ method called when the template is
instantiated or used as a Webware servlet, or when the module is run as a
standalone program. We can see it calling its superclass constructor and
setting ._filePath and ._fileMtime to the filename and
modification time (in Unix ticks) of the source .tmpl file.
Lines 47-84 are the main method .respond, the one that fills the
template. Normally you call it without arguments, but Webware calls it with a
Webware Transaction object representing the current request. Lines
57-59 set up the trans variable. If a real or dummy transaction is
passed in, the method uses it. Otherwise (if the trans argument is
None), the method creates a DummyTransaction instance.
dummyTrans is a flag that just tells whether a dummy transaction is in
effect; it'll be used at the end of the method.
The other four .respond arguments aren't anything you'd ever want to
pass in; they exist solely to speed up access to these frequently-used
global functions. This is a standard Python trick described in question 4.7
of the Python FAQ (http://www.python.org/cgi-bin/faqw.py).
VFS and VFN are the functions that give your template the
benefits of NameMapper lookup, such as the ability to use the searchList.
Line 60 initializes the write variable. This important variable is
discussed below.
Lines 60-63 initialize a few more local variables. SL is the
searchList. filter is the current output filter. globalSetVars
are the variables that have been defined with #set global.
The comments at lines 65 and 78 delimit the start and end of the code that
varies with each template. The code outside this region is identical in all
template modules. That's not quite true - #import for instance
generates additional import statements at the top of the module -
but it's true enough for the most part.
Lines 68-74 exist only if the template source was a named file rather than
a string or file object. The stanza recompiles the template if the source
file has changed. Lines 70-74 seem to be redundant with 75-83: both
fill the template and send the output. The reason the first set of lines
exists is because the second set may become invalid when the template is
recompiled. (This is for re compilation only. The initial compilation
happened in the .__init__ method if the template wasn't
precompiled.)
Line 75 is the most interesting line in this module. It's a direct
translation of what we put in the template definition, ``Hello, world!'' Here
the content is a single string literal. write looks like an ordinary
function call, but remember that line 60 made it an alias to
trans.response().write, a method in the transaction. The next few
chapters describe how the different placeholders and directives influence this
portion of the generated class.
Lines 80-83 finish the template filling. If trans is a real Webware
transaction, write has already sent the output to Webware for handling,
so we return "". If trans is a dummy transaction,
write has been accumulating the output in a Python StringIO
object rather than sending it anywhere, so we have to return it.
Line 83 is the end of the .respond method.
Line 87 makes code.__str__ an alias for the main method, so that you
can print it or apply str to it and it will fill the template.
Line 88 gives the name of the main method, because sometimes it's not
.respond.
Lines 94-95 allow the module to be run directly as a script. Essentially, they process the command-line arguments and them make the template fill itself.
Let's add a few $placeholders to our template:
>>> from Cheetah.Template import Template
>>> values = {'what': 'surreal', 'punctuation': '?'}
>>> t = Template("""\
... Hello, $what world$punctuation
... One of Python's least-used functions is $xrange.
... """, [values])
>>> print t
Hello, surreal world?
One of Python's least-used functions is <built-in function xrange>.
>>> print t.generatedModuleCode()
1 #!/usr/bin/env python
2 """
3 Autogenerated by CHEETAH: The Python-Powered Template Engine
4 CHEETAH VERSION: 0.9.12
5 Generation time: Sun Apr 21 00:53:01 2002
6 """
7 __CHEETAH_genTime__ = 'Sun Apr 21 00:53:01 2002'
8 __CHEETAH_version__ = '0.9.12'
9 ##################################################
10 ## DEPENDENCIES
11 import sys
12 import os
13 import os.path
14 from os.path import getmtime, exists
15 import time
16 import types
17 from Cheetah.Template import Template
18 from Cheetah.DummyTransaction import DummyTransaction
19 from Cheetah.NameMapper import NotFound, valueForName,
valueFromSearchList
20 import Cheetah.Filters as Filters
21 import Cheetah.ErrorCatchers as ErrorCatchers
22 ##################################################
23 ## MODULE CONSTANTS
24 try:
25 True, False
26 except NameError:
27 True, False = (1==1), (1==0)
28 ##################################################
29 ## CLASSES
30 class GenTemplate(Template):
31 """
32
33 Autogenerated by CHEETAH: The Python-Powered Template Engine
34 """
35 ##################################################
36 ## GENERATED METHODS
37 def __init__(self, *args, **KWs):
38 """
39
40 """
41 Template.__init__(self, *args, **KWs)
42 def respond(self,
43 trans=None,
44 dummyTrans=False,
45 VFS=valueFromSearchList,
46 VFN=valueForName,
47 getmtime=getmtime,
48 currentTime=time.time):
49 """
50 This is the main method generated by Cheetah
51 """
52 if not trans:
53 trans = DummyTransaction()
54 dummyTrans = True
55 write = trans.response().write
56 SL = self._searchList
57 filter = self._currentFilter
58 globalSetVars = self._globalSetVars
59
60 ########################################
61 ## START - generated method body
62
63 write('Hello, ')
64 write(filter(VFS(SL,"what",1))) # generated from '$what' at
# line 1, col 8.
65 write(' world')
66 write(filter(VFS(SL,"punctuation",1))) # generated from
# '$punctuation' at line 1, col 19.
67 write("\nOne of Python's least-used methods is ")
68 write(filter(xrange)) # generated from '$xrange' at line 2,
# col 39.
69 write('.\n')
70
71 ########################################
72 ## END - generated method body
73
74 if dummyTrans:
75 return trans.response().getvalue()
76 else:
77 return ""
78
79 ##################################################
80 ## GENERATED ATTRIBUTES
81 __str__ = respond
82 _mainCheetahMethod_for_GenTemplate= 'respond'
83 # CHEETAH was developed by Tavis Rudd, Chuck Esterbrook, Ian Bicking
# and Mike Orr;
84 # with code, advice and input from many other volunteers.
85 # For more information visit http://www.CheetahTemplate.org
86 ##################################################
87 ## if run from command line:
88 if __name__ == '__main__':
89 GenTemplate().runAsMainProgram()
(Again, I have added line numbers and split the lines as in the previous chapter.)
This generated template module is different from the previous one in several
trivial respects and one important respect. Trivially,
._filePath and ._fileMtime are not updated in
.__init__, so they inherit the value None from
Template. Also, that if-stanza in .respond that recompiles the
template if the source file changes is missing - because there is no source
file. So this module is several lines shorter than the other one.
But the important way this module is different is that instead of the one
write call outputting a string literal, this module has a series of
write calls (lines 63-69) outputting successive chunks of the
template. Regular text has been translated into a string literal, and
placeholders into function calls. Every placeholder is wrapped inside a
filter call to apply the current output filter. (The default
output filter converts all objects to strings, and None to "".)
Placeholders referring to a Python builtin like xrange (line 68)
generate a bare variable name. Placeholders to be looked up in the searchList
have a nested function call; e.g.,
write(filter(VFS(SL,"what",1))) # generated from '$what' at line 1, col 8.
VFS, remember, is a function imported from Cheetah.NameMapper
that looks up a value in a searchList. So we pass it the searchList, the
name to look up, and a boolean (1) indicating we want autocalling. (It's
1 rather than True because it's generated from an
and expression, and that's what Python 2.2 outputs for true and
expressions.)
Placeholders can get far more complicated than that. This example shows what
kind of code the various NameMapper features produce. The formulas are
taken from Cheetah's test suite, in the
Cheetah.Tests.SyntaxAndOutput.Placeholders class.
1 placeholder: $aStr
2 placeholders: $aStr $anInt
2 placeholders, back-to-back: $aStr$anInt
1 placeholder enclosed in {}: ${aStr}
1 escaped placeholder: \$var
func placeholder - with (): $aFunc()
func placeholder - with (int): $aFunc(1234)
func placeholder - with (string): $aFunc('aoeu')
func placeholder - with ('''\nstring'\n'''): $aFunc('''\naoeu'\n''')
func placeholder - with (string*int): $aFunc('aoeu'*2)
func placeholder - with (int*float): $aFunc(2*2.0)
Python builtin values: $None $True $False
func placeholder - with ($arg=float): $aFunc($arg=4.0)
deeply nested argstring: $aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ):
function with None: $aFunc(None)
autocalling: $aFunc! $aFunc().
nested autocalling: $aFunc($aFunc).
list subscription: $aList[0]
list slicing: $aList[:2]
list slicing and subcription combined: $aList[:2][0]
dict - NameMapper style: $aDict.one
dict - Python style: $aDict['one']
dict combined with autocalled string method: $aDict.one.upper
dict combined with string method: $aDict.one.upper()
nested dict - NameMapper style: $aDict.nestedDict.two
nested dict - Python style: $aDict['nestedDict']['two']
nested dict - alternating style: $aDict['nestedDict'].two
nested dict - NameMapper style + method: $aDict.nestedDict.two.upper
nested dict - alternating style + method: $aDict['nestedDict'].two.upper
nested dict - NameMapper style + method + slice: $aDict.nestedDict.two.upper[:4]
nested dict - Python style, variable key: $aDict[$anObj.meth('nestedDict')].two
object method: $anObj.meth1
object method + complex slice: $anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ]
very complex slice: $( anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ] )
We'll need a big program to set up the placeholder values. Here it is:
#!/usr/bin/env python
from ComplexExample import ComplexExample
try: # Python >= 2.2.1
True, False
except NameError: # Older Python
True, False = (1==1), (1==0)
class DummyClass:
_called = False
def __str__(self):
return 'object'
def meth(self, arg="arff"):
return str(arg)
def meth1(self, arg="doo"):
return arg
def meth2(self, arg1="a1", arg2="a2"):
return str(arg1) + str(arg2)
def callIt(self, arg=1234):
self._called = True
self._callArg = arg
def dummyFunc(arg="Scooby"):
return arg
defaultTestNameSpace = {
'aStr':'blarg',
'anInt':1,
'aFloat':1.5,
'aList': ['item0','item1','item2'],
'aDict': {'one':'item1',
'two':'item2',
'nestedDict':{1:'nestedItem1',
'two':'nestedItem2'
},
'nestedFunc':dummyFunc,
},
'aFunc': dummyFunc,
'anObj': DummyClass(),
'aMeth': DummyClass().meth1,
}
print ComplexExample( searchList=[defaultTestNameSpace] )
Here's the output:
1 placeholder: blarg
2 placeholders: blarg 1
2 placeholders, back-to-back: blarg1
1 placeholder enclosed in {}: blarg
1 escaped placeholder: $var
func placeholder - with (): Scooby
func placeholder - with (int): 1234
func placeholder - with (string): aoeu
func placeholder - with ('''\nstring'\n'''):
aoeu'
func placeholder - with (string*int): aoeuaoeu
func placeholder - with (int*float): 4.0
Python builtin values: 1 0
func placeholder - with ($arg=float): 4.0
deeply nested argstring: 1:
function with None:
autocalling: Scooby! Scooby.
nested autocalling: Scooby.
list subscription: item0
list slicing: ['item0', 'item1']
list slicing and subcription combined: item0
dict - NameMapper style: item1
dict - Python style: item1
dict combined with autocalled string method: ITEM1
dict combined with string method: ITEM1
nested dict - NameMapper style: nestedItem2
nested dict - Python style: nestedItem2
nested dict - alternating style: nestedItem2
nested dict - NameMapper style + method: NESTEDITEM2
nested dict - alternating style + method: NESTEDITEM2
nested dict - NameMapper style + method + slice: NEST
nested dict - Python style, variable key: nestedItem2
object method: doo
object method + complex slice: do
very complex slice: do
And here - tada! - is the generated module.
To save space, I've included only the lines containing the write calls.
The rest of the module is the same as in the first example, chapter
2.1. I've split some of the lines to make them fit on
the page.
1 write('1 placeholder: ')
2 write(filter(VFS(SL,"aStr",1))) # generated from '$aStr' at line 1, col 16.
3 write('\n2 placeholders: ')
4 write(filter(VFS(SL,"aStr",1))) # generated from '$aStr' at line 2, col 17.
5 write(' ')
6 write(filter(VFS(SL,"anInt",1)))
# generated from '$anInt' at line 2, col 23.
7 write('\n2 placeholders, back-to-back: ')
8 write(filter(VFS(SL,"aStr",1))) # generated from '$aStr' at line 3, col 31.
9 write(filter(VFS(SL,"anInt",1)))
# generated from '$anInt' at line 3, col 36.
10 write('\n1 placeholder enclosed in {}: ')
11 write(filter(VFS(SL,"aStr",1))) # generated from '${aStr}' at line 4,
# col 31.
12 write('\n1 escaped placeholder: $var\nfunc placeholder - with (): ')
13 write(filter(VFS(SL,"aFunc",0)())) # generated from '$aFunc()' at line 6,
# col 29.
14 write('\nfunc placeholder - with (int): ')
15 write(filter(VFS(SL,"aFunc",0)(1234))) # generated from '$aFunc(1234)' at
# line 7, col 32.
16 write('\nfunc placeholder - with (string): ')
17 write(filter(VFS(SL,"aFunc",0)('aoeu'))) # generated from "$aFunc('aoeu')"
# at line 8, col 35.
18 write("\nfunc placeholder - with ('''\\nstring'\\n'''): ")
19 write(filter(VFS(SL,"aFunc",0)('''\naoeu'\n'''))) # generated from
# "$aFunc('''\\naoeu'\\n''')" at line 9, col 46.
20 write('\nfunc placeholder - with (string*int): ')
21 write(filter(VFS(SL,"aFunc",0)('aoeu'*2))) # generated from
# "$aFunc('aoeu'*2)" at line 10, col 39.
22 write('\nfunc placeholder - with (int*float): ')
23 write(filter(VFS(SL,"aFunc",0)(2*2.0))) # generated from '$aFunc(2*2.0)'
# at line 11, col 38.
24 write('\nPython builtin values: ')
25 write(filter(None)) # generated from '$None' at line 12, col 24.
26 write(' ')
27 write(filter(True)) # generated from '$True' at line 12, col 30.
28 write(' ')
29 write(filter(False)) # generated from '$False' at line 12, col 36.
30 write('\nfunc placeholder - with ($arg=float): ')
31 write(filter(VFS(SL,"aFunc",0)(arg=4.0))) # generated from
# '$aFunc($arg=4.0)' at line 13, col 40.
32 write('\ndeeply nested argstring: ')
33 write(filter(VFS(SL,"aFunc",0)(
arg = VFS(SL,"aMeth",0)( arg = VFS(SL,"aFunc",0)( 1 ) ) )))
# generated from '$aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) )'
# at line 14, col 26.
34 write(':\nfunction with None: ')
35 write(filter(VFS(SL,"aFunc",0)(None))) # generated from '$aFunc(None)' at
# line 15, col 21.
36 write('\nautocalling: ')
37 write(filter(VFS(SL,"aFunc",1))) # generated from '$aFunc' at line 16,
# col 14.
38 write('! ')
39 write(filter(VFS(SL,"aFunc",0)())) # generated from '$aFunc()' at line 16,
# col 22.
40 write('.\nnested autocalling: ')
41 write(filter(VFS(SL,"aFunc",0)(VFS(SL,"aFunc",1)))) # generated from
# '$aFunc($aFunc)' at line 17, col 21.
42 write('.\nlist subscription: ')
43 write(filter(VFS(SL,"aList",1)[0])) # generated from '$aList[0]' at line
# 18, col 20.
44 write('\nlist slicing: ')
45 write(filter(VFS(SL,"aList",1)[:2])) # generated from '$aList[:2]' at
# line 19, col 15.
46 write('\nlist slicing and subcription combined: ')
47 write(filter(VFS(SL,"aList",1)[:2][0])) # generated from '$aList[:2][0]'
# at line 20, col 40.
48 write('\ndict - NameMapper style: ')
49 write(filter(VFS(SL,"aDict.one",1))) # generated from '$aDict.one' at line
# 21, col 26.
50 write('\ndict - Python style: ')
51 write(filter(VFS(SL,"aDict",1)['one'])) # generated from "$aDict['one']"
# at line 22, col 22.
52 write('\ndict combined with autocalled string method: ')
53 write(filter(VFS(SL,"aDict.one.upper",1))) # generated from
# '$aDict.one.upper' at line 23, col 46.
54 write('\ndict combined with string method: ')
55 write(filter(VFN(VFS(SL,"aDict.one",1),"upper",0)())) # generated from
# '$aDict.one.upper()' at line 24, col 35.
56 write('\nnested dict - NameMapper style: ')
57 write(filter(VFS(SL,"aDict.nestedDict.two",1))) # generated from
# '$aDict.nestedDict.two' at line 25, col 33.
58 write('\nnested dict - Python style: ')
59 write(filter(VFS(SL,"aDict",1)['nestedDict']['two'])) # generated from
# "$aDict['nestedDict']['two']" at line 26, col 29.
60 write('\nnested dict - alternating style: ')
61 write(filter(VFN(VFS(SL,"aDict",1)['nestedDict'],"two",1))) # generated
# from "$aDict['nestedDict'].two" at line 27, col 34.
62 write('\nnested dict - NameMapper style + method: ')
63 write(filter(VFS(SL,"aDict.nestedDict.two.upper",1))) # generated from
# '$aDict.nestedDict.two.upper' at line 28, col 42.
64 write('\nnested dict - alternating style + method: ')
65 write(filter(VFN(VFS(SL,"aDict",1)['nestedDict'],"two.upper",1)))
# generated from "$aDict['nestedDict'].two.upper" at line 29, col 43.
66 write('\nnested dict - NameMapper style + method + slice: ')
67 write(filter(VFN(VFS(SL,"aDict.nestedDict.two",1),"upper",1)[:4]))
# generated from '$aDict.nestedDict.two.upper[:4]' at line 30, col 50.
68 write('\nnested dict - Python style, variable key: ')
69 write(filter(VFN(VFS(SL,"aDict",1)
[VFN(VFS(SL,"anObj",1),"meth",0)('nestedDict')],"two",1)))
# generated from "$aDict[$anObj.meth('nestedDict')].two" at line 31,
# col 43.
70 write('\nobject method: ')
71 write(filter(VFS(SL,"anObj.meth1",1))) # generated from '$anObj.meth1' at
# line 32, col 16.
72 write('\nobject method + complex slice: ')
73 write(filter(VFN(VFS(SL,"anObj",1),"meth1",1)
[0: ((4/4*2)*2)/VFN(VFS(SL,"anObj",1),"meth1",0)(2) ]))
# generated from '$anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ]'
# at line 33, col 32.
74 write('\nvery complex slice: ')
75 write(filter(VFN(VFS(SL,"anObj",1),"meth1",1)
[0: ((4/4*2)*2)/VFN(VFS(SL,"anObj",1),"meth1",0)(2) ] ))
# generated from '$( anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ] )'
# at line 34, col 21.
76 write('\n')
For each placeholder lookup, the the innermost level of nesting is a VFS
call, which looks up the first (leftmost) placeholder component in the
searchList. This is wrapped by zero or more VFN calls, which perform
Universal Dotted Notation lookup on the next dotted component of the
placeholder, looking for an attribute or key by that name within the previous
object (not in the searchList). Autocalling is performed by VFS and
VFN: that's the reason for their third argument.
Explicit function/method arguments, subscripts and keys (which are all expressions) are left unchanged, besides expanding any embedded $placeholders in them. This means they must result in valid Python expressions, following the standard Python quoting rules.
Built-in Python values (None, True and False) are
converted to filter(None), etc. They use normal Python variable
lookup rather than VFS. (Cheetah emulates True and False
using global variables for Python < 2.2.1, when they weren't builtins yet.)
The template:
Dynamic variable: $voom
The command line and the output:
% voom='Voom!' python x.py --env Dynamic variable: Voom!
The generated code:
write('Dynamic variable: ')
write(filter(VFS(SL,"voom",1))) # generated from '$voom' at line 1, col 20.
write('\n')
Just what we expected, like any other dynamic placeholder.
The template:
Cached variable: $*voom
The command line and output:
% voom='Voom!' python x.py --env Cached variable: Voom!
The generated code, with line numbers:
1 write('Cached variable: ')
2 ## START CACHE REGION: at line, col (1, 19) in the source.
3 RECACHE = True
4 if not self._cacheData.has_key('19760169'):
5 pass
6 else:
7 RECACHE = False
8 if RECACHE:
9 orig_trans = trans
10 trans = cacheCollector = DummyTransaction()
11 write = cacheCollector.response().write
12 write(filter(VFS(SL,"voom",1))) # generated from '$*voom' at line 1,
# col 19.
13 trans = orig_trans
14 write = trans.response().write
15 self._cacheData['19760169'] = cacheCollector.response().getvalue()
16 del cacheCollector
17 write(self._cacheData['19760169'])
18 ## END CACHE REGION
19 write('\n')
That one little star generated a whole lotta code. First, instead of an
ordinary VFS lookup (searchList) lookup, it converted the
placeholder to a lookup in the ._cacheData dictionary. Cheetah also
generated a unique key ('19760169') for our cached item - this is its
cache ID.
Second, Cheetah put a pair of if-blocks before the write. The first
(lines 3-7) determine whether the cache value is missing or out of date, and
sets local variable RECHARGE true or false.
This stanza may look unnecessarily verbose - lines 3-7 could be eliminated if
line 8 was changed to
if not self._cacheData.has_key('19760169'):
The second if-block, lines 8-16, do the cache updating if necessary.
Clearly, the programmer is trying to stick as close to normal (dynamic)
workflow as possible. Remember that write, even though it looks like a
local function, is actually a method of a file-like object. So we create a
temporary file-like object to divert the write object into, then read
the result and stuff it into the cache.
The template:
Timed cache: $*.5m*voom
The command line and the output:
% voom='Voom!' python x.py --env Timed cache: Voom!
The generated method's docstring:
""" This is the main method generated by Cheetah This cache will be refreshed every 30.0 seconds. """
The generated code:
1 write('Timed cache: ')
2 ## START CACHE REGION: at line, col (1, 15) in the source.
3 RECACHE = True
4 if not self._cacheData.has_key('55048032'):
5 self.__cache55048032__refreshTime = currentTime() + 30.0
6 elif currentTime() > self.__cache55048032__refreshTime:
7 self.__cache55048032__refreshTime = currentTime() + 30.0
8 else:
9 RECACHE = False
10 if RECACHE:
11 orig_trans = trans
12 trans = cacheCollector = DummyTransaction()
13 write = cacheCollector.response().write
14 write(filter(VFS(SL,"voom",1))) # generated from '$*.5m*voom' at
# line 1, col 15.
15 trans = orig_trans
16 write = trans.response().write
17 self._cacheData['55048032'] = cacheCollector.response().getvalue()
18 del cacheCollector
19 write(self._cacheData['55048032'])
20 ## END CACHE REGION
21 write('\n')
This code is identical to the static cache example except for the docstring and the first if-block. (OK, so the cache ID is different and the comment on line 14 is different too. Big deal.)
Each timed-refresh cache item has a corrsponding private attribute
.__cache########__refreshTime giving the refresh time
in ticks (=seconds since January 1, 1970). The first if-block (lines 3-9)
checks whether the cache value is missing or its update time has passed, and if
so, sets RECHARGE to true and also schedules another refresh at the next
interval.
The method docstring reminds the user how often the cache will be refreshed. This information is unfortunately not as robust as it could be. Each timed-cache placeholder blindly generates a line in the docstring. If all refreshes are at the same interval, there will be multiple identical lines in the docstring. If the refreshes are at different intervals, you get a situation like this:
""" This is the main method generated by Cheetah This cache will be refreshed every 30.0 seconds. This cache will be refreshed every 60.0 seconds. This cache will be refreshed every 120.0 seconds. """
This example is the same but with the long placeholder syntax. It's here because it's a Cheetah FAQ whether to put the cache interval inside or outside the braces. (It's also here so I can look it up because I frequently forget.) The answer is: outside. The braces go around only the placeholder name (and perhaps some output-filter arguments.)
The template:
Timed with {}: $*.5m*{voom}
The output:
Timed with {}: Voom!
The generated code differs only in the comment. Inside the cache-refresh if-block:
write(filter(VFS(SL,"voom",1))) # generated from '$*.5m*{voom}' at line 1,
#col 17.
The reason this example is here is because it's a Cheetah FAQ whether to
put the cache interval inside or outside the {}. (Also so I can look
it up when I forget, as I frequently do.) The answer is: outside. The
{} go around only the placeholder name and arguments. If you do it
this way:
Timed with {}: ${*.5m*voom} ## Wrong!
Timed with {}: ${*.5m*voom}
${ is not a valid placeholder, so it's treated as ordinary text.
The template:
#cache This is a cached region. $voom #end cache
The output:
This is a cached region. Voom!
The generated code:
1 ## START CACHE REGION: at line, col (1, 1) in the source.
2 RECACHE = True
3 if not self._cacheData.has_key('23711421'):
4 pass
5 else:
6 RECACHE = False
7 if RECACHE:
8 orig_trans = trans
9 trans = cacheCollector = DummyTransaction()
10 write = cacheCollector.response().write
11 write('This is a cached region. ')
12 write(filter(VFS(SL,"voom",1))) # generated from '$voom' at line 2,
# col 27.
13 write('\n')
14 trans = orig_trans
15 write = trans.response().write
16 self._cacheData['23711421'] = cacheCollector.response().getvalue()
17 del cacheCollector
18 write(self._cacheData['23711421'])
19 ## END CACHE REGION
This is the same as the $*voom example, except that the plain text
around the placeholder is inside the second if-block.
The template:
#cache timer='.5m', id='cache1' This is a cached region. $voom #end cache
The output:
This is a cached region. Voom!
The generated code is the same as the previous example except the first if-block:
RECACHE = True
if not self._cacheData.has_key('13925129'):
self._cacheIndex['cache1'] = '13925129'
self.__cache13925129__refreshTime = currentTime() + 30.0
elif currentTime() > self.__cache13925129__refreshTime:
self.__cache13925129__refreshTime = currentTime() + 30.0
else:
RECACHE = False
The template:
#cache test=$isDBUpdated This is a cached region. $voom #end cache
(Analysis postponed: bug in Cheetah produces invalid Python.)
The template:
#cache id='cache1', test=($isDBUpdated or $someOtherCondition) This is a cached region. $voom #end cache
The output:
This is a cached region. Voom!
The first if-block in the generated code:
RECACHE = True
if not self._cacheData.has_key('36798144'):
self._cacheIndex['cache1'] = '36798144'
elif (VFS(SL,"isDBUpdated",1) or VFS(SL,"someOtherCondition",1)):
RECACHE = True
else:
RECACHE = False
() around the test expression, the result is the same, although
it may be harder for the template maintainer to read.
You can even combine arguments, although this is of questionable value.
The template:
#cache id='cache1', timer='30m', test=$isDBUpdated or $someOtherCondition This is a cached region. $voom #end cache
The output:
This is a cached region. Voom!
The first if-block:
RECACHE = True
if not self._cacheData.has_key('88939345'):
self._cacheIndex['cache1'] = '88939345'
self.__cache88939345__refreshTime = currentTime() + 1800.0
elif currentTime() > self.__cache88939345__refreshTime:
self.__cache88939345__refreshTime = currentTime() + 1800.0
elif VFS(SL,"isDBUpdated",1) or VFS(SL,"someOtherCondition",1):
RECACHE = True
else:
RECACHE = False
We are planning to add a 'varyBy' keyword argument in the future that
will allow a separate cache instances to be created for a variety of conditions,
such as different query string parameters or browser types. This is inspired by
ASP.net's varyByParam and varyByBrowser output caching keywords. Since this is
not implemented yet, I cannot provide examples here.
The template:
Text before the comment. ## The comment. Text after the comment. #* A multi-line comment spanning several lines. It spans several lines, too. *# Text after the multi-line comment.
The output:
Text before the comment. Text after the comment. Text after the multi-line comment.
The generated code:
write('Text before the comment.\n')
# The comment.
write('Text after the comment.\n')
# A multi-line comment spanning several lines.
# It spans several lines, too.
write('\nText after the multi-line comment.\n')
The template:
##doc: .respond() method comment. ##doc-method: Another .respond() method comment. ##doc-class: A class comment. ##doc-module: A module comment. ##header: A header comment.
The output:
The beginning of the generated .respond method:
def respond(self,
trans=None,
dummyTrans=False,
VFS=valueFromSearchList,
VFN=valueForName,
getmtime=getmtime,
currentTime=time.time):
"""
This is the main method generated by Cheetah
.respond() method comment.
Another .respond() method comment.
"""
The class docstring:
""" A class comment. Autogenerated by CHEETAH: The Python-Powered Template Engine """
The top of the module:
#!/usr/bin/env python # A header comment. """A module comment. Autogenerated by CHEETAH: The Python-Powered Template Engine CHEETAH VERSION: 0.9.13a1 Generation time: Fri Apr 26 22:39:23 2002 Source file: x.tmpl Source file last modified: Fri Apr 26 22:36:23 2002 """
The template:
Here is my #echo ', '.join(['silly']*5) # example
The output:
Here is my silly, silly, silly, silly, silly example
The generated code:
write('Here is my ')
write(filter(', '.join(['silly']*5) ))
write(' example\n')
The template:
Here is my #silent ', '.join(['silly']*5) # example
The output:
Here is my example
The generated code:
write('Here is my ')
', '.join(['silly']*5)
write(' example\n')
OK, it's not quite covert because that extra space gives it away, but it almost succeeds.
The template:
Text before raw. #raw Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c. #end raw Text after raw.
The output:
Text before raw. Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c. Text after raw.
The generated code:
write('''Text before raw.
Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c.
Text after raw.
''')
So we see that #raw is really like a quoting mechanism. It says that
anything inside it is ordinary text, and Cheetah joins a #raw section
with adjacent string literals rather than generating a separate write
call.
The main template:
#include "y.tmpl"
The included template y.tmpl:
Let's go $voom!
The shell command and output:
% voom="VOOM" x.py --env Let's go VOOM!
The generated code:
write(self._includeCheetahSource("y.tmpl", trans=trans, includeFrom="file",
raw=0))
The main template:
#include raw "y.tmpl"
The shell command and output:
% voom="VOOM" x.py --env Let's go $voom!
The generated code:
write(self._includeCheetahSource("y.tmpl", trans=trans, includeFrom="fil
e", raw=1))
That last argument, raw, makes the difference.
The template:
#attr $y = "Let's go $voom!" #include source=$y #include raw source=$y #include source="Bam! Bam!"
The output:
% voom="VOOM" x.py --env Let's go VOOM!Let's go $voom!Bam! Bam!
The generated code:
write(self._includeCheetahSource(VFS(SL,"y",1), trans=trans,
includeFrom="str", raw=0, includeID="481020889808.74"))
write(self._includeCheetahSource(VFS(SL,"y",1), trans=trans,
includeFrom="str", raw=1, includeID="711020889808.75"))
write(self._includeCheetahSource("Bam! Bam!", trans=trans,
includeFrom="str", raw=0, includeID="1001020889808.75"))
Later in the generated class:
y = "Let's go $voom!"
The template:
#for $i in range(5) $i #end for #for $i in range(5) $i #slurp #end for Line after slurp.
The output:
0 1 2 3 4 0 1 2 3 4 Line after slurp.
The generated code:
for i in range(5):
write(filter(i)) # generated from '$i' at line 2, col 1.
write('\n')
for i in range(5):
write(filter(i)) # generated from '$i' at line 5, col 1.
write(' ')
write('Line after slurp.\n')
The space after each number is because of the space before #slurp in
the template definition.
The template:
#attr $ode = ">> Rubber Ducky, you're the one! You make bathtime so much fun! <<"
$ode
#filter WebSafe
$ode
#filter MaxLen
${ode, maxlen=13}
#filter None
${ode, maxlen=13}
The output:
>> Rubber Ducky, you're the one! You make bathtime so much fun! << >> Rubber Ducky, you're the one! You make bathtime so much fun! << >> Rubber Duc >> Rubber Ducky, you're the one! You make bathtime so much fun! <<
The WebSafe filter escapes characters that have a special meaning in
HTML. The MaxLen filter chops off values at the specified length.
#filter None returns to the default filter, which ignores the maxlen
argument.
The generated code:
1 write(filter(VFS(SL,"ode",1))) # generated from '$ode' at line 2, col 1.
2 write('\n')
3 filterName = 'WebSafe'
4 if self._filters.has_key("WebSafe"):
5 filter = self._currentFilter = self._filters[filterName]
6 else:
7 filter = self._currentFilter = \
8 self._filters[filterName] = getattr(self._filtersLib,
filterName)(self).filter
9 write(filter(VFS(SL,"ode",1))) # generated from '$ode' at line 4, col 1.
10 write('\n')
11 filterName = 'MaxLen'
12 if self._filters.has_key("MaxLen"):
13 filter = self._currentFilter = self._filters[filterName]
14 else:
15 filter = self._currentFilter = \
16 self._filters[filterName] = getattr(self._filtersLib,
filterName)(self).filter
17 write(filter(VFS(SL,"ode",1), maxlen=13)) # generated from
#'${ode, maxlen=13}' at line 6, col 1.
18 write('\n')
19 filter = self._initialFilter
20 write(filter(VFS(SL,"ode",1), maxlen=13)) # generated from
#'${ode, maxlen=13}' at line 8, col 1.
21 write('\n')
As we've seen many times, Cheetah wraps all placeholder lookups in a
filter call. (This also applies to non-searchList lookups: local,
global and builtin variables.) The filter ``function''
is actually an alias to the current filter object:
filter = self._currentFilter
filter is not an alias to
the filter object itself but to that object's .filter method. Line 19
switches back to the default filter.
In line 17 we see the maxlen argument being passed as a keyword
argument to filter (not to VFS). In line 20 the same thing
happens although the default filter ignores the argument.
The template:
#import math
This construct does not produce any output.
The generated module, at the bottom of the import section:
import math
The template:
#extends SomeClass
The generated import (skipped if SomeClass has already been
imported):
from SomeClass import SomeClass
The generated class:
class x(SomeClass):
The template:
#implements doOutput
In the generated class, the main method is .doOutput instead of
.respond, and the attribute naming this method is:
_mainCheetahMethod_for_x2= 'doOutput'
The template:
#set $namesList = ['Moe','Larry','Curly'] $namesList #set global $toes = ['eeny', 'meeny', 'miney', 'moe'] $toes
The output:
['Moe', 'Larry', 'Curly'] ['eeny', 'meeny', 'miney', 'moe']
The generated code:
1 namesList = ['Moe','Larry','Curly']
2 write(filter(namesList)) # generated from '$namesList' at line 2, col 1.
3 write('\n')
4 globalSetVars["toes"] = ['eeny', 'meeny', 'miney', 'moe']
5 write(filter(VFS(SL,"toes",1))) # generated from '$toes' at line 4, col 1.
6 write('\n')
globalSetVars is a local variable shadowing ._globalSetVars.
Writes go into it directly, but reads take advantage of the fact that
._globalSetVars is on the searchList. (In fact, it's the very first
namespace.)
The template:
#set $a = 1 #del $a #set $a = 2 #set $arr = [0, 1, 2] #del $a, $arr[1]
In the generated class:
1 a = 1 2 del a 3 a = 2 4 arr = [0, 1, 2] 5 del a, arr[1]
The template:
#attr $namesList = ['Moe', 'Larry', 'Curly']
In the generated class:
## GENERATED ATTRIBUTES namesList = ['Moe', 'Larry', 'Curly']
The template:
#def printArg($arg) The argument is $arg. #end def My method returned $printArg(5).
The output:
My method returned The argument is 5. .
Hmm, not exactly what we expected. The method returns a trailing newline
because we didn't end the last line with #slurp. So the second
period (outside the method) appears on a separate line.
The #def generates a method .printArg whose structure is similar
to the main method:
def printArg(self,
arg,
trans=None,
dummyTrans=False,
VFS=valueFromSearchList,
VFN=valueForName,
getmtime=getmtime,
currentTime=time.time):
"""
Generated from #def printArg($arg) at line 1, col 1.
"""
if not trans:
trans = DummyTransaction()
dummyTrans = True
write = trans.response().write
SL = self._searchList
filter = self._currentFilter
globalSetVars = self._globalSetVars
########################################
## START - generated method body
write('The argument is ')
write(filter(arg)) # generated from '$arg' at line 2, col 17.
write('.\n')
########################################
## END - generated method body
if dummyTrans:
return trans.response().getvalue()
else:
return ""
When .printArg is called from a placeholder, only the arguments the user
supplied are passed. The other arguments retain their default values.
The template:
#block content This page is under construction. #end block
The output:
This page is under construction.
This construct generates a method .content in the same structure
as .printArg above, containing the write code:
write('This page is under construction.\n')
In the main method, the write code is:
self.content(trans=trans) # generated from ('content', '#block content')
# at line 1, col 1.
So a block placeholder implicitly passes the current transaction to the method.
This directive is undocumented because it's likely to disappear in Cheetah 0.9.14.
The template:
#for $i in $range(10) $i #slurp #end for
The output:
0 1 2 3 4 5 6 7 8 9
The generated code:
for i in range(10):
write(filter(i)) # generated from '$i' at line 2, col 1.
write(' ')
The template:
#repeat 3 My bonnie lies over the ocean #end repeat O, bring back my bonnie to me!
The output:
My bonnie lies over the ocean My bonnie lies over the ocean My bonnie lies over the ocean O, bring back my bonnie to me!
The generated code:
for i in range( 3):
write('My bonnie lies over the ocean\n')
write('O, bring back my bonnie to me!\n')
The template:
#set $alive = True #while $alive I am alive! #set $alive = False #end while
The output:
I am alive!
The generated code:
alive = True
while alive:
write('I am alive!\n')
alive = False
The template:
#set $size = 500 #if $size >= 1500 It's big #else if $size < 1500 and $size > 0 It's small #else It's not there #end if
The output:
It's small
The generated code:
size = 500
if size >= 1500:
write("It's big\n")
elif size < 1500 and size > 0:
write("It's small\n")
else:
write("It's not there\n")
The template:
#set $count = 9 #unless $count + 5 > 15 Count is in range. #end unless
The output:
Count is in range.
The generated code:
count = 9
if not (count + 5 > 15):
write('Count is in range.\n')
Note: There is a bug in Cheetah 0.9.13. It's forgetting the
parentheses in the if expression, which could lead to it calculating
something different than it should.
The template:
#for $i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'James', 'Joe', 'Snow'] #if $i == 10 #continue #end if #if $i == 'Joe' #break #end if $i - #slurp #end for
The output:
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 11 - 12 - James -
The generated code:
for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'James', 'Joe', 'Snow']:
if i == 10:
write('')
continue
if i == 'Joe':
write('')
break
write(filter(i)) # generated from '$i' at line 8, col 1.
write(' - ')
The template:
Let's check the number. #set $size = 500 #if $size >= 1500 It's big #elif $size > 0 #pass #else Invalid entry #end if Done checking the number.
The output:
Let's check the number. Done checking the number.
The generated code:
write("Let's check the number.\n")
size = 500
if size >= 1500:
write("It's big\n")
elif size > 0:
pass
else:
write('Invalid entry\n')
write('Done checking the number.\n')
The template:
A cat #if 1 sat on a mat #stop watching a rat #end if in a flat.
The output:
A cat sat on a mat
The generated code:
write('A cat\n')
if 1:
write(' sat on a mat\n')
if dummyTrans:
return trans.response().getvalue()
else:
return ""
write(' watching a rat\n')
write('in a flat.\n')
The template:
1 $test[1] 3 #def test 1.5 #if 1 #return '123' #else 99999 #end if #end def
The output:
1 2 3
The generated code:
def test(self,
trans=None,
dummyTrans=False,
VFS=valueFromSearchList,
VFN=valueForName,
getmtime=getmtime,
currentTime=time.time):
"""
Generated from #def test at line 5, col 1.
"""
if not trans:
trans = DummyTransaction()
dummyTrans = True
write = trans.response().write
SL = self._searchList
filter = self._currentFilter
globalSetVars = self._globalSetVars
########################################
## START - generated method body
write('1.5\n')
if 1:
return '123'
else:
write('99999\n')
########################################
## END - generated method body
if dummyTrans:
return trans.response().getvalue()
else:
return ""
def respond(self,
trans=None,
dummyTrans=False,
VFS=valueFromSearchList,
VFN=valueForName,
getmtime=getmtime,
currentTime=time.time):
"""
This is the main method generated by Cheetah
"""
if not trans:
trans = DummyTransaction()
dummyTrans = True
write = trans.response().write
SL = self._searchList
filter = self._currentFilter
globalSetVars = self._globalSetVars
########################################
## START - generated method body
write('\n1\n')
write(filter(VFS(SL,"test",1)[1])) # generated from '$test[1]' at line 3, col 1.
write('\n3\n')
########################################
## END - generated method body
if dummyTrans:
return trans.response().getvalue()
else:
return ""
The template:
#import traceback
#try
#raise RuntimeError
#except RuntimeError
A runtime error occurred.
#end try
#try
#raise RuntimeError("Hahaha!")
#except RuntimeError
#echo $sys.exc_info()[1]
#end try
#try
#echo 1/0
#except ZeroDivisionError
You can't divide by zero, idiot!
#end try
The output:
A runtime error occurred. Hahaha! You can't divide by zero, idiot!
The generated code:
try:
raise RuntimeError
except RuntimeError:
write('A runtime error occurred.\n')
write('\n')
try:
raise RuntimeError("Hahaha!")
except RuntimeError:
write(filter(VFN(sys,"exc_info",0)()[1]))
write('\n')
write('\n')
try:
write(filter(1/0))
write('\n')
except ZeroDivisionError:
write("You can't divide by zero, idiot!\n")
#finally works just like in Python.
The template:
#assert False, "You lose, buster!"
The output:
Traceback (most recent call last):
File "x.py", line 117, in ?
x().runAsMainProgram()
File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
Template.py", line 331, in runAsMainProgram
CmdLineIface(templateObj=self).run()
File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
TemplateCmdLineIface.py", line 59, in run
print self._template
File "x.py", line 91, in respond
assert False, "You lose, buster!"
AssertionError: You lose, buster!
The generated code:
assert False, "You lose, buster!"
The template:
$noValue
The output:
Traceback (most recent call last):
File "x.py", line 118, in ?
x().runAsMainProgram()
File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
Template.py", line 331, in runAsMainProgram
CmdLineIface(templateObj=self).run()
File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
TemplateCmdLineIface.py", line 59, in run
print self._template
File "x.py", line 91, in respond
write(filter(VFS(SL,"noValue",1))) # generated from '$noValue' at line
1, col 1.
NameMapper.NotFound: noValue
The generated code:
write(filter(VFS(SL,"noValue",1))) # generated from '$noValue' at line 1,
# col 1.
write('\n')
The template:
#errorCatcher Echo $noValue #errorCatcher BigEcho $noValue
The output:
$noValue ===============<$noValue could not be found>===============
The generated code:
if self._errorCatchers.has_key("Echo"):
self._errorCatcher = self._errorCatchers["Echo"]
else:
self._errorCatcher = self._errorCatchers["Echo"] = ErrorCatchers.Echo(self)
write(filter(self.__errorCatcher1(localsDict=locals())))
# generated from '$noValue' at line 2, col 1.
write('\n')
if self._errorCatchers.has_key("BigEcho"):
self._errorCatcher = self._errorCatchers["BigEcho"]
else:
self._errorCatcher = self._errorCatchers["BigEcho"] = \
ErrorCatchers.BigEcho(self)
write(filter(self.__errorCatcher1(localsDict=locals())))
# generated from '$noValue' at line 4, col 1.
write('\n')
The template:
#import pprint #errorCatcher ListErrors $noValue $anotherMissingValue.really $pprint.pformat($errorCatcher.listErrors) ## This is really self.errorCatcher().listErrors()
The output:
$noValue
$anotherMissingValue.really
[{'code': 'VFS(SL,"noValue",1)',
'exc_val': <NameMapper.NotFound instance at 0x8170ecc>,
'lineCol': (3, 1),
'rawCode': '$noValue',
'time': 'Wed May 15 00:38:23 2002'},
{'code': 'VFS(SL,"anotherMissingValue.really",1)',
'exc_val': <NameMapper.NotFound instance at 0x816d0fc>,
'lineCol': (4, 1),
'rawCode': '$anotherMissingValue.really',
'time': 'Wed May 15 00:38:23 2002'}]
The generated import:
import pprint
Then in the generated class, we have our familiar .respond method
and several new methods:
def __errorCatcher1(self, localsDict={}):
"""
Generated from $noValue at line, col (3, 1).
"""
try:
return eval('''VFS(SL,"noValue",1)''', globals(), localsDict)
except self._errorCatcher.exceptions(), e:
return self._errorCatcher.warn(exc_val=e, code= 'VFS(SL,"noValue",1)' ,
rawCode= '$noValue' , lineCol=(3, 1))
def __errorCatcher2(self, localsDict={}):
"""
Generated from $anotherMissingValue.really at line, col (4, 1).
"""
try:
return eval('''VFS(SL,"anotherMissingValue.really",1)''', globals(),
localsDict)
except self._errorCatcher.exceptions(), e:
return self._errorCatcher.warn(exc_val=e,
code= 'VFS(SL,"anotherMissingValue.really",1)' ,
rawCode= '$anotherMissingValue.really' , lineCol=(4, 1))
def __errorCatcher3(self, localsDict={}):
"""
Generated from $pprint.pformat($errorCatcher.listErrors) at line, col
(5, 1).
"""
try:
return eval('''VFN(pprint,"pformat",0)(VFS(SL,
"errorCatcher.listErrors",1))''', globals(), localsDict)
except self._errorCatcher.exceptions(), e:
return self._errorCatcher.warn(exc_val=e, code=
'VFN(pprint,"pformat",0)(VFS(SL,"errorCatcher.listErrors",1))' ,
rawCode= '$pprint.pformat($errorCatcher.listErrors)' ,
lineCol=(5, 1))
def respond(self,
trans=None,
dummyTrans=False,
VFS=valueFromSearchList,
VFN=valueForName,
getmtime=getmtime,
currentTime=time.time):
"""
This is the main method generated by Cheetah
"""
if not trans:
trans = DummyTransaction()
dummyTrans = True
write = trans.response().write
SL = self._searchList
filter = self._currentFilter
globalSetVars = self._globalSetVars
########################################
## START - generated method body
if exists(self._filePath) and getmtime(self._filePath) > self._fileMtime:
self.compile(file=self._filePath)
write(getattr(self, self._mainCheetahMethod_for_x)(trans=trans))
if dummyTrans:
return trans.response().getvalue()
else:
return ""
if self._errorCatchers.has_key("ListErrors"):
self._errorCatcher = self._errorCatchers["ListErrors"]
else:
self._errorCatcher = self._errorCatchers["ListErrors"] = \
ErrorCatchers.ListErrors(self)
write(filter(self.__errorCatcher1(localsDict=locals())))
# generated from '$noValue' at line 3, col 1.
write('\n')
write(filter(self.__errorCatcher2(localsDict=locals())))
# generated from '$anotherMissingValue.really' at line 4, col 1.
write('\n')
write(filter(self.__errorCatcher3(localsDict=locals())))
# generated from '$pprint.pformat($errorCatcher.listErrors)' at line
# 5, col 1.
write('\n')
# This is really self.errorCatcher().listErrors()
########################################
## END - generated method body
if dummyTrans:
return trans.response().getvalue()
else:
return ""
So whenever an error catcher is active, each placeholder gets wrapped in its own method. No wonder error catchers slow down the system!
The template:
Text before breakpoint. #breakpoint Text after breakpoint. #raise RuntimeError
The output:
Text before breakpoint.
The generated code:
write('Text before breakpoint.\n')
Nothing after the breakpoint was compiled.
The template:
// Not a comment #compiler commentStartToken = '//' // A comment #compiler reset // Not a comment
The output:
// Not a comment // Not a comment
The generated code:
write('// Not a comment\n')
# A comment
write('// Not a comment\n')
So this didn't affect the generated program, it just affected how the template definition was read.
This chapter will be an overview of the files in the Cheetah package, and how they interrelate in compiling and filling a template. We'll also look at files in the Cheetah tarball that don't get copied into the package.
This chapter will mainly walk through the Cheetah.Template constructor
and not at what point the template is compiled.
(Also need to look at Transaction,py and Servlet.py .)
How templates are compiled: a walk through Parser.py's source. (Also need to look at Lexer.py, but not too closely.)
How templates are compiled: a walk through Compiler.py .
In spring 2001, several members of the webware-discuss mailing list expressed
the need for a template engine. Webware like Python is great for organizing
analytical logic, but they both suffer when you need to do extensive variable
interpolation into large pieces of text, or to build up a text string from its
nested parts. Python's % operator gets you only so far, the syntax is
cumbersome, and you have to use a separate format string for each nested part.
Most of us had used template systems from other platforms-chiefly Zope's DTML,
PHPLib's Template object and Java's Velocity-and wanted to port something like
those so it could be used both in Webware servlets and in standalone Python
programs.
Since I (Mike Orr) am writing this history, I'll describe how I encountered Cheetah. I had written a template module called PlowPlate based on PHPLib's Template library. Like PHPLib, it used regular expressions to search and destroy-er, replace-placeholders, behaved like a dictionary to specify placeholder values, contained no directives, but did have BEGIN and END markers which could be used to extract a named block (subtemplate). Meanwhile, Tavis Rudd was also on webware-discuss and interested in templates, and he lived just a few hours away. So 12 May 12, 2001 we met in Vancouver at a gelato shop on Denman Street and discussed Webware, and he drew on a napkin the outline of a template system he was working on.
Instead of filling the template by search-and-replace, he wanted to break it up into parts. This was a primitive form of template compiling: do the time-consuming work once and put it to a state where you can fill the template quickly multiple times. A template without directives happens to break down naturally into a list of alternating text/placeholder pairs. The odd subscript values are literal strings; the even subscripts are string keys into a dictionary of placeholder values. The project was called TemplateServer.
In a couple months, Tavis decided that instead of compiling to a list, he
wanted to compile to Python source code: a series of write calls that
would output onto a file-like object. This was the nucleus that became
Cheetah. I thought that idea was stupid, but it turned out that this
not-so-stupid idea blew the others out of the water in terms of performance.
Another thing Tavis pushed hard for from near the beginning was ``display
logic'', or simple directives like #for, #if and
#echo. (OK, #echo came later, but conceptually it belongs
here. I thought display logic was even stupider than compiling to Python
source code because it would just lead to ``DTML hell''-complicated templates
that are hard to read and maintain, and for which you have to learn (and debug)
a whole new language when Python does it just fine. But others (hi Chuck!) had
templates that were maintained by secretaries who didn't know Python, and the
secretaries needed display logic, so that was that. Finally, after working
with Cheetah templates (with display logic) and PlowPlate templates (with just
blocks rather than display logic), I realized Tavis was smarter than I was and
display logic really did belong in the template.
The next step was making directives for all the Python flow-control
statements: #while, #try, #assert, etc. Some of
them we couldn't think of a use for. Nevertheless, they were easy to code,
and ``somebody'' would probably need them ``someday'', so we may as well
implement them now.
During all this, Chuck Esterbrook, Ian Bicking and others offered (and still offer) their support and suggestions, and Chuck gave us feedback about his use of Cheetah-its first deployment in a commercial production environment. Later, Edmund Lian became our #1 bug reporter and suggester as he used Cheetah in his web applications.
A breakthrough came in fall 2001 when Tavis figured out how to implement the name mapper in C. The name mapper is what gives Cheetah its Autocalling and Uniform Dotted Notation features. This raised performance sufficiently to rewrite Cheetah in a totally ``late binding'' manner like Python is. More about this is in the next chapter.
We were going to release 1.0 in January 2002, but we decided to delay it until more people used it in real-world situations and gave us feedback about what is still needed. This has led to many refinements, and we have added (and removed) features according to this feedback. Nevertheless, Cheetah has been changing but stable since the late-binding rewrite in fall 2001, and anybody who keeps up with the cheetah-discuss mailing list will know when changes occur that require modifying one's template, and since most people use point releases rather than CVS, they generally have a few week's warning about any significant changes.
More detail on Cheetah's history and evolution, and why it is the way it is, can be found in our paper for the Python10 conference, http://www.cheetahtemplate.org/Py10.html.
One of the first decisions we encountered was which delimiter syntax to use.
We decided to follow Velocity's $placeholder and #directive
syntax because the former is widely used in other languages for the same
purpose, and the latter stands out in an HTML or text document. We also
implemented the ${longPlaceholder} syntax like the shells for cases
where Cheetah or you might be confused where a placeholder ends. Tavis went
ahead and made $(longPlaceholder} and $[longPlaceholder]
interchangeable with it since it was trivial to implement. Finally,
the #compiler directive allows you to change the delimiters if you
don't like them or if they conflict with the text in your document.
(Obviously, if your document contains a Perl program listing, you don't
necessarily want to backslash each and every $ and #, do you?)
The choice of comment delimiters was more arbitrary. ## and
#* ...*# doesn't match any language, but it's reminiscent of
Python and C while also being consistent with our ``# is for
directives'' convention.
We specifically chose not to use pseudo HTML tags for placeholders and directives, as described more thoroughly in the Cheetah Users' Guide introduction. Pseudo HTML tags may be easier to see in a visual editor (supposedly), but in text editors they're hard to distinguish from ``real'' HTML tags unless you look closely, and they're many more keystrokes to type. Also, if you make a mistake, the tag will show up as literal text in the rendered HTML page where it will be easy to notice and eradicate, rather than disappearing as bogus HTML tags do in browsers.
One of Cheetah's unique features is the name mapper, which lets you write
$a.$b without worrying much about the type of a or b.
Prior to version 0.9.7, Cheetah did the entire NameMapper lookup at runtime.
This provided maximum flexibility at the expense of speed. Doing a NameMapper
lookup is intrinsically more expensive than an ordinary Python expression
because Cheetah has to decide what type of container a is, whether the
the value is a function (autocall it), issue the appropriate Python incantation
to look up b in it, autocall again if necessary, and then convert the
result to a string.
To maximize run-time (filling-time) performance, Cheetah 0.9.7 pushed much of
this work back into the compiler. The compiler looked up a in the
searchList at compile time, noted its type, and generated an eval'able Python
expression based on that.
This approach had two significant drawbacks. What if a later changes
type before a template filling? Answer: unpredictable exceptions occur. What
if a does not exist in the searchList at compile time? Answer: the
template can't compile.
To prevent these catastrophes, users were required to prepopulate the
searchList before instantiating the template instance, and then not to change
a's type. Static typing is repugnant in a dynamic language like Python,
and having to prepopulate the searchList made certain usages impossible. For
example, you couldn't instantiate the template object without a searchList and
then set self attributes to specify the values.
After significant user complaints about the fragility of this system, Tavis
rewrote placeholder handling, and in version 0.9.8a3 (August 2001), Tavis
moved the name mapper lookup back into runtime. Performance wasn't crippled
because he discovered that writing a C version of the name mapper was easier
than anticipated, and the C version completed the lookup quickly. Now Cheetah
had ``late binding'', meaning the compiler does not look up a or care
whether it exists. This allows users to create a or change its type
anytime before a template filling.
The lesson we learned is that it's better to decide what you want and then figure out how to do it, rather than assuming that certain goals are unattainable due to performance considerations.
How to commit changes to CVS or submit patches, how to run the test suite. Describe distutils and how the regression tests work.
The codeTemplate class contains not only the Cheetah infrastructure, but also
some convenience methods useful in all templates. More methods may be added if
it's generally agreed among Cheetah developers that the method is sufficiently
useful to all types of templates, or at least to all types of HTML-output
templates. If a method is too long to fit into Template - especially
if it has helper methods - put it in a mixin class under Cheetah.Utils
and inherit it.
Routines for a specific problem domain should be put under
Cheetah.Tools, so that it doesn't clutter the namespace unless the user
asks for it.
Remember: Cheetah.Utils is for objects required by any part of Cheetah's
core. Cheetah.Tools is for completely optional objects. It should
always be possible to delete Cheetah.Tools without breaking Cheetah's
core services.
If a core method needs to look up an attribute defined under
Cheetah.Tools, it should use hasattr() and gracefully provide a
default if the attribute does not exist (meaning the user has not imported that
subsystem).
Cheetah ships with a regression test suite. To run the built-in tests, execute at the shell prompt:
cheetah test
Before checking any changes in, run the tests and verify they all pass. That way, users can check out the CVS version of Cheetah at any time with a fairly high confidence that it will work. If you fix a bug or add a feature, please take the time to add a test that exploits the bug/feature. This will help in the future, to prevent somebody else from breaking it again without realizing it. Users can also run the test suite to verify all the features work on their particular platform and computer.
The general procedure for modifying Cheetah is as follows:
diff on the two
outputs. (diff is a utility included on all Unix-like systems. It
shows the differences between two files line by line. A precompiled
Windows version is at
http://gnuwin32.sourceforge.net/packages/diffutils.htm, and MacOS
sources at http://perso.wanadoo.fr/gilles.depeyrot/DevTools_en.html.)
python setup.py install before testing it. If you make it in the
installed version, do not run the installer or it will overwrite your
changes!
cheetah test to verify you didn't break anything. Then run
your little test program.
cheetah test runs cleanly with your regression test
included, update the CHANGES file and check in your changes. If you
made the changes in your installed copy of Cheetah, you'll have to copy
them back into the CVS sandbox first. If you added any files that must be
distributed, be sure to cvs add them before committing.
Otherwise Cheetah will run fine on your computer but fail on anybody
else's, and the test suite can't check for this.
If you add a directory to Cheetah, you have to mention it in setup.py or
it won't be installed.
The tests are in the Cheetah.Tests package, aka the src/Tests/
directory of your CVS sandbox. Most of the tests are in
SyntaxAndOutput.py. You can either run all the tests or choose which
to run:
python Test.py
Run all the tests. (Equivalent to cheetah test.)
python SyntaxAndOutput.py
Run only the tests in that module.
python SyntaxAndOutput.py CGI
Run only the tests in the class CGI inside the module. The class
must be a direct or indirect subclass of
unittest_local_copy.TestCase.
python SyntaxAndOutput.py CGI Indenter
Run the tests in classes CGI and Indenter.
python SyntaxAndOutput.py CGI.test1
Run only test test1, which is a method in the CGI class.
To make a SyntaxAndOutput test, first see if your test logically fits into one
of the existing classes. If so, simply add a method; e.g., test16.
The method should not require any arguments except self, and should
call .verify(source, expectedOutput), where the two arguments are
a template definition string and a control string. The tester will complain
if the template output does not match the control string. You have a wide
variety of placeholder variables to choose from, anything that's included in
the defaultTestNameSpace global dictionary. If that's not enough, add
items to the dictionary, but please keep it from being cluttered with wordy
esoteric items for a single test).
If your test logically belongs in a separate class, create a subclass of
OutputTest. You do not need to do anything else; the test suite will
automatically find your class in the module. Having a separate class allows
you to define state variables needed by your tests (see the CGI class)
or override .searchList() (see the Indenter class) to provide
your own searchList.
To modify another test module or create your own test module, you'll have to
study the existing modules, the unittest_local_copy source, and the
unittest documentation in the Python Library Reference. Note that we
are using a hacked version of unittest to make a more convenient test
structure for Cheetah. The differences between unittest_local_copy
and Python's standard unittest are documented at the top of the module.
How to build the documentation. Why LaTeX, a minimum LaTeX reference, etc.
Safe delegation, as provided by Zope and Allaire's Spectra, is not implemented in Cheetah. The core aim has been to help developers and template maintainers get things done, without throwing unnecessary complications in their way. So you should give write access to your templates only to those whom you trust. However, several hooks have been built into Cheetah so that safe delegation can be implemented at a later date.
It should be possible to implement safe delegation via a future configuration
Setting safeDelegationLevel (0=none, 1=semi-secure, 2-alcatraz). This
is not implemented but the steps are listed here in case somebody wants to try
them out and test them.
Of course, you would also need to benchmark your code and verify it does not impact performance when safe delegation is off, and impacts it only modestly when it is on." All necessary changes can be made at compile time, so there should be no performance impact when filling the same TO multiple times.
#attr directive and maybe the #set directive.
self, etc
(e.g. #if $steal(self.thePrivateVar) )
$obj.__class__.__dict__ are not possible.
This document was generated using the LaTeX2HTML translator.
LaTeX2HTML is Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds, and Copyright © 1997, 1998, Ross Moore, Mathematics Department, Macquarie University, Sydney.
The application of LaTeX2HTML to the Python documentation has been heavily tailored by Fred L. Drake, Jr. Original navigation icons were contributed by Christopher Petrilli.