Journal Envoyer des Python à roues

Posté par  . Licence CC By‑SA.
Étiquettes :
25
3
mar.
2018

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 et py3 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 et linux-x86_64, comme dans le cas du paquet pycurl, 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  . É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  (site web personnel) . Évalué à 3.

    Un petite note sur ça :

    paquet==3.2.1
    moumoule==1.2.3

    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  . É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  . É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  . É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  . É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 paquet wheel 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.