Bonjour à tous,
Il ne me semble pas avoir vu passer de journal sur la distribution de paquets Python sur LinuxFR, du coup je vous propose un petit tuto sur un mode de distribution que je trouve fort sympathique en plus de devenir la référence.
Tout d’abord le site du projet : https://pythonwheels.com/
Et un petit résumé, traduit, depuis les information du site en question et au sujet de ce que j’ai effectivement pu tester :
- Installation plus rapide des paquets Python, avec support des extensions en C
- Évite d’exécuter du code à l’installation, notemment depuis le setup.py. [note perso: j’en ai déjà fais les frais : le mainteneur avait oublié d’inclure un fichier dans le paquet source]
- Pas besoin d’avoir un compilateur pour installer les paquets nécessitant une extension en C
- Les installations sont plus uniformes étant donné qu’on ne compile plus
Les paquets Python sont usuellement fournis avec un zip, une tarball ou encore directement depuis un dépôt git par exemple.
Et quand vous voulez installer vos dépendances, il est préférable d’utiliser un fichier requirements.txt
contenant une liste du genre :
paquet==3.2.1
moumoule==1.2.3
Mais saviez-vous que vous pouvez vous-même construire votre propre dépôt de paquets Python ?
Pour notre exemple, nous allons créer un fichier ~/requirements.txt
comme suit :
requests==2.18.4
pycurl==7.43.0.1
Le premier paquet est une bibliothèque absolument géniale permettant de faire des requêtes HTTP[S] avec tout un tas d’options super faciles à utiliser, écrite entièrement en Python.
Quant au deuxième paquet il s’agit d’un binding sur la bibliothèque cURL, cet autre super outil pour faire la même chose que requests
.
Aller, je ne vous fais pas plus attendre, de la commande à tapay avec des commentaires pour ceux qui ne sont pas familiers :
# Faisons nos cochonneries dans un « venv » Python, permettant d’isoler nos manipulations
virtualenv ~/buildenv
source ~/buildenv/bin/activate
# Assurons-nous que les outils sont à jour et installés
pip install --upgrade setuptools pip wheel
# Si vos dépendances ont besoin d’installer des paquets sur le système, faites donc
# Ici on ne devrait avoir besoin que de libcurl, mais selon votre distro, le nom du paquet va changer.
sudo apt install ...
sudo yum install ...
sudo pacman -S ...
sudo yenatrop install ...
# Créons notre « dépôt » de .whl
mkdir ~/binary-deps && cd ~/binary-deps
# Et maintenant lançons donc la récupération/construction des .whl
pip wheel -r ~/requirements.txt
Normalement tout s’est bien passé et la sortie de la dernière commande va ressembler à cela :
Collecting requests==2.18.4 (from -r /home/trublion/requirements.txt (line 1))
Downloading requests-2.18.4-py2.py3-none-any.whl (88kB)
100% |████████████████████████████████| 92kB 2.8MB/s
Saved ./requests-2.18.4-py2.py3-none-any.whl
Collecting pycurl==7.43.0.1 (from -r /home/trublion/requirements.txt (line 2))
Downloading pycurl-7.43.0.1.tar.gz (195kB)
100% |████████████████████████████████| 204kB 2.0MB/s
Collecting certifi>=2017.4.17 (from requests==2.18.4->-r /home/trublion/requirements.txt (line 1))
Downloading certifi-2018.1.18-py2.py3-none-any.whl (151kB)
100% |████████████████████████████████| 153kB 2.5MB/s
Saved ./certifi-2018.1.18-py2.py3-none-any.whl
Collecting urllib3<1.23,>=1.21.1 (from requests==2.18.4->-r /home/trublion/requirements.txt (line 1))
Downloading urllib3-1.22-py2.py3-none-any.whl (132kB)
100% |████████████████████████████████| 133kB 3.0MB/s
Saved ./urllib3-1.22-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests==2.18.4->-r /home/trublion/requirements.txt (line 1))
Downloading chardet-3.0.4-py2.py3-none-any.whl (133kB)
100% |████████████████████████████████| 143kB 2.8MB/s
Saved ./chardet-3.0.4-py2.py3-none-any.whl
Collecting idna<2.7,>=2.5 (from requests==2.18.4->-r /home/trublion/requirements.txt (line 1))
Downloading idna-2.6-py2.py3-none-any.whl (56kB)
100% |████████████████████████████████| 61kB 4.3MB/s
Saved ./idna-2.6-py2.py3-none-any.whl
Skipping requests, due to already being wheel.
Skipping certifi, due to already being wheel.
Skipping urllib3, due to already being wheel.
Skipping chardet, due to already being wheel.
Skipping idna, due to already being wheel.
Building wheels for collected packages: pycurl
Running setup.py bdist_wheel for pycurl ... done
Stored in directory: /home/trublion/binary-deps
Successfully built pycurl
Et si maintenant vous faites un ls
, vous aurez ceci :
certifi-2018.1.18-py2.py3-none-any.whl chardet-3.0.4-py2.py3-none-any.whl idna-2.6-py2.py3-none-any.whl pycurl-7.43.0.1-cp36-cp36m-linux_x86_64.whl requests-2.18.4-py2.py3-none-any.whl urllib3-1.22-py2.py3-none-any.whl
Et je pense que nous allons nous attarder sur les noms de ces fichiers :
-
py2
etpy3
font référence aux versions de Python pour lesquels ce wheel est compatible -
none-any
nous indique que toutes les plateformes (linux, windows, macos… plan9 ?) sont supportées : ce sont des paquets où il n’y a besoin que de Python pour s’exécuter, et donc indépendant de la plateforme cible. - En revanche lorsqu’on voit
cp36
etlinux-x86_64
, comme dans le cas du paquetpycurl
, c’est que le wheel généré n’est compatible qu’avec Python 3.6, sur une plateforme Linux x86_64.
Quant à cp36m
je ne sais pas ce que cela signifie, et j’ai un peu la flemme d’aller chercher, je l’avoue.
Et comment utilise-t’on un tel dépôt ? C’est très simple :
virtualenv ~/production
source ~/production/bin/activate
cd ~/binary-deps
pip install --no-index -f file://$(pwd) -r ~/requirements.txt
Pour cette démonstration vous ne verrez que peu de différence sur la question de la vitesse d’installation des .whl. C’est quand vous commencez à avoir quelques dépendances sur des bibliothèques en C/C++ que cela devient intéressant, comme avec le paquet xmlsec
.
En somme, pour vous simplifier la vie, retenez que lorsque vous faites un dépôt de wheel ce dépôt ne fonctionnera que pour la plateforme sur laquelle il a été construit.
Dans ma boîte, cela fait passer le temps de construction de plusieurs minutes à seulement une dizaine de secondes tout au plus, c’est extrêmement agréable.
Bon weekend à tous !
# Précision - compatibilité binaire
Posté par Chmoldu . Évalué à 6.
Les noms des .whl expliqué par la PEP 427 : https://www.python.org/dev/peps/pep-0427/#file-name-convention
# Versionning absolue des dépendances considered harmful
Posté par Firwen (site web personnel) . Évalué à 3.
Un petite note sur ça :
Ne JAMAIS au grand JAMAIS exprimer ses dépendances en requirement absolue sur une version particulière (==) mais toujours utiliser filtrage sur une version minimal compatible (>= ).
La dépendance sur une version absolue te garantie une explosion nucléaire dés que tu auras une dépendance en diamant quelque part. (e.g paquet A requiert C == 0.04 et paquet B requiert C == 0.05 )
C'est de mon expérience, une des plus mauvaise pratique et presque malédiction du packaging en python.
[^] # Re: Versionning absolue des dépendances considered harmful
Posté par Chmoldu . Évalué à 3. Dernière modification le 05 mars 2018 à 11:44.
Oui… et pas tout à fait.
Oui dans le cas d’une bibliothèque que l’on va partager, ici ça n’était pas le cas, mais je ne l’ai pas précisé. Il s’agissait d’une produit final où on veut maîtriser les versions utilisées.
Et pas tout à fait car à mon sens il vaudrait mieux utiliser <N+1 pour garantir que l’on va rester avec une dépendance compatible.
Exemple :
On utilise une version 1.X.Y d’une lib. Celle-ci passe en version 2.0.0 et le changelog indique que certaines API sont cassées. Que fait-on ?
[^] # Re: Versionning absolue des dépendances considered harmful
Posté par Albert_ . Évalué à 2.
Je sais que c'est souvent donne comme conseil de bloquer la version des dependances mais cela me pose toujours probleme.
En gros si tu bloques la version et qu'elle a un trou de securite enorme corrige plus tard tu peux te trouver a propager des trucs pas beaux.
Par contre si tu ne mets pas de version ou que tu mets ce que toi tu preconises (ce qui revient au meme car pas de version == derniere version dispo sur le depot), tu peux te trouver avec des API change qui casse ton paquet.
# cache
Posté par saimn . Évalué à 1.
A noter que
pip install
créé automatiquement des wheels pour les paquets qui ne sont pas (encore) distribués comme tel, et les met en cache localement. Donc la méthode présentée ici n'est utile que si on veut partager ce répertoire sur plusieurs machines (et encore, pas sur que les wheel linux - avec extension C - compilées ici soient compatibles sur n'importe quelle machine, dans ce cas il faut faire des wheels "manylinux1").[^] # Re: cache
Posté par Chmoldu . Évalué à 1.
C’est précisément l’objectif ici, se créer un dépôt que l’on conserve pour refaire des installes/images docker plus rapidement.
Par contre pour que
pip install
mette en cache les wheel il faut que le paquetwheel
soit installé si je ne m’abuse ?Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.