Moving from pyenv-virtualenv to uv
Published 2 July, 2025.
For a long time now, I’ve had a nice thing going with pyenv-virtualenv. I’ve been using a script like the following to get a virtual environment up and running:
pyenv install --skip-existing 3.13.5
pyenv virtualenv 3.13.5 my-cool-proj
echo "my-cool-proj" > .python-version
This causes pyenv to use its shim magic to switch to the my-cool-proj
virtual environment when I cd
into my project’s directories.
Unfortunately, this conflicts with an emerging community standard where tools are expecting .python-version
to contain the version of Python this project makes use of. Heroku, for example, have marked their runtime.txt
file as deprecated and are now using .python-version
to determine which version of Python to use.
After a short period of moaning to myself about this, I’m now switching over to a workflow using uv and a zsh function which seems to be working almost as well as the above.
Once uv is installed, creating a virtual environment with it is simple.
❯ uv venv --python 3.13.5
Using CPython 3.13.5 interpreter at: /opt/homebrew/opt/python@3.13/bin/python3.13
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
This creates a virtual environment in a .venv
directory inside the project. Activate it like any other virtual environment:
❯ source .venv/bin/activate
This is fine, but I was missing having my virtual environment automatically activate. There are lot of solutions out there for to solve this problem. I found that adding this to my .zshrc
file to be the most elegant and least intrusive (your mileage may vary):
# Adapted from https://andreamaglie.com/automatically-activate-python-virtual-environments-with-zsh
activate_venv() {
local venv_dir=".venv"
if [[ -d "$PWD/$venv_dir" ]]; then
source "$PWD/$venv_dir/bin/activate"
fi
}
autoload -U add-zsh-hook
add-zsh-hook chpwd activate_venv
activate_venv
Warnings when creating virtual environment
On most of the projects I work on, running uv venv
for the first time triggered a warning about a missing version field in pyproject.toml
. I’ve been working around this by adding the following to pyproject.toml
:
[project]
dynamic = ["version"]