Yet Another Python Web Framework


I know it's in bad taste to always reinvent the wheel, but you know what can I say. Initially when we began to work on EventMagnet, we were using Django. I found the opinionated nature of Django to be both limiting and not really in line with my opinions, so we moved on. I had some experience with TurboGears from working on TurboX2, and was not really fond of CherryPy style URL matching or SQLObject. So I decided to go it alone and put together my own framework for doing all this stuff.

When I first started writing the framework it was implemented as a CGI application using Routes to do URL matching. I really do not like regular expressions, so Django style URL matching was out, but I also didn't like the object style matching of CherryPy. Routes provided a system with a URL matching mechanism which was to my taste, although a bit weird. Routes weirdness comes from it being a direct clone of its Rails roots. The use of Ruby symbols in the URL seems like just funny syntax to a Python coder, and something fairly natural to a Rubyist. Funny syntax or not Routes is quite powerful. Just today I started to use the conditions feature of the URL Mapper. My biggest problem with Routes is really that the documentation for integrating it with a framework isn't that great or in depth. For example one needs to set the environ on the Mapper object to be able to use conditions but this isn't documented on the instructions for framework integrators page or in the Routes manual. Instead I had to dig through Routes code. Luckily it's very well commented and straight forward.

After I had URL matching sorted I had to deal with a way to do templates. As easy as it might be, using print to generate pages is never a good idea. :) I've gone through phases on what sort of templating system I like, I learned quickly while working on TurboX2 that I did not like kid, and by extension templating control embedded into XML style templating systems, at all. In a sort of reaction to that style of templating I first tried things like Django templates (which Guido has expressed love for) but I found myself annoyed that we were embedding code in the XML files. It was sort of the opposite of the problem with printing the XML ourselves. Instead of putting XML into our python we were putting a Python like language into our XML. I decided that this just wouldn't do, and went looking for another option.

So I came across PyMeld. Meld made the separation that I was looking for. Inserting our dynamic content was done directly in my Python code, without any XML that we might need to change later. This lets us localize anything aesthetic/structural to our XML templates and localize anything having to do with gathering and inserting our content in the code. Everyone has been happy with the position of Meld, but we've had some issues with its actual implementation. Many of the things that I've really hated I've fixed. For example in pure Meld you might do something like:

base = Meld("<a id='blah' href='url'><div id='href'>..</div></a>")
base.blah.href = 'http://127.0.0.1/'
base.blah = 'a different name'

I immediately saw this as a problem so I separated these two functionalities. I implemented structural access via __getattr__ and attribute (in the XML sense) access via __getitem__. I also renamed all the functions to be in snake_case, since javaCase makes my eyes hurt. Still Meld has some problems that are starting to drive me crazy. In particular anything you want to modify has to have an id attribute. Things like the title tag aren't supposed to have an id attribute, so this causes some problems. In light of that I've started to develop a new templating system with the same intentions as Meld which would also provide a DOM-like API. The DOM-like API will make it easier to access things without an id. I also intend to provide ways to generate new Elements similar to how MochiKit does it. I try to avoid doing Element generation in the code at all, but in some cases it's not possible to avoid that, so I would like to have a clean API for generating those elements instead of raw XML inlined. The DOM-like API is more or less complete, but nothing has been built up from there yet, and I had to set it aside to work on some other things.

I use SQLAlchemy for the database, I use Elixir because I'm lazy. In general my views on the state of database access in Python are that it sucks. In particular I hate the way querying is handled, as I've detailed here and here. To go further on that issue I'd like to say that I'd like transactions to be segregated into functions like they are using Blocks in ActiveRecord in Rails and like they are with Mnesia in Erlang. Of course it's not as easy with Python since it doesn't have multiline anonymous functions, and I believe I read that Guido had declared such a feature syntactically impossible. At least it's possible to do transactions with Python 2.5's with statement.

I did a lot of work today to clean up various ugly points in the framework. I added text for all of the errors so that an error page can be shown with appropriate descriptive text. I added a configuration file so I don't have to run through the source looking for references to the project I grabbed the copy of the framework from. I also made imports one level up possible so that I don't have to change every import line every time. I added the ability to return different types of data (JSON, XML, XHTML, etc) with a mix of Routes and Python magic. There are now Routes mappings for /xapi/:controller/:id, /api/:controller/:id, /:controller/:id, and /rss/:controller/:id. Each of these has a res_type variable (xml, json, html, and rss respectively). The __call__ method the Controller class then checks if res_type+'_'+action_name is a method of the class, and if it is calls it and if not returns a 404. So now I implement functions like json_list(), html_list(), and so on, and if I don't implement them there is just a 404. It works nicely and it makes my life a lot easier. I put all of the common code into a function called _action_name() and just call it from within the return_type functions.

On the JavaScript end we are also developing our own toolkit which is being built atop MochiKit. All the work is being done by Aaron, who has emphasized in his design not the creation of reusable widgets but instead reusable components that can be used to build widgets and interfaces. The main stay of Sparks is collection of DataTypes which are designed to be integrated into an interface and make extensive use of signaling, so operations like adding to a List emits a signal and the likes. This makes it much easier to produce rapidly changing interactive interfaces quickly since almost all of the event handling logic is already internal to Sparks and so it's just required to copy the objects and modify them as needed to get the desired behavior.

The days of CGI for the framework are incidentally over. It's now a WSGI application. I again opted to do it all on my own creating my own Request object and my own way of handling Responses. At this point I'll probably be changing the Response system to use an object instead of the more adhoc returning of a variable up to the top method that is currently in place, and proving to be bad. The Request object makes extensive use of property() to avoiding parsing information that we don't need (like cookies and various POST variable type stuff). Currently we handle input of either text/json or application/x-www-form-urlencoded, though the text/json support is a lot better since most of the API calls are designed around it. I will be using result type dispatching to provide application/x-www-form-urlencoded wrappers for the various API calls at some point.

There are both advantages and disadvantages to rolling your own framework, but it certainly teaches you a lot about how a web application request/response process works, and gives you a lot more control over your pieces. I personally really like to be able to control exactly what is used throughout the system, and so designing my own framework was very advantageous to me. On the other hand I spend a lot of time making it better instead of just accepting that it is limited, like I might have to do with an established framework, and working on my app instead. :)


published at Sun Apr 1 02:18:57 2007 (-0700) by alexbl
Tags: python, web2.0, www, framework, development
| |