How I manage my Python Virtual Environments

Backend Engineer 🚀 Cloud Native Enthusiast ☁
Search for a command to run...

Backend Engineer 🚀 Cloud Native Enthusiast ☁
Managing Python virtual environments is a fundamental practice for keeping project dependencies organized and avoiding version conflicts. By using the built-in venv module, you can create isolated environments for each project, ensuring that your global Python setup stays clean and your workflows remain efficient. It’s an essential habit for any developer looking to build stable, reproducible applications.
vLLM is a workhorse to run inference for any LLM under the sun. One of the recent developments in the project is the ability to define thinking_token_budget, basically a request level argument that ca

Learn how to build your own powerful Large Language Model (LLM) application that can search the web, extract information, and answer your questions – all LOCALLY! In this tutorial, I'll walk you through the process of creating a web-searching LLM fro...

Large Language Models are great at generating code, but we need to go an extra mile to run them; well not any more. In this video, we will be building our application that can use both local and hosted large language models (Gemini, ChatGPT, and so o...

Having a private PyPI repository can be beneficial in many ways. We can host our internal private packages; from security perspective, it can be a controlled environment for public packages from where all the dependencies are installed in our project...

In a common workflow we create a virtual environment using different tools inside of the project directory. You might have venv, .venv, env and so on. I find it a bit cluttered. My approach is a bit different, and you might already be using something similar with a third party tool.
Here's how it goes, all the virtual environments go inside of the central ~/.venvs directory. If I am inside of a project called project-xyz, I create a virtual environment inside of the central directory like ~/.venvs/project-xyz. I have one-to-one mapping of my project directory name with my virtual environment name and it comes with some benefits and shortcuts.
I have a zsh function that helps me build a virtual environment using the python version of my choice. And yes I use uv.
This is what I have in my ~/.zshrc
vm() {
base_name=\((basename "\)PWD")
uv venv --python 3.\({1:-11} ~/.venvs/"\)base_name"
}
vm: name of the function, will also automatically become a SHELL alias
For me everything relating to virtual environment starts with v so it's "virtual environment make", aka vm
base_name=\((basename "\)PWD"): we are taking the base name of the "present working directory (pwd)" which will be the name of the project directory.
example:
if pwd is "/home/yankee/projects/tldr-generator"
then basename is tldr-generator
uv venv --python 3.${1:-11}: create a virtual environment using uv. Set the python version that defaults to 11 and if I want to use any other version it will take the first argument passed to the alias.
example:
if I run vm 13, it will create a virtual environment with python version 3.13. At times I feel lazy to write the whole 3.13 so I just pass the minor version I want to use.
~/.venvs/"$base_name": The later part is just the location where I want to create my virtual environment.
Now with this command, I can to go to any project directory, then run:
vm
# OR
# for python 3.13
vm 13
# for python 3.12
vm 12
# and so on...
Adding on to the idea of having the base_name as the virtual environment on the centralized location. Any time I am in the project directory I can easily activate the virtual environment from ~/.venvs/<base_name>/bin/activate. For that I have a function called va (virtual environment activate).
va() {
base_name=\((basename "\)PWD")
source ~/.venvs/"$base_name"/bin/activate
}
Now if I am in any of my project directory, I can simply run va and it will activate the correct virtual environment for the project. And if it does not exist, I can do vm followed by va.
To deactivate a virtual environment, one can simply write deactivate but it ‘s a long word to type and mistakes have happened. So following the pattern, I have an alias for this one called vd (more about aliases in this video)
alias vd="deactivate"
One common action I have to do sometimes is delete a virtual environment and build a new one again. For deletion I have an alias called vrm that deletes the existing virtual environment.
vrm(){
base_name=\((basename "\)PWD")
venv_path="\(HOME/.venvs/\)base_name"
rm -rf "$venv_path"
echo "Deleted $venv_path"
}
Same idea with basename to find the virtual environment and delete it. Now with creation and activating the new venv, the whole flow looks like this:
vrm
vm
va
uv sync # or whatever package installer action
Birds-eye view of all virtual environments and deletion: most of the time there are projects with venv that takes huge amounts of storage. They are just there sitting idle and it’s hard to find them. With everything under ~/.venvs I have an birds-eye view of which project is consuming what amount of storage. And if it’s taking too much storage or I no longer need it, I can delete it from there. We can do this with a simple built-in du command or you can point your storage manager app here.
du -sh */ | sort -hr
-sh: shows size of each directory in human-readable form
sort -h → sorts by size (handles KB, MB, GB correctly)

Good with git worktrees, kinda*: When I am working with multiple features (mostly two at a time) for a project, I usually work on the main worktree and create a new worktree to hack on a different feature. So the way I create worktrees is similar to virtual environments, where things are kept central. The pattern I follow is:
~/.worktrees/<feature-name>/<project-name>
Here the project-name is same as the main project directory name (basename). For va to work, we explicitly need to match the name of the project directory, hence I create a nested directory structure where feature branch is a parent directory that contains the main project directory.
This way I can switch to my worktree, run va again, and by nature of finding virtual environment and activating based on the basename, it will automatically activate the existing virtual environment that I created from my main worktree.
I have a dedicated tool created to switch between worktrees with breeze called git-worktree-switcher. And to see how you can work with git worktrees and make use of this tool to be more effective you can check my dedicated video:
https://www.youtube.com/watch?v=fRPpM5kvlls
So the `kinda*` is, sometimes the feature that I am building requires a version upgrade, addition or removal of a package. In such cases using the same virtual environment across two different feature branch creates chaos. When this happens I don’t bother creating a project directory nested inside the feature directory. I simply create a worktree that does not match the main worktree project directory name and create a new virtual environment for it.
```shell
git worktree add -b new-feature-x ~/.worktrees/new-feature-x
```