When you need to install something directly from PyPI, Python virtualenvs have been my go-to for over a decade.
A quick virtualenv intro
Most of my readers are probably already familiar with virtualenvs, but for completeness, I'll give you a brief introduction. A virtualenv (short for "virtual environment") is an isolated distribution of Python packages, where you can independently install packages without disturbing your system packages or other virtualenvs.
You can set one up like this, assuming you are using Python 3.3 or higher:
python3 -m venv ~/.venv/my-virtualenv
The directory specified here is just a convention. I keep all my virtualenvs in
the .venv folder in my home directory, but you can pick whatever location you
like.
To use the virtualenv, you must activate it:
source ~/.venv/my-virtualenv/bin/activate
This activation script is a special shell script that configures your current
shell, pointing at all the right paths in order to use the virtual environment.
source runs this script in your current shell session to set it
up. You will notice that this adds (my-virtualenv) to the beginning of your
shell prompt, reminding you that the "my-virtualenv" virtualenv is active. Now
when you pip install amazing-package, the software will only be available in
this virtual environment.
When you're done, you can deactivate it like so:
deactivate
Wonderful!
Managing many virtualenvs gets annoying
Over time, I end up accumulating many virtualenvs, which can become harder to manage. Maybe something like this:
$ ls ~/.venv/
my-virtualenv cool-project snakes-ahoy
I also don't want to type source ~/.venv/my-virtualenv/bin/activate every
time I use the virtualenv, because it gets very repetitive—only the name
of the venv is really needed.
But luckily, we can write a little bit of bash to make managing this less annoying. (Or you can use one of many Python developer tools that are designed to manage this, like pipx, but when I merely want to consume Python software, I might not have a development environment set up. So that's beyond the scope of this post!)
If you add the following shell function to your ~/.bashrc or
~/.bash_aliases file, it will nicely wrap our activation command:
setup-venv() {
source "$HOME/.venv/$1/bin/activate"
}
Now all we need to run is
setup-venv my-virtualenv
So much quicker!
Spicing it up with tab completion
The first thing I noticed after writing this wrapper was that I started hitting tab on the virtual environment name, but... nothing happened. Wouldn't it be nice to know what virtualenvs I had available, and to not have to type out the whole long thing?
Well, we can write it ourselves 😄
If for some reason you don't already have bash completion installed, on a Debian-based system, you will need to install it with
apt install bash-completion
In order to configure our bash completion, we will create a new file,
/etc/bash_completion.d/venv, with the following contents:
_list_venvs()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$(find $HOME/.venv/ -mindepth 1 -maxdepth 1 -type d -printf "%f ")
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _list_venvs setup-venv
This file defines another shell function order to determine how to
autocomplete the options for our setup-venv function.
$opts is where we define the options for our function. We generate it with a
find command—looking at the .venv folder in the current user's home
directory, then only including child folders (excluding the current directory
itself, .venv, in our results) by using the min/max depth and type arguments,
and printing just the individual directory names, deliminated by spaces using
our print formatter.
Everything else is the standard scaffolding required to use bash completions.
Once you save this file and reload your shell, you'll see that you are able to use completions as expected!
setup-venv <tab>
my-virtualenv cool-project snakes-ahoy
setup-venv s<tab>
setup-venv snakes-ahoy
Complaints, comments, questions?
Hope this was helpful! If it wasn't, that's too bad. But don't worry—you can safely ignore this post.