Reputation: 14410
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.
Upvotes: 109
Views: 65970
Reputation: 11482
I've used direnv in the past, as others have mentioned. Lyft uses 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.
Upvotes: 2
Reputation: 11
you can add an alias command rather than susing actually cd as it is critcial and most atmoic command. I did this in my zsh:
update the ~/.bashrc
export VIRTUAL_ENV_ACTIVE=""
function pycd() {
if [[ -n "$VIRTUAL_ENV_ACTIVE" ]]; then echo "Deactivating virtual environment..." deactivate export VIRTUAL_ENV_ACTIVE="" fi
builtin cd "$1" || return
if [ -f "./.venv/bin/activate" ]; then echo "Activating virtual environment from .venv..." source .venv/bin/activate export VIRTUAL_ENV_ACTIVE="$PWD" # Store the path of the activated virtual environment elif [ -f "./venv/bin/activate" ]; then echo "Activating virtual environment from venv..." source venv/bin/activate export VIRTUAL_ENV_ACTIVE="$PWD" else echo "No virtual environment found in this directory." fi }
source ~/.bashrc
now for the first time you have to create .venv
by python3 -m venv .venv
next time you can simply do pycd my_project
and it will activate the virtual environment and take you inside directory.
For deactivating simply use deactivate in the project directory
Works for me :)
Upvotes: 0
Reputation: 734
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!...
PS: Applicable to OP's question, the answer should be the following:
After autoenv installation:
echo "export AUTOENV_ENABLE_LEAVE=true" >> ~/.zshrc
omz reload
After that in a folder where you need to set the env:
echo "source venv/bin/activate" > .env
echo "deactivate" > .env.leave
After that, you may leave the directory, return to it, and confirm that config is okay. After confirming, both .env and .env.leave will be executed automatically.
Upvotes: 27
Reputation: 13842
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 a 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_venv() {
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
}
Upvotes: 8
Reputation: 2492
The most upvoted answer didn't work for me. But adding this to my .bashrc
did
# Automatically activate Python venv if it exists
auto_activate_venv() {
if [ -e ".venv/bin/activate" ]; then
source .venv/bin/activate
elif [ "$VIRTUAL_ENV" != "" ] && [ ! -e "$PWD/.venv" ]; then
deactivate
fi
}
# Override the 'cd' command to call our function
cd() {
builtin cd "$@" && auto_activate_venv
}
# If you use pushd/popd, you can override them too.
pushd() {
builtin pushd "$@" && auto_activate_venv
}
popd() {
builtin popd "$@" && auto_activate_venv
}
Upvotes: 0
Reputation: 8103
pyenv-virtualenv
+ pyenv local
$ pyenv virtualenv 3.10.9 my-project
$ pyenv local my-project
That's is.
Every time you cd into that project directory pyenv
automatically:
.python-version
file made by pyenv local
$HOME/.pyenv/versions/3.10.9/envs/my-project
Upvotes: 0
Reputation: 659
Self promotion warning!
If you use oh-my-zsh I've forked and greatly improved (imo) a plugin that works for both bash and zsh and I included instructions for installing with oh-my-zsh
https://github.com/RobertDeRose/virtualenv-autodetect
Upvotes: 2
Reputation: 11
Here is an alternative which sets an env variable when cd'ing into a virtualenv directory and checks if an active subdir is a child-directory of said virtualenv.
This will also deactivate when exiting or cd'ing to another directory.
This assumes you're using Pyenv to manage your Python installations, however you can change the pyvenv.cfg
notation to anything else.
function cd() {
builtin cd "$@"
if [[ -f ./pyvenv.cfg ]] ; then
export VENV_CURRENT=$PWD
source ./bin/activate
else
if [[ $(env | fgrep VENV_CURRENT) ]]; then
if ! [[ $(pwd | fgrep $VENV_CURRENT) ]]; then
unset VENV_CURRENT
deactivate
fi
fi
fi
}
Upvotes: 1
Reputation: 1283
You can use a zsh hook function that runs whenever you change directories. For example, you could add this to your ~/.zshrc:
# Define a function to activate/deactivate virtualenvs based on the current directory
function venv_cd() {
# Check if the current directory has a venv subdirectory
if [[ -d venv ]]; then
# If yes, activate it if it's not already active
if [[ "$VIRTUAL_ENV" != "$PWD/venv" ]]; then
source venv/bin/activate
fi
else
# If no, deactivate the current virtualenv if any
if [[ -n "$VIRTUAL_ENV" ]]; then
deactivate
fi
fi
}
Add the function to the chpwd hook, which runs after every cd
add-zsh-hook chpwd venv_cd
Optionally, run the function once at the start of the session
venv_cd
This should automatically activate the virtualenv in the current directory if it exists, or deactivate it if you move to a directory without one.
Upvotes: 1
Reputation: 2214
You don't have to execute deactivate
on the directory where venv exists. When virtual environment is active you can deactivate anywhere.
So, say you have 2 venvs, <somepath>/project1/venv
and <somepath>/project2/venv
, and currently project1/venv
is active.
If you want to switch project2/venv
, do below.
cd project2
deactivate && source ./venv/bin/activate
It will deactivate previous one and activate current one.
Now the above you can just make an alias, or shell function in ~/.zshrc
as below:
function avenv(){
deactivate
source ./venv/bin/activate
}
go the path where you want to activate the venv and just run avenv
.
Python venv has a feature called --prompt, while creating venv you can mention the prompt, so that in the terminal you will understand which venv is active.
python3 -m venv venv --prompt PROJECT1_VENV
Now in terminal, it will come as (PROJECT1_VENV) -> ~
Upvotes: 0
Reputation: 11034
I tried direnv as suggested by others but found it a bit too opinionated and it didn't exactly do what I wanted.
The solution below is for fish shell users only. Also, it assumes a pyproject.toml
file and stdlib's venv
, but that can be easily changed.
The fish shell has the concept of event handlers so you can easily define a function that gets run whenever an "event" gets triggered (Unix signals, environment variables changing, etc). See this a blog post for a quick introduction.
Add the following to your ~/.config/fish/config.fish
(and modify as needed):
function venv_activate --on-variable PWD
if test ! -e pyproject.toml
if test -n "$VIRTUAL_ENV"
deactivate
end
return
end
# echo "Found pyproject.toml"
if test ! -d .venv
# echo "Creating $(python -V) venv"
python -m venv .venv --prompt (basename (pwd))
end
source .venv/bin/activate.fish
end
As a side note, the above works nicely with pyenv
's .python-version
files as well.
Upvotes: 1
Reputation: 535
Similar to Jake's answer but supports cd
ing 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
}
Upvotes: 2
Reputation: 166
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.
Upvotes: 13
Reputation: 9441
By far the easiest option (in 2019+) is to add virtualenvwrapper
into your ~/.zshrc
plugins
For example:
plugins=(
git pip python brew virtualenvwrapper
)
Upvotes: 17
Reputation: 421
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
}
Upvotes: 0
Reputation: 975
This is my solution:
/venv
part) and verify that the deactivate command exists
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)
Upvotes: 0
Reputation: 851
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.
Upvotes: 66
Reputation: 624
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
Upvotes: 1
Reputation: 61
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")
Upvotes: 4
Reputation: 5167
For posterity: I used @MS_'s solution but ran into the problem where cd
ing 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
}
Upvotes: 5
Reputation: 41945
This is my solution, which:
venv
, and do nothing in that casevenv
folder, deactivate the active one if there is onevenv
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
}
Upvotes: 0
Reputation: 757
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
Upvotes: 20
Reputation: 13038
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.
Upvotes: 19