Load virtualenvwrapper on demand

(updated: )

UPDATE 2012-06-01: Doug released virtualenvwrapper 3.4 with built-in support for lazy loading. It’s a lot better than the hack in this post!

Loading virtualenvwrapper.sh takes too long:

$ time source /usr/bin/virtualenvwrapper.sh
real       0m1.034s
user       0m0.625s
sys        0m0.161s

If you load virtualenvwrapper.sh in your .bashrc, this means waiting an extra second every time you open a terminal. But you can avoid the delay by loading the functions on demand! The trick is to create stub functions workon, deactivate and mkvirtualenv that will load virtualenvwrapper.sh and then run the real function.

To make this work, save the following snippet as .bashrc.virtualenvwrapper in your home directory:

# Dynamically load virtualenvwrapper functions to reduce shell startup
# time.
#
# Copyright 2012 Aron Griffis <aron@arongriffis.com>
# Released under the GNU GPL v3
#######################################################################

# Python virtualenvwrapper loads really slowly, so load it on demand.
if [[ $(type -t workon) != function ]]; then
  virtualenv_funcs=( workon deactivate mkvirtualenv )

  load_virtualenv() {
    # If these already exist, then virtualenvwrapper won't override them.
    unset -f "${virtualenv_funcs[@]}"

    # virtualenvwrapper doesn't load if PYTHONPATH is set, because the
    # virtualenv python doesn't have the right modules.
    declare _pp="$PYTHONPATH"
    unset PYTHONPATH

    # Attempt to load virtualenvwrapper from its many possible sources...
    _try_source() { [[ -f $1 ]] || return; source "$1"; return 0; }
    _try_source /usr/local/bin/virtualenvwrapper.sh || \
    _try_source /etc/bash_completion.d/virtualenvwrapper || \
    _try_source /usr/bin/virtualenvwrapper.sh
    declare status=$?
    unset -f _try_source

    # Restore PYTHONPATH
    [[ -n $_pp ]] && export PYTHONPATH="$_pp"

    # Did loading work?
    if [[ $status != 0 || $(type -t "$1") != function ]]; then
      echo "Error loading virtualenvwrapper, sorry" >&2
      return $status
    fi

    # Chain-load the appropriate function
    "$@"
  }

  for v in "${virtualenv_funcs[@]}"; do
    eval "$v() { load_virtualenv $v \"\$@\"; }"
  done
fi

Then replace source virtualenvwrapper.sh in your .bashrc with:

source ~/.bashrc.virtualenvwrapper

This loads in about 1/100 of a second, and the real virtualenvwrapper.sh will load when you first invoke workon or mkvirtualenv.

Originally posted on the n01se blog.