How to automatically activate virtualenvs when cd'ing into a directory

VirtualenvZshOh My-ZshVirtualenvwrapperDirenv

Virtualenv Problem Overview


I have a bunch of projects in my ~/Documents. I work almost exclusively in python, so these are basically all python projects. Each one, e.g. ~/Documents/foo has its own virtualenv, ~/Documents/foo/venv (they're always called venv). Whenever I switch between projects, which is ~10 times a day, I do

deactivate
cd ..
cd foo
source venv/bin/activate

I've reached the point where I'm sick of typing deactivate and source venv/bin/activate. I'm looking for a way to just cd ../foo and have the virtualenv operations handled for me.

  • I'm familiar with VirtualEnvWrapper which is a little heavy-handed in my opinion. It seems to move all your virtualenvs somewhere else, and adds a little more complexity than it removes, as far as I can tell. (Dissenting opinions welcome!)

  • I am not too familiar with shell scripting. If you can recommend a low-maintenance script to add to my ~/.zshrc that accomplishes this, that would be more than enough, but from some quick googling, I haven't found such a script.

  • I'm a zsh/oh-my-zsh user. oh-my-zsh doesn't seem to have a plugin for this. The best answer to this question would be someone contributing an oh-my-zsh plugin which does this. (Which I might do if the answers here are lackluster.

Virtualenv Solutions


Solution 1 - Virtualenv

Add following in your .bashrc or .zshrc

function cd() {
  builtin cd "$@"

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    ## If env folder is found then activate the vitualenv
      if [[ -d ./.env ]] ; then
        source ./.env/bin/activate
      fi
  else
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate
      parentdir="$(dirname "$VIRTUAL_ENV")"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        deactivate
      fi
  fi
}

This code will not deactivate the virtualenv even if someone goes into subfolder. Inspired by answers of @agnul and @Gilles.

If the virtualenv is made by pipenv, then please consider this wiki page.

Furthermore, for added security please consider direnv.

Solution 2 - Virtualenv

Put something like this in your .zshrc

function cd() {
  if [[ -d ./venv ]] ; then
    deactivate
  fi

  builtin cd $1

  if [[ -d ./venv ]] ; then
    . ./venv/bin/activate
  fi
}

Edit: As noted in comments cd-ing into a subfolder of the current virtual env would deactivate it. One idea could be to deactivate the current env only if cd-ing into a new one, like

function cd() {
  builtin cd $1

  if [[ -n "$VIRTUAL_ENV" && -d ./venv ]] ; then
    deactivate
    . ./venv/bin/activate
  fi
}

that could still be improved, maybe turning it into a "prompt command" or attempting some prefix matching on the folder names to check there's a virtual env somewhere up the path, but my shell-fu is not good enough.

Solution 3 - Virtualenv

Rather than writing a custom script you could use direnv. It's not a zsh specific solution (for that you could try zsh-autoenv), but is well-maintained and easy to use with zsh. Once you've installed it, you'd want to put eval "$(direnv hook zsh)" at the end of your .zshrc. At that point you can do:

$ source ~/.zshrc
$ cd foo
$ echo "layout python" > .envrc
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
$ direnv allow
direnv: loading .envrc
direnv: export +VIRTUAL_ENV ~PATH

Now you should be in your virtualenv. You can test by running pip freeze to see that your virtualenv specific packages are installed. To deactivate

$ cd ..
direnv: unloading

Solution 4 - Virtualenv

You should try something like autoenv if not direnv.

The first one is considered to be "lightweight", while the second one "simply, higher quality software", listening respectively to each one's author, talking about the other one's project. Thus, they seem to me fairly good options, to try both!

Anyway, both have been tested on zsh shells. In particular, autoenv is really simple to use, after installing it:

$ git clone git://github.com/inishchith/autoenv.git ~/.autoenv
$ echo 'source ~/.autoenv/activate.sh' >> ~/.bashrc

just "follow the white rabbit " and try for example

$ mkdir project
$ echo "echo 'whoa'" > project/.env
$ cd project
whoa

"If a directory contains a .env file, it will automatically be executed when you cd into it. When enabled (set AUTOENV_ENABLE_LEAVE to a non-null string), if a directory contains a .env.leave file, it will automatically be executed when you leave it."

Have a look at <https://github.com/inishchith/autoenv> for more detailed instructions!...

Solution 5 - Virtualenv

By far the easiest option (in 2019+) is to add virtualenvwrapper into your ~/.zshrc plugins

For example:

plugins=(
  git pip python brew virtualenvwrapper
)

Solution 6 - Virtualenv

For posterity: I used @MS_'s solution but ran into the problem where cding directly from one project to another deactivates the old virtualenv but doesn't activate the new one. This is a slightly modified version of that solution which solves this problem:

# auto activate virtualenv
# Modified solution based on https://stackoverflow.com/questions/45216663/how-to-automatically-activate-virtualenvs-when-cding-into-a-directory/56309561#56309561
function cd() {
  builtin cd "$@"
  
  ## Default path to virtualenv in your projects
  DEFAULT_ENV_PATH="./env"
  
  ## If env folder is found then activate the vitualenv
  function activate_venv() {
    if [[ -f "${DEFAULT_ENV_PATH}/bin/activate" ]] ; then 
      source "${DEFAULT_ENV_PATH}/bin/activate"
      echo "Activating ${VIRTUAL_ENV}"
    fi
  }

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    activate_venv
  else
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate then run a new env folder check
      parentdir="$(dirname ${VIRTUAL_ENV})"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        echo "Deactivating ${VIRTUAL_ENV}"
        deactivate
        activate_venv
      fi
  fi
}

Solution 7 - Virtualenv

For anyone using (or considering to use) pyenv this can be achieved very conveniently using the pyenv-virtualenv plugin as described here.

Basically you simply add a .python-version file to the directory in which the name of the virtualenv is specified.

Solution 8 - Virtualenv

that is the solution without cd'ing, with zsh set to setop auto_cd w'll be able to change directories without cd, just type directory name and hit enter. it is anhence of above solution:

    # auto activate virtualenv
# Modified solution based on https://stackoverflow.com/questions/45216663/how-to-automatically-activate-virtualenvs-when-cding-into-a-directory/56309561#56309561
function auto_active_env() {

  ## Default path to virtualenv in your projects
  DEFAULT_ENV_PATH="./env"

  ## If env folder is found then activate the vitualenv
  function activate_venv() {
    if [[ -f "${DEFAULT_ENV_PATH}/bin/activate" ]] ; then 
      source "${DEFAULT_ENV_PATH}/bin/activate"
      echo "Activating ${VIRTUAL_ENV}"
    fi
  }

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    activate_venv
  else
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate then run a new env folder check
      parentdir="$(dirname ${VIRTUAL_ENV})"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        echo "Deactivating ${VIRTUAL_ENV}"
        deactivate
        activate_venv
      fi
  fi
}
chpwd_functions=(${chpwd_functions[@]} "auto_active_env")

Solution 9 - Virtualenv

Here is (yet) another solution to automatically activate a virtual environment; it's based on a number of the answers already posted here.

This will work for any Virtual Environment name or directory (not just ./env, ./venv, etc.). Also supports subdirectories, as well as cd-ing into symlinks of virtual environment (parent) folders.

This code searches for a pyvenv.cfg file instead of a particular named directory. If one is found within a subdirectory of the current folder, the environment is automatically activated. Once inside a virtual environment, that state is retained until you move out of the parent virtual environment directory, at which point the environment is deactivated.

Place this inside your .bashrc or .bash_profile.

function cd() {
  builtin cd "$@"

  if [[ -z "$VIRTUAL_ENV" ]] ; then
      # If config file is found -> activate the vitual environment
      venv_cfg_filepath=$(find . -maxdepth 2 -type f -name 'pyvenv.cfg' 2> /dev/null)
      if [[ -z "$venv_cfg_filepath" ]]; then
        return # no config file found
      fi

      venv_filepath=$(cut -d '/' -f -2 <<< ${venv_cfg_filepath})
      if [[ -d "$venv_filepath" ]] ; then
        source "${venv_filepath}"/bin/activate
      fi
  else
    # If the current directory is not contained 
    # within the venv parent directory -> deactivate the venv.
      cur_dir=$(pwd -P)
      venv_dir="$(dirname "$VIRTUAL_ENV")"
      if [[ "$cur_dir"/ != "$venv_dir"/* ]] ; then
        deactivate
      fi
  fi
}

Personally I think it's an improvement on a lot of the solutions here, since it should work for any virtual environment

Solution 10 - Virtualenv

This is a zsh only solution.

This is an improvement over daveruinseverything's answer which is an improvement over MS_'s answer.

We are using precmd hook instead of overwriting cd.

We have added another extra feature. Suppose the directory structure is

├── .venv
│   ├── bin
│   │   └── activate
├── subdir
│   ├── subdir1
│   │   ├── subdir2
│   │   │   └── test2.txt
│   │   └── test1.txt
│   └── test.txt
└── testing.py

If you now open new terminal in subdir2, or directly cd to subdir2 from other place, it will activate the venv.

The solution is:

autoload -Uz add-zsh-hook
add-zsh-hook precmd automatically_activate_python_venv

function automatically_activate_python_env() {
  if [[ -z $VIRTUAL_ENV ]] ; then
    activate_venv
  else
    parentdir="$(dirname ${VIRTUAL_ENV})"
    if [[ "$PWD"/ != "$parentdir"/* ]] ; then
      deactivate
      activate_venv
    fi
  fi
}

function activate_venv() {  
  local d n
  d=$PWD
  
  until false 
  do 
  if [[ -f $d/.venv/bin/activate ]] ; then 
    source $d/.venv/bin/activate
    break
  fi
    d=${d%/*}
    # d="$(dirname "$d")"
    [[ $d = *\/* ]] || break
  done
}

Solution 11 - Virtualenv

For a python developer like myself, I use this plugin to make, activate python virtual environments when cding into a python project, it also deactivates after cding into another directory.

Solution 12 - Virtualenv

Similar to Jake's answer but supports cding from one virtualenv to another. In this case it deactivates the old virtualenv then activates the new one.

function cd() {
  builtin cd "$@"

  if [[ ! -z "$VIRTUAL_ENV" ]] ; then
    # If the current directory is not contained
    # within the venv parent directory -> deactivate the venv.
    cur_dir=$(pwd -P)
    venv_dir="$(dirname "$VIRTUAL_ENV")"
    if [[ "$cur_dir"/ != "$venv_dir"/* ]] ; then
      deactivate
    fi
  fi

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    # If config file is found -> activate the vitual environment
    venv_cfg_filepath=$(find . -maxdepth 2 -type f -name 'pyvenv.cfg' 2> /dev/null)
    if [[ -z "$venv_cfg_filepath" ]]; then
      return # no config file found
    fi

    venv_filepath=$(cut -d '/' -f -2 <<< ${venv_cfg_filepath})
    if [[ -d "$venv_filepath" ]] ; then
      source "${venv_filepath}"/bin/activate
    fi
  fi
}

Solution 13 - Virtualenv

This is my solution, which:

  • checks if already at the currently active venv, and do nothing in that case
  • if there is a venv folder, deactivate the active one if there is one
  • activate the new venv whatever if there was an old one or not.

In my bash_aliases:

function cd() {
    builtin cd "$@"

    if [ $(dirname "$VIRTUAL_ENV") == $(pwd) ] ; then
	    # Already at the active virtual env
    	return
    fi

    if [[ -d ./venv ]] ; then
    	if type deactivate > /dev/null 2>&1 ; then
            printf "Deactivating virtualenv %s\n" "$VIRTUAL_ENV"
         	deactivate
    	fi
	
	    source ./venv/bin/activate
    	printf "Setting up   virtualenv %s\n" "$VIRTUAL_ENV"
    fi
}

Solution 14 - Virtualenv

This is my solution:

  1. If VIRTUAL_ENV is not set then:
  2. Check if we're inside a virtual env
  3. If yes, then activate it
  4. Else (VIRTUAL_ENV is defined), check that the current folder starts with $VIRTUAL_ENV (removing the /venv part) and verify that the deactivate command exists
  5. Deactivate teh environment

This is the script:

function cd() {
  builtin cd $1

  if [[ -z "${VIRTUAL_ENV}" ]]; then
    if [[ -d ./venv && -f ./venv/bin/activate ]]; then
      source ./venv/bin/activate
    fi
  elif [[ ! "$(pwd)" == ${VIRTUAL_ENV:0:n-5}* && ! -z "$(command -v deactivate)" ]]; then
    deactivate
  fi
}

Note: You need to add this to .bashrc. If it doesn't work, check if your .profile is not overriding your command (it happened to me)

Solution 15 - Virtualenv

Based on @MS_'s solution:

function cd() {
  builtin cd "$@"

  ## If env folder is found then activate the vitualenv
  if [[ -d ./venv ]] ; then
    source ./venv/bin/activate
  fi

  if [[ -n "$VIRTUAL_ENV" ]] ; then
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate
      parentdir="$(dirname "$VIRTUAL_ENV")"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        deactivate
      fi
  fi
}

Solution 16 - Virtualenv

I've used direnv in the past, as others have mentioned. Lyft use aactivator for this exact scenario.

> Once the venv is built it must be activated (added to $PATH). We use aactivator to automatically activate the venv each time a user enters the service directory and deactivates as they leave.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlex LenailView Question on Stackoverflow
Solution 1 - VirtualenvMS_View Answer on Stackoverflow
Solution 2 - VirtualenvagnulView Answer on Stackoverflow
Solution 3 - Virtualenvmc_kaiserView Answer on Stackoverflow
Solution 4 - VirtualenvandreagalleView Answer on Stackoverflow
Solution 5 - VirtualenvRomanView Answer on Stackoverflow
Solution 6 - VirtualenvdaveruinseverythingView Answer on Stackoverflow
Solution 7 - VirtualenvhummatView Answer on Stackoverflow
Solution 8 - VirtualenvyattaraView Answer on Stackoverflow
Solution 9 - VirtualenvJake TeslerView Answer on Stackoverflow
Solution 10 - VirtualenvAhmad IsmailView Answer on Stackoverflow
Solution 11 - Virtualenva3kView Answer on Stackoverflow
Solution 12 - VirtualenvalihView Answer on Stackoverflow
Solution 13 - VirtualenvGauthierView Answer on Stackoverflow
Solution 14 - VirtualenvgfournierView Answer on Stackoverflow
Solution 15 - VirtualenvJonny ShanahanView Answer on Stackoverflow
Solution 16 - VirtualenvHolly CumminsView Answer on Stackoverflow