Automating Python Virtual Environments with Zsh on macOS

Managing Python virtual environments can be tedious - having to manually activate and deactivate them as you move between projects. I decided to create a script that will automatically activate virtual environment. When you cd into a directory containing a .venv folder. When you leave, it will deactivate it.

Installing Zsh

While macOS comes with zsh as the default shell since Catalina (10.15), you may want to ensure you have the latest version. The easiest way to install or update zsh is using Homebrew:

# Install Homebrew if you haven't already
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install zsh
brew install zsh

Oh My Zsh is a framework for managing your zsh configuration. It comes with helpful plugins and themes:

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Configuring Automatic Virtual Environment Activation

Now we’ll set up the automatic virtual environment activation. Add the following code to your ~/.zshrc file:

#---------------------------------------------- chpwd pyvenv ---
python_venv() {
  MYVENV=./.venv
  # when you cd into a folder that contains $MYVENV
  [[ -d $MYVENV ]] && source $MYVENV/bin/activate > /dev/null 2>&1
  # when you cd into a folder that doesn't
  [[ ! -d $MYVENV ]] && deactivate > /dev/null 2>&1 || true
}
autoload -U add-zsh-hook
add-zsh-hook chpwd python_venv

python_venv

Let’s break down what this script does:

  1. python_venv() - Defines a function that checks for and manages virtual environments
  2. MYVENV=./.venv - Sets the virtual environment directory name (change this if you use a different name)
  3. [[ -d $MYVENV ]] - Checks if the .venv directory exists
  4. source $MYVENV/bin/activate - Activates the virtual environment if found
  5. deactivate - Deactivates any active virtual environment when leaving a project directory
  6. add-zsh-hook chpwd python_venv - Runs our function whenever we change directories

Testing the Setup

  1. First, source your updated .zshrc:
source ~/.zshrc
  1. Create a test project with a virtual environment:
# Create a test directory
mkdir ~/test-venv
cd ~/test-venv

# Create a virtual environment
python -m venv .venv
  1. Test the automatic activation:
# Move out of the directory
cd ~
# Notice the virtual environment is deactivated

# Move back into the test directory
cd ~/test-venv
# The virtual environment should automatically activate

You should see your prompt change to indicate the virtual environment is active when entering the directory, and return to normal when leaving it.

Troubleshooting

If the automatic activation isn’t working:

  1. Make sure zsh is your default shell:
echo $SHELL
# Should output /bin/zsh
  1. Check that the function is loaded:
type python_venv
# Should show the function definition
  1. Verify your virtual environment structure:
ls -la .venv/bin/activate
# Should show the activation script

Additional Tips

  • If you use a different virtual environment directory name, modify the MYVENV variable in the script.
  • Add export VIRTUAL_ENV_DISABLE_PROMPT=1 to your .zshrc if you want to use your own prompt styling.
  • Consider adding the following to your global .gitignore:
echo ".venv" >> ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global

Conclusion

With this setup, you can seamlessly work across multiple Python projects without manually managing virtual environments. The shell takes care of activation and deactivation as you navigate your filesystem, making your development workflow more efficient.

Remember to create your virtual environments with the name .venv in your project roots to take advantage of this automation. Happy coding!