Les outils de gestion de packages python
Python est langage fantastique, mais il y a un point qui laisse à désirer par rapport aux environnements plus récents comme node ou rust : le gestionnaire de package. Pip, le gestionnaire de package de python, quoique simple d’utilisation et fiable, pose rapidement des problèmes. On va voir dans cet article les projets qui essayent de proposer une solution plus robuste: Pip-tools, Pipenv et Poetry. Mais d’abord voyons les difficultés que l’on va immanquablement rencontrer avec Pip.
Les problèmes avec Pip
Pour déclarer les dépendances d’une application python, l’usage est de créer un fichier requirements.txt
listant tous
les packages nécessaires avec leur version
redis==2.10.6
rq==0.13
Pour spécifier la version, Pip propose plusieurs options:
- on peut mettre une version exacte (par exemple
redis==2.10.6
) - ou des bornes pour définir une plage de versions acceptables, par exemple
redis>=2.1.3,<3
accepte toutes les versions supérieures à la version 2.1.3 et inférieures à la version 3.0.0
Il est cependant vivement recommandé d’utiliser des versions exactes pour éviter que les versions sélectionnées par Pip changent sans prévenir au cours du temps.
Pour l’installation, on utilise ensuite la commande :
pip install -r requirements.txt
L’isolation des applications
Ici se pose le premier problème, Pip n’a pas de notion d’application ou de projet: si on utilise naïvement cette commande depuis deux applications, Pip va mixer les dépendances des deux applications et créer un système généralement inutilisable. Pour isoler chaque application, il est donc nécessaire d’utiliser un autre outil: virtualenv ce qui va compliquer tout de suite la création de l’environnement de développement et l’installation de l’application en production.
Les jeux de dépendances multiples
Un autre problème commun est de distinguer differents jeux de dépendances; un cas qui se pose pour tous les projets est de distinguer les dépendances de développement (comme le framework de test, les linters de code …) des dépendances nécessaires à l’execution de l’application en production. La solution la plus simple est d’utiliser plusieurs fichiers de dépendances et d’installer les fichiers un par un selon le besoin; c’est simple mais il faut s’assurer manuellement que tous les fichiers de dépendances sont compatibles.
Les mises à jour
Comme toutes les versions des packages doivent être manuellement spécifiées dans les fichiers requirements, il est très facile de créer des incompatibilités et Pip, s’il détecte le problème, n’aide pas du tout à le résoudre. Nous allons prendre cet exemple:
redis==2.10.6
rq==1.0
Ces deux packages sont incompatibles car rq dépend de redis>=3.0.0
. Voilà le comportement de Pip:
$ pip install -r requirements.txt
Collecting redis==2.10.6 (from -r requirements.txt (line 1))
Using cached https://files.pythonhosted.org/packages/3b/f6/7a76333cf0b9251ecf49efff635015171843d9b977e4ffcf59f9c4428052/redis-2.10.6-py2.py3-none-any.whl
Collecting rq==1.0 (from -r requirements.txt (line 2))
Using cached https://files.pythonhosted.org/packages/ee/f6/dbcf2a28e5621e1fcf6be6937da9777ad9ab03c7d3cb7d6ee835adc43329/rq-1.0-py2.py3-none-any.whl
Collecting click>=5.0 (from rq==1.0->-r requirements.txt (line 2))
Using cached https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl
ERROR: rq 1.0 has requirement redis>=3.0.0, but you'll have redis 2.10.6 which is incompatible.
Installing collected packages: redis, click, rq
Successfully installed click-7.0 redis-2.10.6 rq-1.0
Pip affiche bien un message d’erreur, mais le package rq est maintenant inutilisable et il va falloir trouver à la main, par essai-erreur, une combinaison de versions qui fonctionne.
On se retrouve généralement dans cette situation en essayant de faire une mise à jour : on modifie la version d’un package et la nouvelle version introduit une incompatibilité. Ce problème rend les mises à jour de versions dans les fichiers requirements pénibles et dangereuses. Cela n’incite bien sûr pas à les faire régulièrement.
Les packages résiduels
Au fil du temps, les dépendances d’une application vont évoluer : on va rajouter des dépendances mais aussi en supprimer
et Pip ne fournit aucun moyen utilisable pour supprimer une dépendance. Pip a bien une commande uninstall
mais :
- la commande ne supprime que le package demandé, pas ses dépendances puisqu’il ne peut pas déterminer si elles sont encore utilisées ou non
- même si on arrive à établir la liste des packages à supprimer il faut encore arriver à programmer les exécutions des
commandes
uninstall
sur tous les environnements de développement, test et production.
En pratique, le seul moyen de s’assurer que l’environnement ne contient pas de packages résiduels est de supprimer le virtualenv et de relancer l’installation complète. Ces packages résiduels peuvent induire deux types d’erreurs :
- on utilise sans s’en rendre compte un package non déclaré dans le
requirements.txt
et tout fonctionne jusqu’à ce que l’on crée un nouvel environnement - la simple présence d’un package peut altérer le comportement d’un autre : on peut donc avoir de subtiles différences de comportement entre les environnements amenées par la présence d’un package résiduel
Pip-tools
Ce projet est le plus ancien des trois et propose deux utilitaires: pip-compile
et pip-sync
.
Le principe de fonctionnement est le suivant :
-
on va créer un fichier de dépendances similaire au requirements.txt, mais au lieu de fixer des versions, on va utiliser des bornes. Par convention, on appelle ce fichier
requirements.in
:redis>=2.10.6,<3 rq>=0.8
-
On lance
pip-compile
. Il va rechercher un jeu de versions qui satisfait toutes les contraintes spécifiées dans lerequirements.in
et créer le fichierrequirements.txt
$ pip-compile requirements.in # # This file is autogenerated by pip-compile # To update, run: # # pip-compile # click==7.0 # via rq redis==2.10.6 rq==0.8.2
- le fichier requirements.txt est bien créé avec des versions fixes et contient aussi les dépendances de deuxième niveau
click
dans l’exemple est une dépendances de rq.
-
On installe le fichier
requirements.txt
avec la commandepip-sync
.pip-sync
est similaire à la commandepip install -r requirements.txt
mais va aussi supprimer tous les packages non déclarés dans lerequirements.txt
.
Dans sa résolution de dépendance, pip-compile
prend automatiquement en compte le fichier requirements.txt
existant:
on peut donc relancer pip-compile
à tout moment sans craindre qu’il modifie de manière imprévue les versions
enregistrées dans le requirements.txt
. Les mises à jour doivent être faites explicitement :
- soit avec la commande
pip-compile --upgrade
pour mettre à jour tous les packages d’un coup - soit avec
pip-compile --upgrade-package <package>
pour ne mettre à jour qu’un seul package
Contrairement à Pip qui n’hésite pas à installer des packages incompatibles, pip-compile
échoue de manière explicite
quand on lui demande une combinaison de packages impossible. Sur l’exemple d’incompatibilité redis/rq qu’on a vu
précédemment, voilà ce qu’il se passe:
$ cat requirements.in
redis>=2.0,<3.0
rq>=1.0
$ pip-compile requirements.in
Could not find a version that matches redis<3,>=2.0,>=3.0.0
Tried: 0.6.0, 0.6.1, 1.34, 1.34.1, 2.0.0, 2.2.0, 2.2.2, 2.2.4, 2.4.0, 2.4.1, 2.4.2, 2.4.3, 2.4.4, 2.4.5, 2.4.6, 2.4.7, 2.4.8, 2.4.9, 2.4.10, 2.4.11, 2.4.12, 2.4.13, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.7.1, 2.7.2, 2.7.3, 2.7.4, 2.7.5, 2.7.6, 2.8.0, 2.9.0, 2.9.1, 2.10.0, 2.10.1, 2.10.2, 2.10.3, 2.10.5, 2.10.5, 2.10.6, 2.10.6, 3.0.0, 3.0.0, 3.0.0.post1, 3.0.0.post1, 3.0.1, 3.0.1, 3.1.0, 3.1.0, 3.2.0, 3.2.0, 3.2.1, 3.2.1
There are incompatible versions in the resolved dependencies.
Malheureusement la résolution de dépendances va parfois échouer alors qu’une solution existe et ce, même sur des cas apparemment simples. Par exemple, ce fichier provoque une erreur :
redis>=2.0,<3.0
rq>=0.8
La solution ici est redis==2.10.6
et rq==0.12.0
Comme on peut le voir, les pip-tools adressent uniquement les problèmes de mise à jour de versions et de packages résiduels. Pour isoler son environment et gérer différents jeux de dépendances, il faudra recourir au mêmes techniques qu’avec Pip.
Pipenv
Le second outil que nous allons voir est Pipenv. Pipenv est une création de Kenneth Reitz, l’auteur de la célèbre librairie requests, cela lui a valu une popularité rapide et aussi d’essuyer des critiques sur sa stabilité. Pipenv a maintenant plus de deux ans d’existence et le rythme des changements a aujourd’hui largement diminué.
L’outil s’appuie sur deux fichiers: Pipfile
et Pipfile.lock
. Le premier sert à déclarer les dépendances du projet et
le second à enregistrer les versions concrètes de tous les packages. Voici un exemple de Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
redis = ">=3,<4"
rq = ">=1,<2"
[requires]
python_version = "3.6"
L’installation des dépendances se fait simplement avec la commande:
pipenv install
Pipenv prendra automatiquement en charge la création d’un virtualenv pour isoler les packages du projet et, si
pyenv est disponible sur le système, installera aussi la version de python requise
dans le Pipfile
. Cette version de python dans le Pipfile
est importante : c’est un prérequis à l’installation, si
Pipenv n’arrive ni à détecter ni à installer la version demandée, la commande échouera.
La création automatique du virtualenv est optionnelle, si la commande est invoquée alors qu’un virtualenv est actif, Pipenv opérera directement sur cet environnement. Cela permet d’associer facilement cet outil avec d’autres gestionnaires de virualenv (comme virtualenvwrapper ou pyenv-virtualenv)
La commande install
ne fait qu’installer de nouveaux packages, pour supprimer les packages résiduels, Pipenv a une
commande dédiée :
pipenv clean
S’il est possible d’éditer le fichier Pipfile
à la main, cela n’est généralement pas nécessaire. On peut passer un nom
de package à la commande install
:
pipenv install request>=2.20
Dans ce cas Pipenv ajoutera la dépendence au fichier Pipfile
, installera le package et mettra à jour automatiquement
le fichier Pipfile.lock
.
En ajoutant l’option --dev
à cette commande, Pipenv enregistrera la dépendance comme une dépendance de développement.
Les dépendances de développement sont installées uniquement quand l’option --dev
est passée:
pipenv install --dev
Cela permet de gérer deux jeux de dépendances: un pour la production et un pour le développement. Il n’est malheureusement pas possible de créer d’autres jeux de dépendances, Pipenv se limite à ces deux ensembles.
Les mises à jour de packages se font avec la commande update
, comme avec pip-compile
on peut soit:
- tout mettre à jour avec:
pipenv update
- ou mettre à jour un seul package:
pipenv update rq
La résolution des dépendances souffre des mêmes problèmes que pip-compile
. Il échoue lui aussi sur l’exemple redis/rq:
$ pipenv install 'rq>=0.8' 'redis>=2.0,<3.0'
[...]
[pipenv.exceptions.ResolutionFailure]: Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
First try clearing your dependency cache with $ pipenv lock --clear, then try the original command again.
Alternatively, you can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
Hint: try $ pipenv lock --pre if it is a pre-release dependency.
ERROR: ERROR: Could not find a version that matches redis<3,>=3.0.0
Tried: 0.6.0, 0.6.1, 1.34, 1.34.1, 2.0.0, 2.2.0, 2.2.2, 2.2.4, 2.4.0, 2.4.1, 2.4.2, 2.4.3, 2.4.4, 2.4.5, 2.4.6, 2.4.7, 2.4.8, 2.4.9, 2.4.10, 2.4.11, 2.4.12, 2.4.13, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.7.1, 2.7.2, 2.7.3, 2.7.4, 2.7.5, 2.7.6, 2.8.0, 2.9.0, 2.9.1, 2.10.0, 2.10.1, 2.10.2, 2.10.3, 2.10.5, 2.10.5, 2.10.6, 2.10.6, 3.0.0, 3.0.0, 3.0.0.post1, 3.0.0.post1, 3.0.1, 3.0.1, 3.1.0, 3.1.0, 3.2.0, 3.2.0, 3.2.1, 3.2.1
There are incompatible versions in the resolved dependencies.
La gestion d’erreur dans ce cas n’est d’ailleurs pas satisfaisante:
- le message d’erreur complet fait plus de 40 lignes et est difficilement lisible
- Pipenv laisse l’environnement dans un état inutilisable: le Pipfile est modifié et les package
redis==2.10.6
etrq==1.0
sont installés. Pour revenir dans un état stable, il faudra annuler les modifications dans le Pipfile et relancer unpipenv install
- les conseils donnés dans le message d’erreur ne sont pas adaptés
Enfin il faut noter une limitation importante de Pipenv : un projet doit être associé à une seule version de python. Pour les projets qui doivent supporter différentes versions de python (c’est généralement le cas des librairies), son usage n’est pas conseillé (voir par exemple https://github.com/pypa/pipenv/issues/1911).
Voilà pour le tour d’horizon des fonctionnalités de Pipenv. Malgré quelques limitations, il facilite largement la gestion des packages d’un projet python et apporte des solutions simples d’usage aux principaux problèmes de pip qu’on l’on a vu au premier paragraphe.
Poetry
Poetry est le dernier-né de ces outils et est très similaire à Pipenv dans son usage.
On déclare cette fois les dépendances dans le fichier pyproject.yml
et les versions concrètes sont enregistrées dans
le fichier poetry.lock
. Voilà un exemple de fichier pyproject.yml
:
[tool.poetry]
name = "test-poetry"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^2.7"
redis = ">=3,<4"
rq = ">=1,<2"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
Comme pour Pipenv, l’installation des dépendences se fait avec la commande :
poetry install
et de la même manière que Pipenv, Poetry va automatiquement créer un virtualenv pour le projet ou réutiliser le virtualenv actif.
On ajoute des dépendances avec la commande add
:
poetry add "request >=2.20"
La commande add
accepte aussi l’option --dev
pour déclarer une dépendance de developpement.
Enfin, les mises à jour s’appliquent avec :
poetry update [package]
Maintenant, voyons les différences notables avec Pipenv.
Tout d’abord, poetry n’a pas à ce jour de commande pour supprimer les packages résiduels, il faudra comme avec Pip, recréer l’environnement.
La résolution des dépendances est par contre plus robuste. Poetry arrive à trouver la solution correcte sur l’exemple redis/rq
$ poetry add 'redis >2,<3' 'rq >=0.8'
Updating dependencies
Resolving dependencies... (0.1s)
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals
- Installing redis (2.10.6)
- Installing rq (0.12.0)
Par contre, cela a un prix : la résolution peut parfois prendre un temps considérable en particulier avec des packages ayant de nombreuses versions. Par exemple la commande suivante va mettre près de 5 minutes avant de détecter l’incompatibilité entre les versions demandées d’awscli et pyyaml :
poetry add 'pyyaml >4' 'awscli <=1.16'
Pour cette commande, Poetry va tester toutes les versions de awscli
pour en chercher une compatible avec pyyaml>4
.
Une autre différence importante est la possibilité d’utiliser l’outil avec différentes versions de python. Il faudra
alors déclarer les versions que l’on supporte dans le pyproject.yml
:
[tool.poetry.dependencies]
python = "^2.7 || ^3.6"
et s’assurer qu’on lance la commande poetry
avec la version de python souhaitée activée (par exemple en utilisant
pyenv ou en modifiant son PATH).
Si un package n’est disponible qu’avec python 2 ou python 3, il faudra le déclarer au moment de l’ajout :
poetry add python-saml --python ^2.7
poetry add python3-saml --python ^3.7
Dans sa résolution des dépendances, Poetry ne prend pas en comptes les versions de python que supportent les packages : les deux packages seront résolus, mais uniquement l’un des deux sera installé selon la version de python active.
Enfin, Poetry fournit aussi la possibilité de créer des packages : dans ce cas le pyproject.toml
remplace le
traditionnel fichier setup.py. Il faudra par contre une version récente de Pip (>=18.0
) pour arriver à installer ces
packages.
Malgré toutes ses similarités, Poetry fournit donc une alternative intéressante à Pipenv, en particulier si l’on travaille sur une librairie.
Conclusion
On a présenté ces 3 outils de manière chronologique et on peut constater une progression en matière de fonctionnalités, mais cela ne veut pas dire que les plus anciens sont obsolètes :
- Pip-tools, grace à sa simplicité, permet de conserver une grande flexibilité et sera certainement plus facile à intégrer à des projets existants
- Pipenv, si vous n’êtes pas affecté par ses limitations, est probablement une solution plus mature et complète que Poetry
Python est enfin doté d’outils satisfaisants pour installer et mettre à jour les packages mais il reste encore la difficulté de choisir une de ces solutions et de la mettre en oeuvre : à l’avenir on aimerait que pip intègre les fonctionnalités de ces outils.
Cette entrée a été publiée dans programmation avec comme mot(s)-clef(s) python, pip, pipenv, python-poetry
Les articles suivant pourraient également vous intéresser :
- La bonne et la mauvaise review par Sébastien Bizet
- Dark mode vs Light mode : accessibilité et éco-conception par Jean-Baptiste Bergy
- Principes SOLID et comment les appliquer en Python par Mariana ROLDAN VELEZ
- Pydantic, la révolution de python ? par Pablo Abril
- Comment utiliser les fixtures pytest en autouse tout en maîtrisant ses effets de bord ? par Amaury Boin
Postez votre commentaire :
Votre commentaire a bien été envoyé ! Il sera affiché une fois que nous l'aurons validé.
Vous devez activer le javascript ou avoir un navigateur récent pour pouvoir commenter sur ce site.