DEV Community

Serhat Teker
Serhat Teker

Posted on • Originally published at tech.serhatteker.com on

Automate Python Virtual Environment with a Script

Issue

There are 2 things that annoy me every time when I create a python virtual environment.

NOTE

Below instructions are related to the python's default venv module.

If you are using any other version/virtual environment management tools,
like Conda, Poetry, Pyenv, below doesn't apply.

The first one is that I have to type every time:

$ python -m venv ./.venv
$ . ./.venv/bin/activate
# or
# $ source ./.venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

The second thing is that after installing any package I'm getting below pip
version WARNING:

WARNING: You are using pip version 21.1.1; however, version 22.0.4 is available. You should consider upgrading via the '/path/to/project/.venv/bin/python3.8 -m pip install --upgrade pip' command.
Enter fullscreen mode Exit fullscreen mode

It is an expected behavior, 'cause python -m venv calls python -m ensurepip to install pip.

And there is no way to globally upgrade since pip is installed with bundled version which is almost out of date. You can check it like below:

>>> import ensurepip
>>> ensurepip.version()
'21.1.1'
Enter fullscreen mode Exit fullscreen mode

Even though you can't upgrade to the latest version with below commands as mentioned in ensurepip doc:

$ python -m ensurepip --upgrade
Enter fullscreen mode Exit fullscreen mode

Because ensurepip will only install the bundled version even with the --upgrade option.

There is no official option to update the bundled pip and setuptools, yet.

Solution

Okay, since we are developers, we always find our ways and produce our solutions that suits our needs.

My solution comes from a shell script. You can put it in your shell configuration file, like .zshrc, .bashrc, config.fish.

With below function, when I enter ve command, it will create my virtual environment — if it doesn't exist, activate it and upgrade to the latest pip version.

# 0. If not already in virtualenv:
# 0.1. If virtualenv already exists activate it,
# 0.2. If not create it with global packages, update pip then activate it
# 1. If already in virtualenv: just give info
#
# Usage:
# $ ve
# or
# $ ve python3.9
# or
# $ ve python3.9 ./.venv-diff
ve() {
    local py=${1:-python3.8}
    local venv="${2:-./.venv}"

    local bin="${venv}/bin/activate"

    # If not already in virtualenv
    # $VIRTUAL_ENV is being set from $venv/bin/activate script
      if [ -z "${VIRTUAL_ENV}" ]; then
        if [ ! -d ${venv} ]; then
            echo "Creating and activating virtual environment ${venv}"
            ${py} -m venv ${venv} --system-site-package
            echo "export PYTHON=${py}" >> ${bin}    # overwrite ${python} on .zshenv
            source ${bin}
            echo "Upgrading pip"
            ${py} -m pip install --upgrade pip
        else
            echo "Virtual environment  ${venv} already exists, activating..."
            source ${bin}
        fi
    else
        echo "Already in a virtual environment!"
    fi
}
Enter fullscreen mode Exit fullscreen mode

This function may seem complex, so let me walk through it line by line.

$1 is the first argument, the desired python version, $2 is the second argument which is virtual environment's name/directory, provided to the function ve(). If no arguments provided function will use default values: python3.8 and .venv. You can these default values according to your needs.

ve() {
    local py=${1:-python3.8}
    local venv="${2:-./.venv}"

    local bin="${venv}/bin/activate"
    ...
}
Enter fullscreen mode Exit fullscreen mode

You can provide positional arguments like:

$ ve python3.9 .venv2
Enter fullscreen mode Exit fullscreen mode

After creating local variables, if we are already in a virtual environment, it just gives "already in a virtual environment" message. It decides this whether we're in a virtual environment by checking if there is a environment variable called $VIRTUAL_ENV exported from activate.venv/bin/activate, script.

ve() {
    ...

    if [ -z "${VIRTUAL_ENV}" ]; then
        ...
    else
        echo "Already in a virtual environment!"
    fi
}
Enter fullscreen mode Exit fullscreen mode

If we're not in a virtual environment, it will control if .venv directory exists. If the directory exists, it will be activated:

ve() {
    ...

    if [ -z "${VIRTUAL_ENV}" ]; then
        if [ ! -d ${venv} ]; then
            ...
        else
            echo "Virtual environment  ${venv} already exists, activating..."
            source ${bin}
        fi

    else
        ...
    fi
}
Enter fullscreen mode Exit fullscreen mode

However, if .venv directory does not exist, it will:

  • Create virtual environment .venv with global packages included (line 7),
  • Add exporting desired python version environment variable into activate script (line 8)
  • Activate the virtual environment (line 9)
  • Upgrade the current pip version to the latest one (line 11)
ve() {
    ...

    if [ -z "${VIRTUAL_ENV}" ]; then
        if [ ! -d ${venv} ]; then
            echo "Creating and activating virtual environment ${venv}"
            ${py} -m venv ${venv} --system-site-package
            echo "export PYTHON=${py}" >> ${bin}    # overwrite ${python} on .zshenv
            source ${bin}
            echo "Upgrading pip"
            ${py} -m pip install --upgrade pip
        else
            ...
        fi

    else
        ...
    fi
}
Enter fullscreen mode Exit fullscreen mode

You probably don't need line 8, I'm adding this, because using this environment variable in my other aliases/functions/binaries.

Conclusion

In the end, with this shell script we solved our annoying bugs and make us more productive again.

You can find this script in this gist or in function from my dotfiles repo.

All done!


Changelog

  • 2022-05-10 : Added gist and dotfiles link references

surepip

Discussion (0)