Application servers
DWIM HTML TEMPLATING IN PYTHON
I've never been happy with templating languages. Oh, sure, I use them. Wordpress (what this blog is [was - Elf in 2024] written in) is written in PHP, and I've used eruby and erb for other websites. I understand what they're trying to accomplish, but if the objective is to write an application that happens to use HTML as its display language, templating languages aren't my favorite way to go.
My main objection to templating languages is that they're a third language between your HTML and your programming language. This isn't obvious when using PHP because PHP crams both the template and the language into the same semantic level. ERB makes templating almost painless. Python, however, with its need for whitespace delimeters, has a much harder time of bringing those two layers into congruence, and so you get oddities like Cheetah, which is "a front end for Python, not a replacement."
When I started writing Python I was pretty naive about the whole Python universe of code. I dealt with Cheetah and really, didn't like it at all (although my old friend Alex Martelli seems to like it). My biggest complaint about Cheetah was that it looked too much like HTML, which is not the language we're writing in. We're writing in Python. We just happen to have HTML as output.
People will tell you that templating languages like Cheetah separate the view from the controller and they will insist that this is a Good Thing, and I agree. They ought to be separate methods. They don't need to be in separate rooms, though. Really, when I'm working in Rails I loathe the fact that I have to dig around in the layouts and views for the visual display and then hunt down the code that populates it all somewhere else. I'll later show that I love this for other language features. I'm not gonna be consistent about this one.
It's the ugliness that bugs me most. So, rather than make my programming language of choice, Python, which has that whitespace as delimeter issue that makes the shoehorn even more painful to use, look like awful HTML, I've decided to try and make HTML look like Python. This is nice because it lets me containerize HTML, which guarantees HTML consistency.
My application server of choices is Webware, an older and more traditional application server. I like it because it "fits my brain," and I came up with this templating solution because it, too, fits my brain. You don't have to know much about Webware, although in the future my example code will be a Webware application. What you do need to know is that when a transaction comes in on the server, you call a method write() to spew content back out to the client (usually your web browser).
Here's an example:
self.write(HTML(Head(
Title("Hello World!")),
Body(
P("Hello, World", klass="uberprettified"))))
That's about as self-explanatory code as I can imagine. You can imagine the nightmare, though, in adding all of these keywords to Python. Obviously, I'm not going to hand-code every one of the HTML tags, right? There's a parent class, and here's what it looks like:
class _Tag:
def __init__(self, *content, **kw):
self.data = content
self._attributes = {}
for i in kw:
self._attributes[(i == 'klass' and 'class') or
(i == 'fur' and 'for') or i] = kw[i]
def render_content(self, content):
if type(content) == type(""):
return content
if hasattr(content, '__iter__'):
return string.join([self.render_content(i) for i in content], '')
return self.render_content(str(content))
def __str__(self):
tagname = self.__class__.__name__.lower()
return ('<%s' % tagname +
(self._attributes and ' ' or '') +
string.join(['%s="%s"' %
(a, htmlEncode(str(self._attributes[a])))
for a in self._attributes], ' ') +
'>' +
self.render_content(self.data) +
'</%s>\n' % tagname)
And then we go through the heartache:
class HTML(_Tag): pass
class Head(_Tag): pass
class Body(_Tag): pass
class Title(_Tag): pass
class P(_Tag): pass
... and so forth.
The keywords class
and for
are reserved words, so I've had to change them to 'klass' and 'fur'
respectively, giving my code a vaguely germanic air.
The real beauty of this code is the way the second line of render_content
works: it exploits
Python's ability to have a list of things thrown into a method call, which in the example at top is
shown most clearly in the HTML() object. Those list can then contain other lists. I'm down to
not-list things (the two strings), I call str()
, which then renders both the containing HTML tag
and the stuff it contains (which can be more _Tag objects). It's powerful recursion, and it creates
automagically containerized, human-readable HTML embededed in Python.
In the next part, I'll show a way to manipulate Python introspection's abilities to make the
hand-coding of all the HTML objects a thing of the past, and I promise it doesn't involve eval()
.