Switching from virtualenvwrapper to direnv, Starship, and uv
Earlier this week I considered whether I should finally switch away from virtualenvwrapper to using local .venv
managed by direnv.
I’ve never seriously used direnv, but I’ve been hearing Jeff and Hynek talk about their use of direnv for a while.
After a few days, I’ve finally stumbled into a setup that works great for me.
I’d like to note the basics of this setup as well as some fancy additions that are specific to my own use case.
My old virtualenvwrapper workflow
First, I’d like to note my old workflow that I’m trying to roughly recreate:
- I type
mkvenv3 <project_name>
to create a new virtual environment for the current project directory and activate it - I type
workon <project_name>
when I want to workon that project: this activates the correct virtual environment and changes to the project directory
The initial setup I thought of allows me to:
- Run
echo layout python > .envrc && direnv allow
to create a virtual environment for the current project and activate it - Change directories into the project directory to automatically activate the virtual environment
The more complex setup I eventually settled on allows me to:
- Run
venv <project_name>
to create a virtual environment for the current project and activate it - Run
workon <project_name>
to change directories into the project (which automatically activates the virtual environment)
The initial setup
First, I installed direnv and added this to my ~/.zshrc
file:
1
|
|
Then whenever I wanted to create a virtual environment for a new project I created a .envrc
file in that directory, which looked like this:
Then I ran direnv allow
to allow, as direnv
instructed me to, to allow the new virtual environment to be automatically created and activated.
That’s pretty much it.
Unfortunately, I did not like this initial setup.
No shell prompt?
The first problem was that the virtual environment’s prompt didn’t show up in my shell prompt.
This is due to a direnv not allowing modification of the PS1
shell prompt.
That means I’d need to modify my shell configuration to show the correct virtual environment name myself.
So I added this to my ~/.zshrc
file to show the virtual environment name at the beginning of my prompt:
1 2 3 4 5 6 7 |
|
Wrong virtual environment directory
The next problem was that the virtual environment was placed in .direnv/python3.12
.
I wanted each virtual environment to be in a .venv
directory instead.
To do that, I made a .config/direnv/direnvrc
file that customized the python layout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Loading, unloading, loading, unloading…
I also didn’t like the loading and unloading messages that showed up each time I changed directories.
I removed those by clearing the DIRENV_LOG_FORMAT
variable in my ~/.zshrc
configuration:
1
|
|
The more advanced setup
I don’t like it when all my virtual environment prompts show up as .venv
.
I want ever prompt to be the name of the actual project… which is usually the directory name.
I also really wanted to be able to type venv
to create a new virtual environment, activate it, and create the .envrc
file for my automatically.
Additionally, I thought it would be really handy if I could type workon <project_name>
to change directories to a specific project.
I made two aliases in my ~/.zshrc
configuration for all of this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
Now I can type this to create a .venv
virtual environment in my current directory, which has a prompt named after the current directory, activate it, and create a .envrc
file which will automatically activate that virtual environment (thanks to that ~/.config/direnv/direnvrc
file) whenever I change into that directory:
If I wanted to customized the prompt name for the virtual environment, I could do this:
When I wanted to start working on that project later, I can either change into that directory or if I’m feeling lazy I can simply type:
That reads from my ~/.projects
file to look up the project directory to switch to.
Switching to uv
I also decided to try using uv for all of this, since it’s faster at creating virtual environments.
One benefit of uv
is that it tries to select the correct Python version for the project, if it sees a version noted in a pyproject.toml
file.
Another benefit of using uv
, is that I should also be able to update the venv
to use a specific version of Python with something like --python 3.12
.
Here are the updated shell aliases for the ~/.zshrc
for uv
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Switching to starship
I also decided to try out using Starship to customize my shell this week.
I added this to my ~/.zshrc
:
1
|
|
And removed this, which is no longer needed since Starship will be managing the shell for me:
1 2 3 4 5 6 7 |
|
I also switched my python
layout for direnv to just set the $VIRTUAL_ENV
variable and add the $VIRTUAL_ENV/bin
directory to my PATH
, since the $VIRTUAL_ENV_PROMPT
variable isn’t needed for Starship to pick up the prompt:
1 2 3 4 5 |
|
I also made a very boring Starship configuration in ~/.config/starship.toml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
I setup such a boring configuration because when I’m teaching, I don’t want my students to be confused or distracted by a prompt that has considerably more information in it than their default prompt may have.
The biggest downside of switching to Starship has been my own earworm-oriented brain.
As I update my Starship configuration files, I’ve repeatedly heard David Bowie singing “I’m a Starmaaan”. 🎶
Ground control to major TOML
After all of that, I realized that I could additionally use different Starship configurations for different directories by putting a STARSHIP_CONFIG
variable in specific layouts.
After that realization, I made my configuration even more vanilla and made some alternative configurations in my ~/.config/direnv/direnvrc
file:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Those other two configuration files are fancier, as I have no concern about them distracting my students since I’ll never be within those directories while teaching.
You can find those files in my dotfiles repository.
The necessary tools
So I replaced virtualenvwrapper with direnv, uv, and Starship.
Though direnv was is doing most of the important work here.
The use of uv and Starship were just bonuses.
I am also hoping to eventually replace my pipx use with uv and once uv supports adding python3.x commands to my PATH
, I may replace my use of pyenv with uv as well.
Thanks to all who participated in my Mastodon thread as I fumbled through discovering this setup.