top of page
  • Writer's pictureAlexander Deca

Python Virtual Environments on macOS

Updated: Aug 31, 2023


A long time ago in a galaxy far, far away... wait, no scratch that, during one of the Datacenter Partner Virtual Teams from Cisco a couple of years back, I got introduced to Visual Studio Code by one of the presenters. I must admit I usually try to avoid Microsoft products, yes I know they are not all that bad ;), but this Integrated Development Environment is excellent. I probably only use 10% of its capabilities, but it is still good enough for a non-programmer.


I like the fact you can use different workspaces for different projects and it has standard shell integrations such as bash, zsh and others which make it easy to run for example, your Python script.


Visual Studio Code and shell integration

Now on to the topic of this blog post, I like the idea of structuring your different projects with the workspaces in Visual Studio Code and with Python virtual environments (https://pypi.org/project/virtualenv/) Virtualenv is a tool used in Python programming to create isolated environments for project-specific dependencies. It allows you to maintain separate Python environments for different projects, ensuring that each project has its own set of packages and dependencies without conflicting with each other.


Before we go into the details, there is this great package manager available for macOS called Homebrew, once installed on your Mac it is easy to install the latest Python version (macOS does not always follow the latest releases).


You can easily install the latest Python release by entering "brew install python" in your shell. To verify which Python version globally is installed, run the "python --version" command.

Installation of the latest Python version through Homebre
Verify the installed version of Python

Lastly, verify that the Python executable points to the Python version installed through Homebrew.

Verify that Python is linking to the Homebrew installed version

The next step is to install virtualenv (as of Python 3.3, a module venv is included

in the standard library, which we will not discuss in this post, though). Virtualenv has the following key characteristics:

  1. Isolation: Virtualenv creates an isolated Python environment by copying or linking the Python interpreter and all the necessary libraries and packages. This isolation prevents conflicts between different projects that may require different versions of the same package.

  2. Dependency Management: With virtualenv, you can easily manage project dependencies. Each environment can have its own set of installed packages, which makes it easier to maintain and share project-specific requirements.

  3. Python Version Support: Virtualenv supports creating environments with different Python versions. For example, you can create an environment with Python 3.7 for one project and another environment with Python 3.9 for a different project.

  4. Activation and Deactivation: Once you create a virtual environment, you can activate it to make it the current Python environment for your terminal session. Activation ensures that any Python commands or scripts you run use the packages installed within that environment. When you're done working on a project, you can deactivate the environment to return to the global Python environment.

Virtualenv can be installed by running "pip install virtualenv" For those not aware, PIP is the package installer for Python, which stands for "Pip Installs Packages." It is the standard package management system used to install and manage software packages written in Python. PIP lets you easily install, upgrade, and remove Python libraries and frameworks from the Python Package Index (PyPI) or other package repositories. However, as I am using homebrew I installed virtualenv through "brew install virtualenv". (note in the screenshot below I used the command "brew reinstall virtualenv" as it was already installed on my MacBook)


Once installed, you can verify your running version by entering virtualenv --version.

Installing virtualenv through homebrew

Now we are good to go to start creating virtual environments, however with the following extensions, we can make our lives easier (at least for me...)

  1. Virtualenvwrapper: is a popular extension for the virtualenv tool in Python that simplifies the management of multiple virtual environments. It provides a set of commands and shortcuts to create, activate, deactivate, and delete virtual environments. Please note officially zsh and bash shell are supported.

  2. Direnv: is an open-source software tool that simplifies the management of environment variables within different project directories. It allows developers to automatically define and load environment variables based on the current working directory. Direnv is particularly useful for managing project-specific configurations, credentials, and other environment-dependent settings. For me, the kicker with direnv is automatic activation, meaning when you navigate to a directory containing a ".envrc" file, Direnv automatically loads the environment variables specified in that file into your shell session and activates the virtual environment.

To install virtualenvwrapper use homebrew to install this package as we installed virtualenv as well through homebrew if you don't, you will get errors that the module cannot be found (/usr/bin/python3: Error while finding spec for 'virtualenvwrapper.hook_loader' (<class 'ImportError'>: No module named 'virtualenvwrapper')



Now that virtualenvwrapper is installed, add the following lines to your bash_profile, which tells the system where your virtual environments live, the location of your project directory, the default version of Python to be used, and the location of the virtualenvwrapper script.


Add the following lines to your bash / zsh profile
  • export WORKON_HOME=$HOME/.virtualenvs: this tells the system where your virtual environments live i.e. in my home directory and the directory's name is virtualenvs. When creating a first virtual environment, the directory virtualenvs will be created automatically if it doesn't exist.

  • export PROJECT_HOME=$HOME/Devel: this is the directory where all the actual files live for the different projects

  • export VIRTUALENVWRAPPER_VIRTUALENV=/usr/local/bin/virtualenv: tells the system the location of the virtualenv installation

  • export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3: tells the system which python version is used where you installed the virtualenvwrapper with.

  • source /usr/local/bin/virtualenvwrapper.sh: tells the system to load the virtualenvwrapper script

export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/Devel
export VIRTUALENVWRAPPER_VIRTUALENV=/usr/local/bin/virtualenv
export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
source /usr/local/bin/virtualenvwrapper.sh

Save the file and reload your bash by launching the following command source ~/.bash_profile.


Now you can see the different virtual environments by running the workon command and creating a new environment with mkvirtualenv <name>; if you would like to use a specific version, you can use the -p option mkvirtualenv -p pyhton2.7 <name>. Once the environment is created, you can see the (temp) in front of the prompt this means the virtual environment is now active. To deactivate the environment, launch the command deactivate, to delete the environment, use rmvirtualenv <name>.


Creating and listing your virtual environments

Last but not least, installing direnv is straightforward through Homebrew, brew install direnv. Before you can use direnv, we need to hook it into your preferred shell, this can be done by adding the following line to your bash_profile eval "$(direnv hook bash)".


As I am using the virtualenvwrapper in combination with direnv, we need to add the following lines to the direnv configurations file, which can be found ~/.config/direnv/direnvrc to make sure when you cd into a directory containing a .envrc file, referencing the layout_virtualenvwrapper <name of the virtual envrionment> the virtual environment and your defined environment variables are loaded automatically.

layout_virtualenv() {
  local venv_path="$1"
  source ${venv_path}/bin/activate
}

layout_virtualenvwrapper() {
  local venv_path="${WORKON_HOME}/$1"
  layout_virtualenv $venv_path
}

Example of .envrc file for my "ACI" virtual environment

Every time you change the .envrc file, it will be blocked, and you will need to allow the changes you have made explicitly by typing "direnv allow".


Now I like that when I cd into my virtual environment working directory, my prompt reflects the virtual environment I am working on. When I leave the directory, direnv will unload the virtualenv and unset the prompt to the default prompt (see screenshot above, "unset PS1"). This can be achieved by adding the following lines to your .bash_profile:

show_virtual_env() {
  if [[ -n "$VIRTUAL_ENV" && -n "$DIRENV_DIR" ]]; then
    echo "($(basename $VIRTUAL_ENV))"
  fi
}
export -f show_virtual_env
PS1='$(show_virtual_env)'$PS1

Circling back to Visual Studio Code, when you have a workspace open, and the workspace directory is the same directory as where you have your .envrc file when you open up a terminal in Visual Studio Code, it will load the virtual environment, changes the prompt, and loads your environment variables.



Happy coding :D

197 views0 comments
bottom of page