Discovering Pyenv

Before learning Python, I had been working with Ruby over a period of 3 years having the opportunity to participate in a great Meetup Group as well my professional role as a Development Operations Engineer at LivingSocial.

I soon discovered Sam Stephenson’s GitHub project Rbenv.

Sam is an A+ contributor in my book and has developed other great projects such as the Bash unit test framework BATS.

Rbenv hooks into your interactive shell environment by manipulating PATH and using shims which are a set of shell scripts and functions which intercept the invocation of executables such as ruby gem, or whatever a package installs as an executable, (e.g. rake).

With these shims, Rbenv can conditionaly execute different versions of Ruby based on the shell environment or by consulting the file .ruby-version which can set this based on a current directory path.

This allows you to install many versions of Ruby and easily isolate the version to a specific project’s code base.

This is a great pattern (as well as utilizing Bundler) for limiting a majority of the side-effects encountered when developing or deploying multiple applications in the same execution environment.

I was perusing through a friend of mine’s Github account recently and stumbled upon a Python version of Rbenv entitled Pyenv from Yuu Yamashita.

Knowing the power of Rbenv I was excited to give this version a try.

Setting up a Build Environment

Since Pyenv builds many of it’s versions of python from source, its important to have a compiler as well as associated build tools and libraries installed previous to setting it up in your shell.

This article does not address the MacOS but you will likely need to install Xcode from Apple App Store (free) which is their compiler and associated tools.

I ran the following script on a Fedora 20 Linux desktop to install the tools and libraries needed for a basic build environment (your mileage may vary):

~/install-workstation-dev-env.sh (run as root or via sudo):

#!/usr/bin/env bash

cd /var/tmp

packages="gcc gcc-c++ tar binutils readline-devel sqlite-devel zlib-devel \
  openssl-devel expat-devel postgresql-devel mysql-devel texinfo curl git"

yum -y install ${packages} && yum clean all

yaml_url=http://pyyaml.org/download/libyaml/yaml-0.1.5.tar.gz
ffi_url=ftp://sourceware.org/pub/libffi/libffi-3.1.tar.gz

for url in ${yaml_url} ${ffi_url}; do
  extract_dir="$(basename $url .tar.gz)"
  curl -sL $url | tar zxf -
  cd ${extract_dir} && ./configure && make install
  cd .. && rm -r ${extract_dir}
done

Setting up Pyenv

Configure the shell

The official installation instructions are here however I wrote the following snippet which initiates an install if Pyenv does not exist.

In addition there is an optional cleanup function which strips any references from the environment to cleanly initialize new Pyenv settings or location.

Add to /.bash_profile

pyenv_cleanup() {
  export PATH=$(echo $PATH \
    | perl -p -e "s{(^|:)${PYENV_ROOT:-$HOME/.pyenv}[^:]*}{}g; s{^:}{};")
  for v in ${!PYENV_*}; do unset $v; done
  unset VIRTUAL_ENV
}

pyenv_init() {
  export PYENV_ROOT="${PYENV_ROOT:-$HOME/.pyenv}"
  local pyenv="git://github.com/yyuu/pyenv.git"
  local pyenv_virtualenv="https://github.com/yyuu/pyenv-virtualenv.git"

  [[ -d ${PYENV_ROOT} ]] || git clone ${pyenv} ${PYENV_ROOT}
  [[ -d ${PYENV_ROOT}/cache ]] || mkdir  ${PYENV_ROOT}/cache

  [[ -d ${PYENV_ROOT}/plugins/pyenv-virtualenv ]] \
    || git clone ${pyenv_virtualenv} ${PYENV_ROOT}/plugins/pyenv-virtualenv

  export PATH="${PYENV_ROOT}/bin:$PATH"
  eval "$(pyenv init -)"
  eval "$(pyenv virtualenv-init -)"
}

pyenv_init

Source the environment

user@sys:~$ source ~/.bash_profile

Install Python

Use pyenv install --list to list all versions available for install.

In this example we will install python 2.7.8.

user@sys:~$ pyenv install 2.7.8
  Downloading Python-2.7.8.tgz...
  -> https://yyuu.github.io/pythons/74d70b915....2f825f26cb2b93abd20fdda56b557
  Installing Python-2.7.8...
  Installing setuptools from https://bootstrap.pypa.io/ez_setup.py...
  Installing pip from https://bootstrap.pypa.io/get-pip.py...
  Installed Python-2.7.8 to /home/u/.pyenv/versions/2.7.8
  etc...

Enter the project directory and create a Virtualenv

user@sys:~$ mkdir -p ~/src/myproject && cd ~/src/myproject

Create a virtualenv by running pyenv <python_version> <virtualenv_name>.

In this example we use command line substitution to automatically set the virtualenv name to the project’s root directory.

user@sys:~/src/myproject$ pyenv virtualenv 2.7.8 $(basename $(pwd))
  New python executable in /home/u/.pyenv/versions/myproject/bin/python
  Installing setuptools, pip...done.
  Installing setuptools from https://bootstrap.pypa.io/ez_setup.py...
  Downloading https://pypi.python.org/packages/source/s/setuptools/setuptools-7.0.zip
  Extracting in /tmp/tmpTWQYyY
  Now working in /tmp/tmpTWQYyY/setuptools-7.0
  Installing Setuptools
  running install
  running bdist_egg
  running egg_info
  writing requirements to setuptools.egg-info/requires.txt
  etc...

To automatically set the virtualenv every time you enter the project directory, use pyenv local <virtualenv> to create a .python-version file.

user@sys:~/src/myproject$ pyenv local myproject
  pyenv-virtualenv: activate myproject

Here are some example command to run to see how virtualenv is set via a .python-version file and how it activates and de-activates as you navigate in and out of the project directory.

user@sys:~/src/myproject$ cat .python-version
  myproject

user@sys:~/src/myproject$ pyenv version
  myproject (set by /home/u/~/src/myproject/.python-version)

user@sys:~/src/myproject$ cd ~
  pyenv-virtualenv: deactivate myproject

user@sys:~$ cd -
  /home/u/~/src/myproject
  pyenv-virtualenv: activate myproject

Install any required packages

user@sys:~/src/myproject$ pip install <package...>

or:

user@sys:~/src/myproject$ pip install -r requirements.txt

 

Share this post