Python - Comprendre et démystifier virtualenv

Introduction

Avec Python, lors de l'installation d'un produit, par exemple Alerta server, beaucoup de dépendances peuvent être aussi installées.

python@vpsfrsqlpac2$ pip3 search alerta
alerta (7.4.0)              - Alerta unified command-line tool and SDK
alerta-server (7.4.1)       - Alerta server WSGI application
...
python@vpsfrsqlpac2$ pip3 install alerta-server
Installing collected packages: pymongo, certifi, urllib3, chardet, idna, requests,
MarkupSafe, Jinja2, Werkzeug, itsdangerous, click, Flask, blinker, sentry-sdk, Flask-Compress,
pyyaml, six, python-dateutil, pycparser, cffi, bcrypt, cryptography, pyparsing, Flask-Cors,
PyJWT, pytz, alerta-server
  Running setup.py install for MarkupSafe ... done
  Running setup.py install for blinker ... done
  Running setup.py install for Flask-Compress ... done
  Running setup.py install for pyyaml ... done
  Running setup.py install for pycparser ... done
Successfully installed Flask-1.1.1 Flask-Compress-1.4.0 Flask-Cors-3.0.8 Jinja2-2.10.3
MarkupSafe-1.1.1 PyJWT-1.7.1 Werkzeug-0.16.0 alerta-server-7.4.1 bcrypt-3.1.7 blinker-1.4
certifi-2019.11.28 cffi-1.13.2 chardet-3.0.4 click-7.0 cryptography-2.8 idna-2.8
itsdangerous-1.1.0 pycparser-2.19 pymongo-3.10.1 pyparsing-2.4.6 python-dateutil-2.8.1
pytz-2019.3 pyyaml-5.3 requests-2.22.0 sentry-sdk-0.14.0 six-1.13.0 urllib3-1.25.7

Il devient fastidieux de gérer la pollution de la distribution globale Python: dépendances, conflits de versions de packages, binaires ... De plus, certains packages peuvent être nécessaires pour un seul utilisateur/produit. Par défaut, tous les packages et les dépendances sont téléchargés et installés dans $PYTHON_HOME/lib/python<version>/site-packages.

Le package Python virtualenv résout ce type de problème. Un utilisateur peut gérer et exécuter son propre environnement isolé sans aucune installation de package dans la distribution Python globale. L'environnement virtuel est indépendant de la distribution source Python, cependant une option permet l'utilisation des packages installés dans la distribution source.

Dans le schéma ci-dessous :

  • L'environnement virtuel py-influxdb est complètement isolé et 2 packages y sont installés.
  • Dans l'environnement virtuel py-alerta pour l'utilisateur alerta, les packages alerta sont installés, mais l'environnement peut aussi utiliser les packages installés dans la distribution système (PyMySQL, psycopg2, pymongo).
Architecture Python - Environnements virtuels

Ainsi, les packages utilisés par la plupart des utilisateurs peuvent être installés dans la distribution système, et les packages nécessaires à un seul utilisateur/produit dans un environnement virtuel avec virtualenv.

Voyons comment créer et utiliser des environnements virtuels, et comment les packages et versions sont gérés dans les distributions virtuelles et système.

Contexte

Dans cet article, Python 3.8 a été compilé et installé dans le répertoire personnalisé /opt/python/python-3.8. Notre philosophie : aucune installation dans les répertoires systèmes du système d'exploitation (/usr …).

Le compte python (groupe : wapp) est le propriétaire de la distribution système /opt/python/python-3.8.

L'environnement Python 3.8 est chargé en sourçant le fichier $HOME/.python-3.8 ci-dessous.

$HOME/.python-3.8
#!/bin/bash
export PYHOME=/opt/python/python-3.8
export PATH=$PYHOME/bin:$PATH
export LD_LIBRARY_PATH=$PYHOME/lib:$LD_LIBRARY_PATH
export PYTHONPATH=/opt/python/packages

export PGLIB=/opt/postgres/pgsql-11/lib
export LD_LIBRARY_PATH=$PGLIB:$LD_LIBRARY_PATH
python@vpsfrsqlpac2$ . $HOME/.python-3.8
python@vpsfrsqlpac2$ which python3
python@vpsfrsqlpac2$ which pip3
/opt/python/python-3.8/bin/python3
/opt/python/python-3.8/bin/pip3

La variable d'environnement personnalisée $PYHOME spécifie le répertoire racine de Python 3.8 (/opt/python/python-3.8).

Installation de virtualenv

Si ce n'est déjà fait, installer le package virtualenv dans la distribution globale avec pip3 :

python@vpsfrsqlpac2$ pip3 install virtualenv

Collecting virtualenv
  Downloading virtualenv-16.7.9-py2.py3-none-any.whl (3.4 MB)
     |..............................| 3.4 MB 4.3 MB/s
Installing collected packages: virtualenv
Successfully installed virtualenv-16.7.9

virtualenv est installé dans le répertoire $PYHOME/bin.

Créer et utiliser des environnements virtuels avec virtualenv

Créer un référentiel où tous les environnements virtuels seront installés. Il s'agira d'un simple répertoire créé en dehors des distributions python :

python@vpsfrsqlpac2$ cd /opt/python
python@vpsfrsqlpac2$ mkdir venv

La variable d'environnement $PYVENV identifie ce répertoire.

python@vpsfrsqlpac2$ export PYVENV=/opt/python/venv
                    

Créons l'environnement virtuel py-alerta qui sera utilisé par le compte alerta :

python@vpsfrsqlpac2$ virtualenv $PYVENV/py-alerta

Using base prefix '/opt/python/python-3.8'
New python executable in /opt/python/venv/py-alerta/bin/python3.8
Also creating executable in /opt/python/venv/py-alerta/bin/python
Installing setuptools, pip, wheel...
done.

L'interpréteur Python (python3...) est copié dans le répertoire $PYVENV/py-alerta/bin. Le répertoire $PYVENV/py-alerta/lib/python3.8 est également préparé à partir de $PYHOME/lib/python3.8 pour l'installation des packages puis les packages pip et setuptools y sont copiés. La commande peut être exécutée plusieurs fois, cela n'altère pas les packages éventuellement déjà existants sauf si l'option --clear a été utilisée, seuls les binaires et scripts source sont écrasés (python3, pip3, activate...)

Exécuter le script $PYVENV/py-alerta/bin/activate pour utiliser l'environnement virtuel :

python@vpsfrsqlpac2$ source $PYVENV/py-alerta/bin/activate
          
(py-alerta) python@vpsfrsqlpac2:~$

L'invite est modifiée avec le nom de l'environnement virtuel (préfixe). La variable d'environnement $PATH est modifiée en conséquence :

(py-alerta) python@vpsfrsqlpac2:~$ echo $PATH
/opt/python/venv/py-alerta/bin:/opt/python/python-3.8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Les exécutables python3 et pip3 par défaut sont ceux de l'environnement virtuel.

(py-alerta) python@vpsfrsqlpac2:~$ which python3
(py-alerta) python@vpsfrsqlpac2:~$ which pip3
        
/opt/python/venv/py-alerta/bin/python3
/opt/python/venv/py-alerta/bin/pip3

En utilisant Python, le path est également modifié en conséquence pour les chemins d'installation des packages :

(py-alerta) python@vpsfrsqlpac2:~$ python3

import sys
sys.path

['', '/opt/python/packages', '/opt/python/venv/py-alerta/lib/python38.zip',
'/opt/python/venv/py-alerta/lib/python3.8', '/opt/python/venv/py-alerta/lib/python3.8/lib-dynload',
'/opt/python/python-3.8/lib/python3.8', '/opt/python/venv/py-alerta/lib/python3.8/site-packages']

Dans l'environnement virtuel, les packages seront installés dans le répertoire /opt/python/venv/py-alerta/lib/python3.8/site-packages et les binaires dans le répertoire /opt/python/venv/py-alerta/bin.

Exemple : installation du package chardet dans l'environnement virtuel py-alerta.

(py-alerta) python@vpsfrsqlpac2:~$ pip3 install chardet

Collecting chardet
  Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Installing collected packages: chardet
Successfully installed chardet-3.0.4
        
(py-alerta) python@vpsfrsqlpac2:~$ pip3 show chardet
Name: chardet
Version: 3.0.4
Summary: Universal encoding detector for Python 2 and 3
Home-page: https://github.com/chardet/chardet
Author: Daniel Blanchard
Author-email: dan.blanchard@gmail.com
License: LGPL
Location: /opt/python/venv/py-alerta/lib/python3.8/site-packages
Requires:
Required-by:

Les environnements virtuels n'altèrent pas toute définition existante dans la variable d'environnement $PYTHONPATH.

Pour quitter l'environnement virtuel, lancer la fonction deactivate, fonction créée après avoir exécuté le script activate.

(py-alerta) python@vpsfrsqlpac2:~$ deactivate
        
python@vpsfrsqlpac2:~$

L'environnement est alors de retour à la distribution globale Python 3.8.

python@vpsfrsqlpac2:~$ which python3
        
/opt/python/python-3.8/bin/python3

Les environnements virtuels et les packages systèmes

Qu'en est-il d'un package déjà installé dans la distribution système ? Par exemple, PyMySQL est installé pour être disponible par défaut :

python@vpsfrsqlpac2:~$ pip3 list
        
Package    Version
---------- -------
PyMySQL    0.9.3

Évidemment, ce package ne sera pas disponible dans l'environnement virtuel. Il faut utiliser l'option --system-site-packages lors de la création de l'environnement virtuel afin de pouvoir utiliser les packages installés dans la distribution source globale :

python@vpsfrsqlpac2:~$ virtualenv --system-site-packages $PYVENV/py-alerta
python@vpsfrsqlpac2:~$ source $PYVENV/py-alerta/bin/activate

(py-alerta) python@vpsfrsqlpac2:~$ pip3 list

Package    Version
---------- -------
PyMySQL    0.9.3

Le répertoire $PYHOME/lib/python<version>/site-packages est ajouté dans le path avec l'option --system-site-packages :

(py-alerta) python@vpsfrsqlpac2:~$ python3

import sys
sys.path
['', '/opt/python/packages', '/opt/python/venv/py-alerta/lib/python38.zip', '/opt/python/venv/py-alerta/lib/python3.8',
'/opt/python/venv/py-alerta/lib/python3.8/lib-dynload', '/opt/python/python-3.8/lib/python3.8',
'/opt/python/venv/py-alerta/lib/python3.8/site-packages', '/opt/python/.local/lib/python3.8/site-packages',
'/opt/python/python-3.8/lib/python3.8/site-packages']

Aucune crainte à avoir niveau sécurité, l'environnement virtuel sait quand il n'est pas le propriétaire d'un package, essayer de supprimer un package de la distribution système depuis un environnement virtuel échoue :

(py-alerta) python@vpsfrsqlpac2:~$ pip3 uninstall PyMySQL

Found existing installation: PyMySQL 0.9.3
Not uninstalling pymysql at /opt/python/python-3.8/lib/python3.8/site-packages, outside environment /opt/python/venv/py-alerta
Can't uninstall 'PyMySQL'. No files were found to uninstall.

Pour les mises à niveau (upgrades), c'est légèrement différent. Dans l'exemple ci-dessous, le package chardet 3.0.0 est installé dans le référentiel système :

(py-alerta) python@vpsfrsqlpac2:~$ pip3 list

Package    Version
---------- -------
chardet    3.0.0

La mise à niveau échoue en essayant de supprimer le package système, en revanche la mise à niveau installe la nouvelle version dans l'environnement virtuel :

(py-alerta) python@vpsfrsqlpac2:~$ pip3 install --upgrade chardet

Collecting chardet
  Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Installing collected packages: chardet
  Attempting uninstall: chardet
    Found existing installation: chardet 3.0.0
    Not uninstalling chardet at /opt/python/python-3.8/lib/python3.8/site-packages, outside environment /opt/python/venv/py-alerta
    Can't uninstall 'chardet'. No files were found to uninstall.
Successfully installed chardet-3.0.4
(py-alerta) python@vpsfrsqlpac2:~$ pip3 list

Package    Version
---------- -------
chardet    3.0.4

L'environnement virtuel utilise de nouveau celui du système lorsque le package est supprimé de l'environnement virtuel :

(py-alerta) python@vpsfrsqlpac2:~$ pip3 uninstall chardet

Found existing installation: chardet 3.0.4
Uninstalling chardet-3.0.4:
  Would remove:
    /opt/python/venv/py-alerta/bin/chardetect
    /opt/python/venv/py-alerta/lib/python3.8/site-packages/chardet-3.0.4.dist-info/*
    /opt/python/venv/py-alerta/lib/python3.8/site-packages/chardet/*
Proceed (y/n)? y
  Successfully uninstalled chardet-3.0.4
(py-alerta) python@vpsfrsqlpac2:~$ pip3 list

Package    Version
---------- -------
chardet    3.0.0

Une version spécifique peut être installée dans l'environnement virtuel et elle a priorité sur la version système, la version peut même être inférieure à la version système :

(py-alerta) python@vpsfrsqlpac2:~$ pip3 install chardet==3.0.2

Collecting chardet==3.0.2
  Downloading chardet-3.0.2-py2.py3-none-any.whl (133 kB)
     |...........................| 133 kB 5.0 MB/s
Installing collected packages: chardet
  Attempting uninstall: chardet
    Found existing installation: chardet 3.0.0
    Not uninstalling chardet at /opt/python/python-3.8/lib/python3.8/site-packages, outside environment /opt/python/venv/py-alerta
    Can't uninstall 'chardet'. No files were found to uninstall.
Successfully installed chardet-3.0.2
(py-alerta) python@vpsfrsqlpac2:~$ pip3 list

Package    Version
---------- -------
chardet    3.0.2

Quand la version d'un package système ne répond pas aux pré-requis, la version appropriée est installée dans l'environnement virtuel : par exemple le package alerta-server a besoin de chardet <3.1.0,>=3.0.2, mais la version système est 3.0.0.

(py-alerta) python@vpsfrsqlpac2:~$ pip3 install alerta-server
...
Collecting chardet<3.1.0,>=3.0.2
  Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB)
...
  Attempting uninstall: chardet
    Found existing installation: chardet 3.0.0
    Not uninstalling chardet at /opt/python/python-3.8/lib/python3.8/site-packages, outside environment /opt/python/venv/py-alerta
    Can't uninstall 'chardet'. No files were found to uninstall.
Successfully installed Flask-1.1.1 Flask-Compress-1.4.0 Flask-Cors-3.0.8 Jinja2-2.10.3 MarkupSafe-1.1.1 PyJWT-1.7.1 Werkzeug-0.16.0
alerta-server-7.4.1 bcrypt-3.1.7 blinker-1.4 certifi-2019.11.28 cffi-1.13.2 chardet-3.0.4 click-7.0 cryptography-2.8 idna-2.8
itsdangerous-1.1.0 pycparser-2.19 pymongo-3.10.1 pyparsing-2.4.6 python-dateutil-2.8.1 pytz-2019.3 pyyaml-5.3 requests-2.22.0
sentry-sdk-0.14.1 six-1.14.0 urllib3-1.25.8
(py-alerta) python@vpsfrsqlpac2:~$ pip3 list

Package    Version
---------- -------
chardet    3.0.4

Compilation de librairies

Certains packages ont besoin de compiler des librairies (*.so): psycopg2 (package Python pour PostgreSQL).

Pas de problème particulier pour compiler des librairies dans un environnement virtuel.

(py-alerta) python@vpsfrsqlpac2:$ export PATH=/opt/postgres/pgsql-11/bin:$PATH
        
(py-alerta) python@vpsfrsqlpac2:$ pip3 install psycopg2

Collecting psycopg2
  Using cached psycopg2-2.8.4.tar.gz (377 kB)
Building wheels for collected packages: psycopg2
  Building wheel for psycopg2 (setup.py) ... done
  ...
Successfully built psycopg2
Installing collected packages: psycopg2
Successfully installed psycopg2-2.8.4

La librairie _psycopg.cpython-38-x86_64-linux-gnu.so est compilée dans le répertoire $PYVENV/py-alerta/lib/python3.8/site-packages/psycopg2.

(py-alerta) python@vpsfrsqlpac2:$ python3

import psycopg2

Il faut juste s'assurer que la variable d'environnement $LD_LIBRARY_PATH contient le chemin vers la librairie libpq.so.5 si elle n'est pas installée dans les répertoires systèmes (/usr). Pas de différence par rapport à la procédure d'installation dans une distribution Python classique.

export LD_LIBRARY_PATH=/opt/postgres/pgsql-11/lib:$LD_LIBRARY_PATH

Environnement virtuel Python pour Alerta

À présent, créons un environnement virtuel complet pour Alerta, les packages nécessaires sont très nombreux et on ne souhaite pas installer l'intégralité de ceux-ci dans la distribution globale système Python. L'option --system-site-packages est utilisée, Alerta a besoin de psycopg2.

python@vpsfrsqlpac2$ virtualenv --system-site-packages $PYVENV/py-alerta
        
python@vpsfrsqlpac2$ source $PYVENV/py-alerta/bin/activate
        
(py-alerta) python@vpsfrsqlpac2:~/venv$ pip3 install alerta-server
        
(py-alerta) python@vpsfrsqlpac2:~/venv$ pip3 list

Package         Version
--------------- ----------
alerta-server   7.4.1
bcrypt          3.1.7
blinker         1.4
certifi         2019.11.28 ...

Le package "Alerta unified command-line tool and SDK" est également installé :

(py-alerta) python@vpsfrsqlpac2:~/venv$ pip3 install alerta
        
(py-alerta) python@vpsfrsqlpac2:~/venv$ pip3 list

Package         Version
--------------- ----------
alerta          7.4.0
alerta-server   7.4.1 ...

Les exécutables alertad (Alerta daemon server) et alerta (Alerta command line) sont alors bien installés dans $PYVENV/py-alerta/bin.

Le script $HOME/.profile pour le user alerta est mis à jour afin d'y ajouter l'activation de l'environnement virtuel py-alerta :

$HOME/.profile (/opt/alerta/.profile)
if [ -f "/opt/python/.python-3.8" ] ; then
        . /opt/python/.python-3.8

        if [ -f $PYVENV/py-${USER}/bin/activate ] ; then
                source $PYVENV/py-$USER/bin/activate
        fi
fi

La commande pip3 list retourne les bonnes informations concernant les packages :

alerta@vpsfrsqlpac2$ . $HOME/.profile

(py-alerta) alerta@vpsfrsqlpac2$ pip3 list

Package         Version
--------------- ----------
alerta          7.4.0
alerta-server   7.4.1 ...

Tout est prêt :

(py-alerta) alerta@vpsfrsqlpac2:~$ alertad run --port 20003 --host vpsfrsqlpac2
        
2020-01-24 16:50:15,311 werkzeug[1944]:  * Running on http://vpsfrsqlpac2:20003/ (Press CTRL+C to quit)

Conclusion

  • Les packages les plus communs (PyMySQL, psycopg2...) sont installés dans la distribution système Python.
  • Les environnements virtuels Python sont utilisés lorsque des packages "exotiques" sont nécessaires pour très peu d'utilisateurs, cela évite de polluer la distribution système avec conflits de versions et divergences pour d'autres packages et applications.
  • Les problèmes de versions de package peuvent être résolus à l'aide d'environnements virtuels Python.
  • Les environnements virtuels Python sont très utiles pour tester les installations de dépendances et à des fins de développement.