Tavis Rudd
Posts: 13
Nickname: tavis
Registered: Jan, 2006
|
|
Re: Django vs. Cheetah: 1-0
|
Posted: Jan 31, 2006 10:38 AM
|
|
Hi Guido, thanks for the note about 2.5 issues. I assume you are using Cheetah 1.0 as that's already fixed in 2.0. I haven't bothered fixing it in 1.0 as it's somewhat unlikely for someone to use 1.0 with Py 2.5a when Cheetah 2.0rc3 is available ;)
Most of the other points you raise are either due to misunderstandings (i.e. from bad explanations in our docs) or have been resolved in Cheetah 2.0.
I'll go through them point by point:
** Point 1: "The biggest difference is that Cheetah allows pretty much arbitrary Python call syntax, e.g. ${foo.bar('hello', $name, 42+42)}."
Cheetah 2.0 provides a facility for selectively restricting both the Cheetah directives allowed and the Python/Cheetah expressions allowed. It allows you to take either a 'deny all then enable' or a 'selectively disable' approach. These restrictions occur at template compile time. It is not a rexec system.
** Point 2: "you have to prefix variable references in the argument list with another $, and there are confusing rules about when the $ is optional."
The docs are confusing, the rules are not. We improving this in the 2.0 documentation. $ is only required when you are inserting a $placeholder into text at the top level. Inside a #directive or other expression, you only need a $ if you want to do a var lookup using the searchList. Note, the NameMapper/searchList stuff is completely optional and many people don't use it.
** Point 3: "A big difference: if Django doesn't find a name, it inserts nothing; but Cheetah raises an exception. ISTM that Django is more user-friendly here, even if its approach could be considered error-prone (typos are easily missed since they simply suppress a small bit of output). In my experience, missing variables are very common in substitution data, and Cheetah requires you to provide explicit default values in this case."
First off, Cheetah 2.0 introduces a 'silent-mode' for placeholders which does exactly what you want: $!doNothingIfNotFound vs. $raiseAnExceptionIfNotFound This syntax is taken from Java's Velocity.
As other commenters have pointed out > > Guido was wrong thinking that Cheetah always raises an > > exception for unfound names. It is optional and > > intentional (usefull if you want to catch any errors > very > > early). > > I read that before I posted, but they state very clearly > that they don't want you to use this to provide default > values for substitutions; they only want you to use it for > debugging. Again, the recommended approach is wrong IMO > and that counts more than what you can hack. Fighting the > system should not be a standard behavior.
Most users of Cheetah would disagree with you here. You don't want errors from mistyped $placeholderNames to slip by silently. If you want to provide default values, use a default namespace in the searchList.
** Point 4: "But then I stumbled upon Cheeta's inline form, which is pretty unreadable due to the symmetric delimiters: # if $name # <h1>Hello $name</h1> # else # <h1>Hello There</h1> # end if # "
In 2.0, you could have also used the following forms:
#if $name:<h1>Hello $name</h1> #else: <h1>Hello There</h1>
OR
#if $name then c'<h1>Hello $name</h1>' else '<h1>Hello There</h1>'
c'<h1>Hello $name</h1>' is a special string syntax that allows you to use $placeholders inside of strings that are part of expressions.
** Point 5: "See the examples near the top. I like Django's version better: you pass the variable bindings in to the render() method. Cheetah lets you specify multiple dictionaries with variable bindings, which are searched one after another, but it bothers me that these all have to be passed to the Template() constructor instead of to the render method (which is called __str__() in Cheetah :-)."
It also bothered me. 2.0 provides a new classmethod api (Template.compile) which returns a compiled template class. You then instantiate that class with a searchList (if you are using NameMapper,etc.). This is the new recommended usage style in 2.0 is to create a template class and reuse it many times:
tclass = Template.compile(src)
t1 = tclass() # or tclass(namespaces=[namespace,...]) t2 = tclass() # or tclass(namespaces=[namespace2,...])
outputStr = str(t1) # or outputStr = t1.aMethodYouDefined()
Template.compile provides a very flexible API via its optional arguments so there are many possible variations of this pattern. One example is:
tclass = Template.compile('hello $name from $caller', baseclass=dict) print tclass(name='world', caller='me')
See the Template.compile() docstring for more details.
Template.compile() caches the compiled code in most cases, so if you call it in a tight loop with a src string that has already been compiled you won't be hit with the cost of recompiling for each iteration.
Furthermore, 2.0 allows you to pass the searchList into a specific method on the template. This post is already way to long, so I'll post an example later.
*** Point 6: "Django's template compilation is much simpler and IMO more elegant than Cheetah: Django parses the template text into nodes of various types using a big regular expression, and each node has an appropriate render() method. Rendering the template in a given context simply concatenates the results of rendering each node in that context. I imagine this could easily be turned into a generator compatible with WSGI."
Guido, are you honestly saying that one big regex is more elegant than a recursive descent parser (even a slightly ugly hand-built one)? Cheetah, in its toddler phase, used a simple regex-based parser. It was a nightmare: brittle, limited, and hard to understand!
Cheetah's parser doesn't build an intermediate AST, which is arguably a failing but hasn't been an issue in practice. Anyway, if you want to critique the parser please look at the 2.0 version :)
*** Point 7: "Cheetah, OTOH, compiles each template to a Python class! This is much slower, and in my experience brittle -- on my first attempt I introduced a syntax error in the template that caused a Python syntax error in the resulting Python class, which was hard to debug. It also seems overkill, and I worry that it might cause security problems"
You're missing a key usage issue here. Cheetah users do *not* compile their templates for every web request. The compile a template *once* and then a method is called on it for each request. Or if you use Template.compile() you can create a new template instance per request. Besides, the compilation is cached.
Regarding Python syntax errors slipping through, you've got a good point. As Cheetah can accept almost any Python syntax in its expressions I avoided implementing full parsing of the Python syntax for fear that I'd introduce more errors in the parsing than I'd prevent from slipping through. That said, we're moving towards full parsing of Python expressions.
Btw, the tracebacks in 2.0 are much easier to understand as Template.compile() allows you to keep the compiled module files in a cache dir which Python can then access during traceback formatting.
Compiling the templates to a Python class is regarded as Cheetah's greatest strength as it allows you to subclass templates in either Python or Cheetah. Vice versa, your templates can be subclasses of *any* Python class:
tclass = Template.compile('hello $name', baseclass=dict) print tclass(name='Guido')
T1 = Template.compile(''' foo - $meth1 - bar #def meth1: this is T1.meth1''')
T2 = T1.subclass('#implements meth1\n this is T2.meth1')
*** Point 8: "I could see a malicious template author "breaking out" of the templating languages and invoking unauthorized Python code. Now, I wouldn't let people I don't trust edit templates on my website anyway, but it appears to be a common pattern that certain people are allowed to edit templates but not code. Cheetah blurs the distinction a little too much for my comfort."
Regarding security, see my note above about the expression/directive filtering in 2.0.
Thanks for checking it out! Tavis
|
|