lonesnake is a zero-config Bash tool that generates self-contained Python environments with a single command. Each environment fits in a single directory, including a CPython interpreter built from source and a venv. When a capricious environment breaks, you can just delete the directory and generate a new one easily.
It enables you to generate isolated environments not only for projects, but also for global environments or Docker images. It integrates seamlessly with IDEs (VS Code, PyCharm) and dependency management tools (Poetry, pip-tools). It does not impose shell init scripts, so you can activate environments with the method of your choice.
What are the limitations of lonesnake?
- accepts only a single interpreter version per project and per global environment
- supports CPython 3.7+ but not older CPython versions nor alternative interpreters like Pypy
- runs on macOS and Linux, but not Windows
- consumes more disk space than tools that store interpreters in a centralized location
I designed lonesnake to be much easier to understand for the average developer than the centralized tools out there. I deliberately renounced many features to keep the code structure simple. But if lonesnake is too basic for you, feel free to adopt the well-established pyenv or asdf.
- Step 1:
brew tap pwalch/lonesnake - Step 2:
brew install lonesnake
Step 1: install the CPython build dependencies and curl, depending on your OS
# The instructions below are taken from the pyenv Wiki and the python.org dev guide.
# Please check them out if you need more details or if you are using a different OS.
# https://github.com/pyenv/pyenv/wiki#suggested-build-environment
# https://devguide.python.org/setup/#install-dependencies
# Ubuntu/Debian/Mint
sudo apt-get update && sudo apt-get install -y \
make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
libsqlite3-dev wget curl llvm libncursesw5-dev xz-utils tk-dev \
libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
# Fedora
sudo dnf install \
curl make gcc zlib-devel bzip2 bzip2-devel readline-devel \
sqlite sqlite-devel openssl-devel tk-devel libffi-devel xz-devel
# Arch Linux
sudo pacman -S --needed curl base-devel openssl zlib xzStep 2: download lonesnake to ~/.local/bin
mkdir -p ~/.local/bin && \
curl -sL -o ~/.local/bin/lonesnake https://raw.githubusercontent.com/pwalch/lonesnake/1.0.0/lonesnake && \
chmod u+x ~/.local/bin/lonesnake- make sure you have
export PATH="$HOME/.local/bin:$PATH"in your.bashrc(Bash) or.zshrc(ZSH), and open a new shell - check that the script is accessible with
lonesnake --help
example commands
lonesnake- generates an environment with the latest CPython version, in the
.lonesnakedirectory at the root of the current working directory
- generates an environment with the latest CPython version, in the
lonesnake --py 3.13- same, but with the latest patch of CPython 3.13
lonesnake --py 3.13.0- same, but with exactly CPython 3.13.0
If the .lonesnake directory already exists, lonesnake asks for confirmation before deleting it.
To activate a lonesnake environment when entering a project directory, you can bring your own shell auto-load tool. If you are undecided, I recommend direnv.
install direnv and register its hook
# macOS
brew install direnv
# Ubuntu/Debian/Mint
sudo apt-get install direnv
# Fedora
sudo dnf install direnv
# Archlinux
sudo pacman -S direnv- Bash: in your
~/.bashrc, appendeval "$(direnv hook bash)" - ZSH: in your
~/.zshrc, appendeval "$(direnv hook zsh)"
generate the project lonesnake environment and enable auto-activation
- start a new shell then
cd YOUR_PROJECT lonesnake- touch
.envrcthen fill it with this code
# lonesnake auto-activation for the project directory
lonesnake_dir="${PWD}/.lonesnake"
PATH_add "${lonesnake_dir}/venv/bin"
export VIRTUAL_ENV="${lonesnake_dir}/venv"
# Solve errors involving "Python.h not found" when building
# Python extensions with a lonesnake environment.
parent_include_dir="${lonesnake_dir}/interpreter/usr/local/include"
if [[ -d "$parent_include_dir" ]]; then
include_dir_name=$(find "$parent_include_dir" \
-mindepth 1 -maxdepth 1 -type d -name "python3.*" \
-exec basename {} \;)
path_add CPATH "${parent_include_dir}/${include_dir_name}"
fidirenv allow- check that
which pythonprints your project's.lonesnake/venvdirectory
ℹ️ In case of trouble, you can get rid of the lonesnake environment by running
rm -rf .lonesnake .envrcat the root of your project. Make sure to open a new shell for the change to take effect.
Tips
If you have lonesnake-kit, you can use lonesnake-kit project --direnv to automatically populate .envrc.
If you find yourself pasting into .envrc files often, automate it with this function for your ~/.bashrc or ~/.zshrc.
# Print direnv activation instructions for lonesnake
# Usage: lonesnake-print-activation >> .envrc
function lonesnake-print-activation() {
cat << EOM
# lonesnake auto-activation for the project directory
lonesnake_dir="\${PWD}/.lonesnake"
PATH_add "\${lonesnake_dir}/venv/bin"
export VIRTUAL_ENV="\${lonesnake_dir}/venv"
# Solve errors involving "Python.h not found" when building
# Python extensions with a lonesnake environment.
parent_include_dir="\${lonesnake_dir}/interpreter/usr/local/include"
if [[ -d "\$parent_include_dir" ]]; then
include_dir_name=\$(find "\$parent_include_dir" \
-mindepth 1 -maxdepth 1 -type d -name "python3.*" \
-exec basename {} \;)
path_add CPATH "\${parent_include_dir}/\${include_dir_name}"
fi
EOM
}To show a prefix in your shell prompt indicating an active lonesnake venv (e.g. 🐍venv-3.13.0), take inspiration from this example code for your ~/.bashrc or ~/.zshrc.
show_lonesnake_venv_prefix () {
local cpython_path="$PWD/.lonesnake/venv/bin/python"
# If the venv is activated, print the prompt prefix
if [[ -x "$cpython_path" ]] && \
[[ "$(which python)" == "$cpython_path" ]]; then
local cpython_version="$(python --version | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')"
echo "🐍venv-${cpython_version} "
fi
}
PS1='$(show_lonesnake_venv_prefix)'"$PS1"Provided you have configured direnv as in the previous section, dependency management tools integrate seamlessly into lonesnake venvs.
pip-tools
pip-tools' sync command installs packages in the current venv. Therefore, all packages are installed in the .lonesnake venv directory by default:
cd YOUR_PROJECTpip install pip-toolspip-sync PINNED_COMPILED_REQUIREMENTS
Poetry
You can integrate Poetry into the .lonesnake directory by specifying the POETRY_VIRTUALENVS_PATH environment variable:
cd YOUR_PROJECT(where yourpyproject.tomlis)- append the following to your
.envrc:
export POETRY_VIRTUALENVS_PATH="${PWD}/.lonesnake/poetry_virtualenvs"direnv allowpip install poetrypoetry install- check with
poetry debugthat the "Virtualenv Path" is in a child directory of.lonesnake/poetry_virtualenvs
Tips
If you have lonesnake-kit, note that lonesnake-kit project --direnv populates the Poetry environment variables in .envrc.
ℹ️ In case of trouble, you can get rid of the Poetry virtualenvs using
rm -rf .lonesnake/poetry_virtualenvs. Make sure to open a new shell for the change to take effect.
Visual Studio Code (VS Code)
Auto-Activation:
- open a project directory that contains a lonesnake environment at its root
- click
File > Preferences > Settingsand then go toWorkspaceand search forpython.defaultInterpreterPath - set this path to
${workspaceFolder}/.lonesnake/venv/bin/python - press
CMD/CTRL + SHIFT + Por clickView > Command Palette, then choosePython: Select Interpreter - choose
Use Python from `python.defaultInterpreterPath` setting- note that after the word
setting, you should see./.lonesnake/venv/bin/python
- note that after the word
- when you open the integrated terminal, VS Code should now be sourcing
.lonesnake/venv/bin/activate
File Exclusion:
- click
File > Preferences > Settingsand then go toUserand search forfiles.exclude - click
Add Patternand register**/.lonesnake
If you have lonesnake-kit, note that lonesnake-kit project --vscode populates the settings.json of the workspace in the working directory.
PyCharm
- open a project directory that contains a lonesnake environment at its root
- click
File > Settings > Project: YOUR_PROJECT > Python Interpreter - click
Add Interpreter > Add Local Interpreter... - in
Virtualenv Environment- set
EnvironmenttoExisting - as
Interpreter, pick.lonesnake/venv/bin/pythonfrom your project - click
OK
- set
- click
OK, then wait for the environment to be indexed - when you create a new
Run/Debugconfiguration, thePython interpreterfield should point to the lonesnake environment
To activate a lonesnake environment user-wide when opening a new shell, you can generate one at the root of your HOME and register it in your .bashrc or .zshrc.
cd ~lonesnake- in your
.bashrc(Bash) or.zshrc(ZSH), append the following:
# global lonesnake auto-activation
export PATH="${HOME}/.lonesnake/venv/bin:${PATH}"- exit your shell and start a new one
- check that
which pythonpoints to your lonesnake environment
Tips
ℹ️ In case of trouble, you can get rid of the lonesnake environment by removing the export statements from your
.bashrcor.zshrcand runningrm -rf ~/.lonesnake. Make sure to open a new shell for the change to take effect.
Pip safeguard shim against accidental pollution of global environment
Sometimes, you will forget to create a project-specific lonesnake environment or to configure its auto-activation. In this case, all pip install commands you want to run will be forwarded to the global environment and pollute its package list.
To safeguard against this pollution, you can intercept pip commands by adding the following shim to your ~/.bashrc or ~/.zshrc:
# Safeguard shim against accidental 'pip install' to
# the global lonesnake environment.
# Call '~/.lonesnake/venv/bin/pip' to bypass.
function pip () {
if [[ -z "$VIRTUAL_ENV" ]]; then
echo "[ERROR] Cannot run 'pip' command outside" \
"of a VIRTUAL_ENV."
return 1
fi
local active_pip=""
if ! active_pip="$(whence -p pip)"; then
echo "[ERROR] There is no 'pip' command in PATH:" \
"${PATH}"
return 1
fi
local global_pip=""
global_pip="${HOME}/.lonesnake/venv/bin/pip"
if [[ -f "$global_pip" ]] && \
[[ "$active_pip" == "$global_pip" ]]; then
echo "[ERROR] Cannot run 'pip' command with global" \
"environment: ${global_pip}"
return 1
fi
command pip "$@"
}pipx support
After setting up a global lonesnake environment, you should install pipx to manage Python command-line tools. In the same spirit as lonesnake, pipx installs all tools in isolated venvs so they don't break each other or interfere with the global one. To integrate pipx:
- append these lines to your
.bashrcor.zshrc:
# By default, pipx stores its files in "~/.local/pipx" and "~/.local/bin", but we
# configure it to use sub-directories of the lonesnake global environment:
# "~/.lonesnake/pipx_bin" and "~/.lonesnake/pipx_home". Thanks to this,
# we keep everything related to the global environment in the same place.
export PIPX_HOME="${HOME}/.lonesnake/pipx_home"
export PIPX_BIN_DIR="${HOME}/.lonesnake/pipx_bin"
export PATH="${PIPX_BIN_DIR}:${PATH}"- exit your shell and start a new one
~/.lonesnake/venv/bin/pip install pipx- from now on, use
pipx installto install Python CLI tools such ashttpie
Tips
ℹ️ In case of trouble, you can get rid of your pipx installation by running
rm -rf ~/.lonesnake/pipx_*and~/.lonesnake/venv/bin/pip uninstall pipx. Make sure to open a new shell for the change to take effect.
If you use direnv, Poetry, VS Code or some other popular tools, you might find lonesnake-kit useful. This program creates a new lonesnake environment and then automatically populates .envrc, .vscode/settings.json, etc... to configure your projects faster.
Before using lonesnake-kit, make sure to read the instructions for vanilla lonesnake. Once you are familiar with it, check out available integrations with lonesnake-kit --help.
mkdir -p ~/.local/bin && \
curl -sL -o ~/.local/bin/lonesnake-kit https://raw.githubusercontent.com/pwalch/lonesnake/1.0.0/helpers/lonesnake-kit && \
chmod u+x ~/.local/bin/lonesnake-kit- make sure you have
export PATH="$HOME/.local/bin:$PATH"in your.bashrc(Bash) or.zshrc(ZSH), and open a new shell - check that the script is accessible with
lonesnake-kit --help
This .lonesnake directory includes a Python interpreter built from source, as well as a venv:
interpreterdirectory includesusr/local/bin,usr/local/include, etc...venvdirectory includesbin,include,pyvenv.cfgetc... It is created by the interpreter above.
Behind the scenes, lonesnake takes advantage of cache directories for the CPython source code and build files, located at ~/.cache/lonesnake/X.Y.Z/ where X.Y.Z is the Python version (e.g. 3.13.0). Cache directories enable us to skip the compilation step when CPython was already compiled for the requested version.
To construct the .lonesnake directory, lonesnake follows the standard CPython build process, but with extra sanity checks:
- before CPython build
- check if there is Internet access to python.org, otherwise you cannot download anything
- during CPython build
- error out if Python could not be compiled with OpenSSL, as it would make Pip unusable
- on macOS, use Brew's OpenSSL and LZMA libraries instead of system ones for consistency
- after generating venv
- upgrade Pip to latest version
- install setuptools + wheel as they are pre-requisites for many other packages