python

Table of content

Virtual Environment or venv

A Virtual Environment (also called venv) helps you keeping your main system clean from packages as these only live in the venv and gives you also the benefit, that you can use different python versions on the same system.

To being able to create a venv, you have to make sure, that you python installation is able to do so. For systems which are using Debain, you can simple do that by installing the python package(s) which are ending with -venv, for example:

$ apt install python3-venv      # will give you the latest python3 venv support
$ apt install python3.12-venv   # for a specific minor verion

Creation

To create a venv, simply run the following command:

$ python -m venv /home/myuser/python_venvs/project1

This can be also done with a specific python version:

$ python3.12 -m venv /home/myuser/python_venvs/project2

Enable and disable the venv

After you have sucessfully created the venv, you have to enable/activate it.

This is done with the simple command:

$ source /home/myuser/python_venvs/project2/bin/activate

In your PS1 you will then see that the name (basename) of the venv dir path is placed in addition. Depending on what you are using, it can be at the beginning like wiht a standard PS1 (venv_basename)user@host:~$ or if you have for examplethe powerline, it will be right after the hostname (if you did not reorder it).

To disable/deactiate it, use the command deactivate which got added while you sourced the bin/activate file.

Update to higher python version

To upgrade an existing venv, we just run the creation command again, specify the pythion version using the correct binary and add the parameter --upgrade

$ python3.13 -m venv --upgrade /home/myuser/python_venvs/project2

This will add to the /home/myuser/python_venvs/project2/bin path the specified python3.13 binarie (manualy re-link project2/bin/python and project2/bin/python3 to the new one), updates the pip binary (also and updates the pyvenv.cfg.

Old unsupported python versions

Somtimes you have the need, that you have to run a old pythion version which is not supported by your OS version any more.

There it comes handy to use a container, which will spin up an older version of your OS and allows you to intall then the old python version.

Of crouse you don’t want to move all your content/additional applications/scripts/configs and so on into the container, so lets make the venv which is available in the container to the host system.

We already assume, that you have deployed the container, installed python and created a venv with the same user as on the host system beneath its home inside the dir python-venv.

In this sample, we need it to support an older ansible version as well as a python3.7 on the clients.

As container system we use incus (give it a try, it is worth it)

This file is also only ment to be sourced and not executed!

# do not exeute this file, only source it
#
# added functions to shell:
#   - actiavte_ansible
#   - deactivate_ansible

eash_itsa_me="$(whoami)"
eash_incus_path="<<< PATH TO YOUR CONTAINERS >>>"
eash_incus_container="<<< CONTAINER NAMME >>>"
eash_python_venv_main_path="${HOME}/python-venv"
eash_python_main_version="3"
eash_python_minor_version="11"
eash_python_version="${eash_python_main_version}.${eash_python_minor_version}"
eash_python_path="${eash_python_venv_main_path}/${eash_python_version}"
eash_python_bin="/usr/bin/python${eash_python_main_version}"

function deactivate_ansible() {
    alias ansible-playbook="ansible-playbook -e \"ansible_python_interpreter=/usr/bin/python${eash_python_main_version}\""
    echo -n "[i] Restored backup alias in ansible-playbook: " ; alias ansible-playbook

    if [[ -n "${VIRTUAL_ENV}" ]]; then
        deactivate
    else
        echo "[s] Python${eash_python_version} venv already deactivated"
    fi

    if findmnt "${eash_python_path}" &>/dev/null ; then
        sudo umount "${eash_python_path}"
        echo "[i] venv bind unmounted"
    else
        echo "[s] venv bind mount already unmounted"
    fi

    if findmnt "${HOME}/.ansible" &>/dev/null ; then
        sudo umount "${HOME}/.ansible"
        echo "[i] .ansible bind unmounted"
    else
        echo "[s] .ansible bind mount already unmounted"
    fi

    if readlink "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}" | grep -q -E "${eash_incus_path}" ; then
        rm -f "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}"
        mv "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}_incus" "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}"
        echo "[i] Restored incus python${eash_python_version} version using internal container link"
    else
        echo "[s] Incus python${eash_python_version} venv bin internal link in place"
    fi

    if readlink "${eash_python_bin}" | grep -q -E "${eash_python_version}$" ; then
        sudo rm -f "${eash_python_bin}"
        sudo mv "${eash_python_bin}"_bk "${eash_python_bin}"
        echo -n "[i] Restored old python binary: " ; readlink "${eash_python_bin}"
    else
        echo "[s] Python binary already set back to default"
    fi

    if incus list "${eash_incus_container}" | grep -q RUNNING ; then
        incus stop "${eash_incus_container}"
        echo "[i] Stopped incus container(${eash_incus_container})"
    else
        echo "[s] Incus container(${eash_incus_container}) already stopped"
    fi
}

function activate_ansible() {
    alias ansible-playbook="${eash_python_path}/bin/ansible-playbook -e \"ansible_python_interpreter=/usr/bin/python${eash_python_main_version}\""
    echo -n "[i] Enabled new alias to use python incus venv: " ; alias ansible-playbook

    if incus list "${eash_incus_container}" | grep -q RUNNING ; then
        echo "[s] Incus container(${eash_incus_container}) already running"
    else
        incus start "${eash_incus_container}"
        echo "[i] Started incus container(${eash_incus_container})"
    fi
    sudo chgrp incus "${eash_incus_path}/${eash_incus_container}"
    sudo chmod g+x "${eash_incus_path}/${eash_incus_container}"
    echo "[i] Placed group permissions for contains/fs"

    if findmnt "${eash_python_path}" &>/dev/null ; then
        echo "[s] venv bind mount in place"
    else
        sudo mount --bind -o "uid=${eash_itsa_me},gid=${eash_itsa_me}" "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}" "${eash_python_path}"
        echo "[i] venv established bind mount at"
    fi

    if findmnt "${HOME}/.ansible" &>/dev/null ; then
        echo "[s] .ansible bind mount in place"
    else
        sudo mount --bind -o "uid=${eash_itsa_me},gid=${eash_itsa_me}" "${eash_incus_path}/${eash_incus_container}/rootfs${HOME}/.ansible" "${HOME}/.ansible"
        echo "[i] .ansible established bind mount at"
    fi

    if readlink "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}" | grep -q -E "${eash_incus_path}" ; then
        echo "[s] Incus python${eash_python_version} venv bin link in place"
    else
        mv "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}" "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}_incus"
        ln -s "${eash_incus_path}/${eash_incus_container}/rootfs/usr/bin/python${eash_python_version}" "${eash_incus_path}/${eash_incus_container}/rootfs${eash_python_path}/bin/python${eash_python_version}"
        echo "[i] Enabled incus python${eash_python_version} version through external path"
    fi

    if readlink "${eash_python_bin}" | grep -q -E "${eash_python_version}$" ; then
        echo "[s] Local python binary already set to ${eash_python_version}"
    else
        sudo mv "${eash_python_bin}" "${eash_python_bin}_bk"
        sudo ln -s "$(sed -E "s/[0-9]$/${eash_python_version}/g" <<<"${eash_python_bin}")" "${eash_python_bin}"
        echo "[i] Enabled local python version ${eash_python_version} through link"
    fi

    if [[ -n "${VIRTUAL_ENV}" ]]; then
        echo "[s] Python${eash_python_version} venv already activated"
    else
        echo "[i] Activationg pythion venv"
        source "${eash_python_path}/bin/activate"
    fi
}

With this sourced to your bash/zsh you can call both functions, activate_ansible and deactiavte_ansible and they will prepare your host system so that it can use the venv from the container installed in incus.

Updates and changes to the venv, shold be ofcourse done when you are attached to the container, just to make sure that nothing gets messed up.

Documentaiton on how to pin a container and run commands (like installing creating the user, pip,…) will be added soon in the incus documentation.