With my recent release of a new version of this site, I finally made the jump from using mod_python to using mod_wsgi. This change had been a long time coming, but was made slightly less than straight forward by my desire to deploy to virtualenv environments.

This desire is borne of convenience and necessity. One of the major problems with any web deployment that is on some kind of shared environment is managing the versions of dependent software. If you want the underlying system to provide a rich set of languages and libraries, eventually the needs of a particular application will no longer match the state of the system: a new app will need a newer version of something, or an old app depend on an older version of something. The other option is to sandbox every app entirely, which means lots of duplication and configuration management.

With my blog, not only had the Django version changed, but I also developed for newer versions of many utilities like python-markdown, pygments, and lxml. Creating a virtualenv to throw these dependencies in made debugging that environment a snap, and allowed me to set up a staging and deployment environment for those new packages without having to manually create an entire shadow python installation.

Virtualenv and mod_wsgi's documentation on how to deploy using virtual environments are at odds, but at the time of this writing mod_wsgi's is essentially correct. My "django.wsgi" application looks something like this:

import sys
import site
import os

STAGE=False

if STAGE:
    vepath = '/path/to/virtualenvs/jmoiron.net-stage/lib/python2.5/site-packages'
else:
    vepath = '/path/to/virtualenvs/jmoiron.net/lib/python2.5/site-packages'

prev_sys_path = list(sys.path)
# add the site-packages of our virtualenv as a site dir
site.addsitedir(vepath)
# add the app's directory to the PYTHONPATH
sys.path.append('/path/to/app/%s/' % ('stage' if STAGE else 'live')

# reorder sys.path so new directories from the addsitedir show up first
new_sys_path = [p for p in sys.path if p not in prev_sys_path]
for item in new_sys_path:
    sys.path.remove(item)
sys.path[:0] = new_sys_path

# import from down here to pull in possible virtualenv django install
from django.core.handlers.wsgi import WSGIHandler
os.environ['DJANGO_SETTINGS_MODULE'] = 'web.settings'
application = WSGIHandler()

The mod_wsgi documentation on deploying via virtualenv was heavily pylons biased, but the beauty of wsgi is that it generally works with any wsgi application.

The second part to migrating to use mod_wsgi instead of mod_python was a bit trickier since it lies outside my area of expertise: the apache configuration. Unfortunately, I run a lot of things off of my www domain other than this django app, but the django application owns '/'. In the past, this meant a lot of <Location> blocks with SetHandler None in them, but since mod_wsgi handles alias stacking properly and the SetHandler hack doesn't work with it, I had to translate that over. The important points are:

  • 'SetHandler None' has to be converted to an Alias, which has to map to a location in the filesystem and thus can't always do the same things in the same way the SetHandler hack does.
  • Your WSGIScriptAlias should come after your other aliases so that they have a chance to match before your application eats '/'

Here's roughly the config I've ended up with:

<VirtualHost *>
    ServerName jmoiron.net
    # ServerAlias and what-not
    
    WSGIDaemonProcess live user=wsgiuser group=users threads=25
    WSGIProcessGroup live 

    Alias /media/ /path/to/app/media/
    # allow jmoiron.net/~user/ to still map to user's public_html
    AliasMatch /~([^/]+)/?(.*) /home/$1/public_html/$2
    
    <Directory /home/jmoiron/public_html>
        # custom stuff i should put in an .htaccess
    </Directory>

    # more Alias config for cgi-bin, et al

    WSGIScriptAlias / /path/to/app/apache/django.wsgi

    <Directory /path/to/app/apache/>
        Order deny,allow
        Allow from all
    </Directory>

    # boring logging stuff
</VirtualHost>

Development and staging is now a snap, as switching my staging setup from "testing new software dependencies" to "testing new code" is as easy as switching the virtualenv I'm using in the wsgi file and letting mod_wsgi reload the application!

May 27 2009