In response to @ericholscher and his recent blog post "Virtualenv Tips", I've decided to describe the way I use virtualenv in the hopes that someone might find it useful. Before you even bother reading this, though, you should install virtualenvwrapper, as most of my customizations are built into the various extra hooks it provides.

The first tip more a matter of philosophy. While virtualenv is designed to keep your python libraries separated, I tend to look at virtualenv as a way of separating entire workflows. When I deactivate, a mental deactivation accompanies it. Let me explain a few of the customizations I make before getting back to why that happens.

With that established, the first thing I do is change the virtuanelvwrapper-wide postdeactivate to change into my home directory. When making a new virtualenv, I change the postactivate script to change into the project directory.

I also keep a set of mnemonic bash aliases that I use from project to project, but behave differently based on settings in my postactivate files. For django projects, this usually implies:


cd ~/devel/project_name
export PYTHONPATH=`pwd`
export DJANGO_SETTINGS_MODULE='settings'

alias rs="python manage.py runserver 0.0.0.0:8000"
alias ds="python manage.py runserver_plus 0.0.0.0:8000"
alias dsh="python manage.py shell"
alias dbsh="python manage.py dbshell"
alias dt="python manage.py test'

These aliases normally live in my bash_aliases, but sometimes different projects have different layouts, and I customize them here. Regardless, source ~/.bash_aliases goes into my postdeactivate to restore my "normal" environment.

I also configure some of my other programs to obey customizations provided by virtualenvs. In my .vimrc, I use the env variable supplied by virtualenv to change the indentation styles for different filetypes depending on the projects I'm working on:

" when hacking on (project), save html w/ tabs, not spaces
if $VIRTUAL_ENV ~= "(project_name)"
    autocmd BufNewFile,BufRead *.jinja setlocal noexpandtab
    autocmd BufNewFile,BufRead *.jinja setlocal ts=2
    autocmd BufNewFile,BufRead *.jinja setlocal shiftwidth=2
    autocmd BufNewFile,BufRead *.jinja setlocal wm=2
    " ...
endif

I also have altered my base ~/.ipython/ipy_user_conf.py to include this code to import per-virtualenv customizations:

import IPython.ipapi, os
ip = IPython.ipapi.get()

def virtualenv():
    return os.environ.get('VIRTUAL_ENV', None)

def main():
    execf('~/.ipython/virtualenv.py')
    ve = virtualenv()
    if ve:
        path = os.path.join(ve, 'ipy_user_conf.py')
        if os.path.isfile(path):
            execf(path)

def execf(path):
    ip.ex('execfile("%s")' % os.path.realpath(path))

This will execute $VIRTUAL_ENV/ipy_user_conf.py within the context of the new ipython interpreter, so I can go in there and add a bunch of import statements or shortcut functions. I also use (via the execf function above) a virtualenv.py function which sets up my system-wide ipython with the virtualenv site settings.

Finally, in my postactivate, I sometimes set which version control system I'm using on that project (since I swap between svn, hg, and git), but use aliases defined elsewhere to do some generic things quickly, like alias vd="$VCS diff |gvim -".

A lot of these things could be streamlined or further automated. When I allow my different environments to be tailored to the projects at hand, I've found over time that workon and deactivate leave me in different mental states. After I've deactivated, I'm left with an environment that is almost unsuitable for development, and when I've activated a virtualenv, I'm instantly in the same environment I was when I was an hour and a half into a tough problem.

Nov 3 2010