Better Form Generation with Mako and Pylons

If you're a web developer in New York City, unless you work here, here or maybe here, you're probably not using Python for primary development (if you are, please post your company here). Since I'm not a PHP monkey or a Microsoftie, everywhere I work they're using Struts, which is where the dust has settled around Java Server Pages (with honorable mentions to Spring MVC and JSF).

I'm in a position where I might be able to push Python technology for a project or two, which have until now been built on Struts 1, the most common version of Struts even though Struts 2 is considerably nicer. So I wanted to see where Pylons is at with form rendering and processing these days. Most importantly, I wanted to ensure that layout is accomplished plainly within a template with no module-embedded HTML and no "magic generation" of forms from classes or other datastructures, and that the cycle of data from form to controller back to form again is similarly simple and obvious. Most projects in New York are the kind that get handed off to totally different people when complete, or even just 80% complete since you've been put onto something else, for remaining development and future enhancements. Code handoffs are extremely common, so it can't be overstated how much more important it is to be obvious than to be DRY. This is a big reason that tedious, plodding approaches like Struts and PHP are so popular - through raw verbosity they discourage opaque ways of doing things. Overly clever, data-driven rendering solutions that nobody can understand or extend (and are usually broken, anyway) are basically what gets thrown away and rewritten by the next team.

In developing Mako, a primary goal was to make a super-nice version of a particular "component" pattern which I had used for years primarily with HTML::Mason which for me provides a "sweet spot" of obviousness, agility, and succinctness. The focus is around the ability to create "tag libraries" which interact easily with a server-parsed templating language, and which can be implemented within templates themselves. In JSP development, taglibs are now the standard way to indicate dynamic areas of templates, but while they look pretty clean, they are painful to implement (requiring HTML embedded in hand-crafted classes, a few dozen XML pushups for every tag you add, and the obligatory application restart whenever they change), and the EL and OGNL expressions which are standard within taglibs interact terribly with straight Java code.

Mako allows the creation of tags which can be arbitrarily nested and interactive with one another via the <%def> construct, in combination with the <%call> tag. It's been my observation that the <%call> tag as well as the usage of nesting-aware <%defs> hasn't caught on yet, as the examples in the docs are a little dense, so here I will seek to demystify it a bit.

Pylons currently recommends a decent approach to rendering forms, using Form Tags which are essentially little functions you can embed in your template to render standard form elements. The handling of the form at the controller level uses FormEncode and routes validation errors through htmlfill. My approach modifies this to use Mako tags to build a site-specific taglib around the webhelpers tags and adds an explicit interaction between those tags and the controller, in a manner similar to a Struts form handler, which replaces htmlfill and allows all layout, including the layout of validation error messages, using the same template system. It also adds a preprocessor that illustrates how to build custom tags in Mako which look as nice as the built-in ones.

A tar.gz of the approach, which at this point should be regarded strictly as a proof of concept, can be downloaded here (works against Pylons 0.9.7), which contains three templates each illustrating a different approach to laying out the form. The three approaches are raw webhelpers with htmlfill, the <%call> tag approach, and finally using the "custom tag" approach. The final result, present in the file templates/mako_helpers.html, looks like this:

<%namespace name="form" file="/form_tags.mako"/>
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Mako Form Helpers</title>
  <link rel="stylesheet" href="/style.css" type="text/css" />
</head>
<body>
 
<h3>Using Mako Helpers</h3>
 
<%form:form controller='comment' action='mako_post'>
<table>
    <tr>
        <th colspan="2">Submit your Comment</th>
    </tr>
    <tr>
        <td>Your Name:</td>
        <td><%form:text name="name"/></td>
    </tr>
 
    <tr>
        <td>How did you hear about this site ?</td>
        <td>
            <%form:select name="heard">
                <%form:option value="" selected="True">None</%form:option>
                % for desc, value in c.heard_choices:
                    <%form:option value="${value}">${desc}</%form:option>
                % endfor
            </%form:select>
        </td>
    </tr>
 
    <tr>
        <td>Comment:</td>
        <td><%form:textarea name="comment"/></td>
    </tr>
 
    <tr>
        <td colspan="2"><%form:submit/></td>
    </tr>
</table>
</%form:form>
 
</body>
</html>

Where of note are the Mako-like <%form:foo> tags that aren't part of Mako ! A short preprocessor is applied to the source file which turns a tag like <%form:foo> into <%call expr="foo()"> at template compile time. I.e., a tag used above as:

<%form:textarea name="comment"/>

is preprocessed into raw Mako code:

<%call expr="form.textarea(name='comment')"/>

The %call tag invokes the textarea def inside the form namespace, which is defined in the file form_tags.mako. The def for textarea looks like:

<%def name="textarea(name, default=None, **attrs)">\
<%doc>
    Render an HTML <textarea></textarea> tag pair with embedded content.
</%doc>\
${form_errors(name)}\
${h.textarea(name, content=request.params.get(name, default), **attrs)}\
</%def>

Above, the $form_errors(name) is a def call used for reporting validation messages. The point of form_tags.mako is that all the form tags and their layout is plainly visible and easily customized. Multiple versions of the file can be used in one application, providing different form layouts for different areas. The fact that it uses the h helper to render the actual HTML for each form control is also arbitrary; you could just as well implement the <textarea> source directly within the def if some special treatment were needed.

The demo also contains a modified version of Pylons' @validate decorator. Usage is mostly the same, except the form parameter is replaced by the more direct input_controller parameter, which is the method used for input:

from formhelpers.lib.mako_forms import validate as mako_validate
 
class CommentController(BaseController):
    def index(self):
        return render('/mako_helpers.html')
 
    @mako_validate(schema=CommentForm, input_controller=index)
    def mako_post(self):
        c.name = self.form_result['name']
        return render('/thanks.html')

Where above, mako_post hits the validator, and on an invalid exception the index controller method is called instead. Validation errors are placed in self.form_errors as well as c.form_errors for template access. The validator as well as the preprocessor are defined in lib/mako_forms.py.

That's pretty much all there is to it at this point, a few folks on #pylons seem enthused about it, so perhaps this can be turned into something more formally available and/or recommended when implementing a Pylons/Mako application.

SQLAlchemy code swarm

Django's already got one, and Brian Rosner made one for us too: link. SQLA's pre-release development is apparent here which is why it floats around "zzzeek" for so long.

Reddit.com Goes Open Source

Reddit has opened their source up, and we can now see just what they've been up to. It's been known for some time that Reddit was (re)built using Pylons and Mako templates, contrary to their FAQ which still states that they use web.py. As it turns out, they've also built something of their own database layer, which seems to include a homegrown caching layer and ultimately is built on top of SQLAlchemy, using the SQLA expression language to generate queries. Connections are served with the QueuePool, and they use the threadlocal setting, so that they can get implicit access to transactions in progress. They vertically partition their database access among four separate engines across four distinct areas of functionality on the site.

This is currently the highest volume website I'm aware of using SQLAlchemy and Pylons, and is a testament to the stability of our core components (I hope). Python in general is not too prominent in New York City where I work; Java, PHP and .NET are still the default "goto" platforms, and most developers here look at you kind of funny when you mention Python. Look how well-known Java advocate Ted Neward says "even Python!", as though we're the most fringe Java alternative imaginable. I hope examples like Reddit continue to illustrate that Python presents the best mix of performance, stability, and rapid development for web development today, not to mention one of the broadest software ecosystems in the field (which I've always maintained is a good thing).

Pycon Wrapup

Jason and I spent a full eight days in Chicago this year, with a full slate of activities. While we didn't commit to SQLAlchemy sprints ahead of time (and were therefore not officially rostered), we sprinted the entire time on SA and had picked up two or three folks to sprint with us, as well as helped with some "parallel" SQLAlchemy-related sprints - next year we'll definitely plan ahead of time so that people can get involved sooner and more explicitly.

Pycon started off for SA with a big full day of tutorials, with the work split up among myself, Jason, and Jonathan. Jonathan did his beginner tutorial, Jason and I did the advanced (slides). The huge winner for this tutorial was the slide runner we came up with ("we" means, I wrote a little 20 line header to page through a Python script one chunk at a time, Jason ran 100 miles further with the idea to turn it into a full blown interactive Python prompt). Using the runner, everyone in the room could keep hitting "enter" and each chunk of code on the projector would come out on their screen and execute itself, leaving them at a Python prompt where they could further explore the constructs that were just created. Some people just sat back and watched the code go by, others dug in and answered our exercise questions with it.

Next up was my "SQLAlchemy 0.4 and Beyond" talk (slides) where my goal was to bring people up to speed on where we're at right now, including where we came from, some of the problems we had, and what we've done about them, then segueing into some deeper examples from the current release and into some of the more interesting projects that are underway.

Onto the sprints. By the time they started, we had already met with various people and attended a few BOFs as well. Here's a rundown of everything going on:

  • Jython - Frank Wierzbicki, recently hired by Sun to work full time on Jython, had presented his Jython integration of SQLAlchemy. Frank and I could not share the same goals more closely; both coming from an "enterprisey" Java background, we are well aware that there's a vast world of Java developers who would flock to Python if a Java-compatible yet Pythonic toolset were available, particularly including an ORM with features comparable to Hibernate. Jason and Frank met up during the sprints to align their architectural paths, as Jason has been working very hard on a full-blown multi-dialect architecture, which will allow the maximum amount of reuse of dialects among any number of DBAPI connectors. An example is to use the same MySQL compiler with MySQLDB, JDBC, and pyODBC. Stub modules which account for the idiosyncrasies of each DBAPI would draw upon the central MySQL functionality. So with a little more effort that's underway, SQLAlchemy should work fully out of the box on Jython trunk in the very near future.

  • Django - yup, a lot went on with Django and SQLAlchemy this year. We met with a whole room full of Djangoers who've expressed the ongoing desire for Django to have a tighter integration layer with SQLAlchemy available as an option. Michael Trier spearheaded the effort, showing us some of the work he and his team had already completed with their own django-sqlalchemy layer. We sat down and shared a lot of notes and ideas, including using the new SQLAlchemy declarative plugin as a guide for developing their own solution, as well as some strategies for best integrating SQLAlchemy connection/transaction management with Django's own transaction middleware. We had an initial mini-sprint during an earlier BOF and then we communicated throughout the main sprints. Jason has hinted he might give Django a try on an upcoming project which would give us a great chance to give it a test spin and fine tune it.

  • Zope - I was thrilled to spend pretty much the entire sprint alongside Christian Theune, a very experienced developer from whom I sought to learn as much as possible. His company already uses a SQLAlchemy/Zope integrated application, and he was very interested in the new custom instrumentation branch, which was started to enable Philip Eby to integrate SQLAlchemy with Trellis. Christian worked through the branch, which presents an entirely open-ended way to redefine how SQLAlchemy classes are instrumented (or not), to make it such that all access to the mapped class and its instances, including SQLA's own private attributes, may be mediated through external code, thus allowing his application to use Zope security proxies to broker access to all end-user instances and classes. At the same time, Christian is also interested in full session rollback capability, something we were already starting to implement, so we're also finally getting deep into letting SA rollback the internal state of its Session in a clean way, so that rollbacks within transactions or SAVEPOINTs will leave you with a session that remains fully usable without needing to expire its contents. The comprehensive rollback feature will appear as an option sometime within the 0.4 series and be on by default in 0.5.

  • Pylons/Turbogears - This is of course our main stomping ground. SQLA sprints took place in the same room as the Pylons TG sprint and there was much beer and margaritas consumed. Chris Perkins of DBSprokets fame has signed on with myself and Mark Ramm to make some progress on the Prentice Hall SQLAlchemy book....but for the impatient there will already be an Oreilly book available in June.

All in all Pycon was fantastic (I missed the controversial lightning talks), the crowd was great (and much bigger), and I got to meet a whole lot of fans..particularly Chris McAvoy who's probably the most old school - he was one of the very first Myghty users. Don't weep for Myghty though, if you use Pylons with Mako, they're both direct descendants.

SQLAlchemy 0.4 and Beyond Slides

Continuing the topic of slides, heres the slides from my Pycon '08 talk, SQLAlchemy 0.4 and Beyond.