Python Deployment Chronicles: Deploying legacy TurboGears projects with modern tools
At CCNMTL most of our new Python projects are written in Django, but we still support a number of older projects that were written with TurboGears 1.0.4. They've continued to be stable, and we don't do a ton of new development on them, so it hasn't been worthwhile to upgrade them to newer versions of TurboGears.
But we do occasionally make changes to their code, and recently we've begun migrating them to newer servers. So I recently spent some time updating their deployment processes to CCNMTL's current best practices:
- Installation with pip instead of easy_install
- Fully pinned local source distributions versioned alongside the code
- No Internet access required anywhere in the deployment
- Containment with virtualenv
Like our Django bootstrap process, this is a one-step installation procedure for creating an isolated TurboGears 1.0.4 environment. Just run bootstrap.py
and you'll have a virtualenv in ./ve
, with all the packages you need to run or create a TurboGears 1.0.4 project.
(If you're curious what I'm upgrading this from, Anders has a post describing our historical TurboGears bootstrap process.)
Switching from easy_install to pip
From Eggs to Tarballs
easy_install
to install the packages we needed. Switching to pip is usually straightforward, but we'd been bundling all our dependencies as eggs instead of source tarballs, and pip doesn't support installation from eggs..tar.gz
file from PyPI that corresponded to the exact version of the package we were installing from an egg. Some of them were hard to find or weren't on PyPI; http://files.turbogears.org/eggs/ and http://peak.telecommunity.com/snapshots/ were helpful here too, and for one or two particularly stubborn dependencies I just had to get the right version from SVN and package it in a tarball myself.The Requirements File
- The order that you list the requirements matters -- if package A depends on package B, you have to list A before B in your requirements file. Otherwise pip might end up downloading a copy of B from the Internet before it even sees that you've specified a local source distribution. We want to prevent deployments from requiring Internet access (and relying on PyPI uptime) -- and, most likely, you'll end up with the wrong version of package B installed.
- We use a couple of TurboGears plugins, and those plugins try to
import turbogears
in theirsetup.py
files -- meaning that you can't install them until after TurboGears itself is fully installed.
requirements.txt
, and repeat until it all worked right. To make it a little easier, I passed an --index-url=''
option to pip, which made it fail early and loudly the first time it tried to download anything. (I blogged about this trick for preventing pip network access a few weeks ago.)setup.py egg_info
on all packages in your requirements file before it runs setup.py install
on any of them -- so when it got to the packages that tried to import turbogears
in their setup.py
, it failed with an ImportError
. So I made a second requirements file, and I ran pip install
a second time to install these packages after TurboGears and its dependencies were all fully installed.Pinning Setuptools
pkg_resources
.