And now for something completely different
Experiences using Pylons
Recently, I've had the opportunity to play with Pylons, a Python web framework that I understand is not too dissimilar to Ruby-on-Rails in functionality and philosophy. Pylons is built on a lot of cutting-edge Python web technologies, such as as Paste for general web server functionality, a variety of templating languages including Genshi (which rocks), and SQLAlchemy (my favourite Python library) for database integration.
This particular project needed to be delivered rapidly, and a relational database (in our case MySQL) was deemed the most appropriate data store. There are no content management requirements - rather, we will have there or four main screens allowing manipulation of data in the database, plus some rudimentary user management.
It's pretty simple to get started with Pylons. Once you've installed the Pylons egg, you can do:
>>> paster create -t pylons myproject
At this point, you will get a new egg, complete with a setup.py file, for your application, with the usual boilerplate.
A file called development.ini sets project options, such as the main database connection, whether or not to enable debugging features and so on. It also sets the port for the web server, which is wired in using WSGI magic. WSGI, if you're not familiar with it, is a pretty powerful means of chaining together Python web applications. Pylons uses it to give you an interactive Python debug prompt in the browser (a bit like Clouseau for Plone) when you get a traceback. Incredibly useful.
To run the application, you just do:
>>> paster serve --reload development.ini
The --reload bit ensures that if you change any Python code, your application is automatically restarted. A restart takes a second or two, making development pretty efficient.
Having the whole application in one egg is pretty nice. It means that you can deploy it as a standalone package. You don't need to install it (even in develop mode) during development, but to deploy, you'd just extract an archive of the package and do:
>>> python setup.py install
You can have a number of dependencies in setup.py, of course. There are also facilities to run bootstrap code (from a websetup.py module), e.g. to initialise your database or perform other configuration. For serious deployment, I imagine you'd use a caching solution such as Varnish or Squid, Apache and/of FastCGI in front of Pylons.
Working with the code
You are not supposed to re-generate the code in that you might do with, say, ArchGenXML in Plone. There are no protected code sections. You get a running application out of the box, but you're meant to modify it to suit your needs. This does potentially mean that migrating to future versions of the framework requires you to understand how things have changed in more detail, but in practice it works pretty well. The generated code is well commented and for the most part sensible. Some of the more ideosyncratic WSGI setup stuff I've chosen to ignore, but I'm sure it wouldn't be hard to learn how to tweak it, should I need to.
The main logic happens through controllers. A library called Routes is used to map URLs onto controllers, so you can have pretty customised URL schemes, but the defaults are quite sensible.
To create a new controller, Pylons uses a feature of Paste where you can have context-sensitive commands (where the "context" is an egg, basically). To create a new controller, you'd do:
>>> paster controller mycontroller
It then creates the necessary parts, in particular 'controllers/mycontroller.py'. This has a method 'index()' and may look like this:
from myapp.lib.base import * from myapp impotr model class MyController(BaseController): def index(self): c.some_variable = 'foo' return render("mytemplate")
To access this, the standard URL would be http://localhost:5000/mycontroller/index, or just http://localhost:5000/mycontroller. You can add new methods and they are accessible immediately under the URL (Routes maps the first part of the URL to a controller and the second part to a method on that controller).
You'll notice a few globals as well. These are imported from the generated lib.base module, and includes utility functions like render() (which renders a template from the templates/ folder using the current templating language), redirect() or abort() (which raises an HTTP exception). The 'c' variable is a global that is reset for each request, but is used to store arbitrary information that can then be fetched in a rendered template. A fairly common pattern is for a controller to perform some calculations (for example using the SQLAlchemy objects in the 'model' module), save the results in the 'c' variable and call a template, which then inspects the 'c' variable and renders the results. 'request' and 'response' are also global singletons, which hold request parameters and so on.
I initially felt a little uncomfortable with this use of globals, but it is very convenient, and controlled quite well because you can change how the 'lib.base' module manages them.
The good and the bad
Pylons has been pretty good to us. It's relatively easy to learn, and most things work as you'd expect. The documentation is lacking in some areas. For example, it's difficult to work out the best way of managing users and sessions (we've settled on using Authkit). There was also a bit of a wobble around how best to use SQLAlchemy - we're still on SQLAlchemy 0.3 and the SAContext integration module; that seems to be dead in favour of using SQLAlchemy 0.4 (currently in beta) instead. Some of the documentation has been inconsistent or changing around this, though things seem to be settling down both in Pylons-land and SQLAlchemy-land.
I'm not convinced I'd use Pylons to build a very large and complex application - at least not until I'd had more experience and there was more "end-to-end" documentation, but most projects are not very large or very complex. I'm pretty sure that I'll go back to Pylons the next time I need to build an RDBMS-driven simple (non-CMS) web application... unless I specifically want the Zope way of working, in which case I'd probably go for Grok.