Python virtual environments with asdf, venv and direnv

How to use common tooling to manage your Python virtual environments

Every language and its community seems to have its own story (and at times drama) on how to handle dependencies. We even have a conference just to talk about it. In Python, isolating dependencies and interpreter versions (together known as a "virtual environment") is notorious for tripping up beginners just getting started. The Python community understands this but is still deciding on a solution. Since Python is not my main daily language, I tend to forget about virtual environments for months at a time. This post is mostly for me to remember how I do it each time, but it's simple enough and uses familiar tooling that I hope it helps someone else.

In Python 3.3, the venv module got added natively to the default Python interpreter. This is the main workhorse between virtual environments and by learning a little about how it works, we can have a simple workflow to automatically create virtual environments when we cd into a project directory. The three main tools involved are asdf, venv (the module), and direnv.

Overview on each tool

asdf is a generic version manager for almost all of the popular language runtimes. Alongside python, I use it to manage my golang, nodejs and perl installations. One package manager to rule them all.

direnv allows you to change your shell's environment based on which current directory your shell session lives in. There are some neat features tucked into this tool so I suggest you dive into their documentation.

As you soon will see, you use the venv module to actually create the virtual environment. Once that happens, you'll have access to executables that can be used to activate these virtual environments. Since all .venv/bin/activate does is change the shell's environment variables, we skip right over using it and change the environment the same way with direnv.

My simple workflow

Change into (or create) your project directory:

$ cd /the/path/to/your/project

Start by creating the virtual environment:

$ python -m venv .venv # Directory name can be anything

Then create an .envrc (what direnv looks for) to set relevant PATH vars for the right executables to be picked up:

$ echo "export VIRTUAL_ENV=$PWD/.venv\nexport PATH=$PWD/.venv/bin:\$PATH" > .envrc

I'm pretty sure VIRTUAL_ENV isn't necessary to be set but since explicit activation does it, there's no harm. Direnv should then warn you that these new environment variables are going to be loaded. To continue,

$ direnv allow

That should be it! To confirm that python3 and pip are pointing to the right places:

$ which python3
/the/path/to/your/project/.venv/bin/python3
$ which pip
/the/path/to/your/project/.venv/bin/pip

You should have now a virtual environment activated each time to change into this directory from your shell!

$ cd /the/path/to/your/project
direnv: loading ~/path/to/your/project/.envrc
direnv: export +VIRTUAL_ENV ~PATH
$ pip install -r requirements.txt # Uses virtual environment

Written by