Quelques outils pour gérer les dépendances Python.
Publié le : 27 novembre 2021 | Mis à jour le : 27 juin 2023Il est fréquent de retrouver dans les projets python une liste impressionnante
de dépendances qui deviennent rapidement ingérables. Il est inutile de lister
toutes les dépendances de seconds, voir de troisièmes niveaux. J’imagine
parfaitement comment on arrive à ce genre de résultats : on lance la commande
pip freeze > requirements.txt
Je vous propose quelques outils qui vont permettre de nettoyer et gérer correctement vos dépendances.
Pipx, installer des programmes python dans des environnements isolés
Pour éviter d’installer des utilitaires de développement dans les environnements virtuels de vos projets, Pix propose de les installer dans des environnements isolés, mais disponibles tout le temps. Cela garantit l’absence de conflits de dépendances.
Installation de pipx
Installez pipx dans votre compte utilisateur :
pip3 install --user pipx
Utilisation de pipx
Le premier moyen est d’utiliser les packages Pypi comme avec pip :
pipx install black
Pour installer un outil depuis son code source :
pipx install git+https://github.com/psf/black.git
Pour utiliser un produit et l’installer, il suffit de lancer la commande suivante :
pipx run black
Pour lister vos outils installés avec pipx il suffit d’utiliser la commande suivante :
pipx list
Pour désinstaller un package :
pipx uninstall blavk
Pour injecter un outil à un autre :
pipx inject pipdeptree pip-tools
Pipdeptree, nettoyer vos fichiers requirements.txt
Comme je le disais plus haut il est inutile de lister tous les packages dans
vos fichiers requirements.txt. Si trop tard pipdeptree
va vous aider à faire
le ménage.
Installation de pipdeptree
Il suffit de ‘linstaller dans votre environnement virtuel :
pip install pipdeptree
Lançons le sur l’environnement ansible :
pipdeptree
ansible==4.8.0
- ansible-core [required: >=2.11.6,<2.12, installed: 2.11.6]
- cryptography [required: Any, installed: 3.4.8]
- cffi [required: >=1.12, installed: 1.14.5]
- pycparser [required: Any, installed: 2.20]
- jinja2 [required: Any, installed: 3.0.1]
- MarkupSafe [required: >=2.0, installed: 2.0.1]
- packaging [required: Any, installed: 21.0]
- pyparsing [required: >=2.0.2, installed: 2.4.7]
- PyYAML [required: Any, installed: 5.4.1]
- resolvelib [required: >=0.5.3,<0.6.0, installed: 0.5.4]
ansible-lint==5.2.1
- enrich [required: >=1.2.6, installed: 1.2.6]
- rich [required: >=9.5.1, installed: 10.11.0]
- colorama [required: >=0.4.0,<0.5.0, installed: 0.4.4]
- commonmark [required: >=0.9.0,<0.10.0, installed: 0.9.1]
- pygments [required: >=2.6.0,<3.0.0, installed: 2.10.0]
- packaging [required: Any, installed: 21.0]
- pyparsing [required: >=2.0.2, installed: 2.4.7]
- pyyaml [required: Any, installed: 5.4.1]
- rich [required: >=9.5.1, installed: 10.11.0]
- colorama [required: >=0.4.0,<0.5.0, installed: 0.4.4]
- commonmark [required: >=0.9.0,<0.10.0, installed: 0.9.1]
- pygments [required: >=2.6.0,<3.0.0, installed: 2.10.0]
- ruamel.yaml [required: >=0.15.37,<1, installed: 0.17.16]
- ruamel.yaml.clib [required: >=0.1.2, installed: 0.2.6]
- tenacity [required: Any, installed: 8.0.1]
- wcmatch [required: >=7.0, installed: 8.2]
- bracex [required: >=2.1.1, installed: 2.1.1]
greenlet==0.4.16
lastversion==1.6.0
- appdirs [required: Any, installed: 1.4.4]
- beautifulsoup4 [required: Any, installed: 4.10.0]
- soupsieve [required: >1.2, installed: 2.2.1]
- cachecontrol [required: Any, installed: 0.12.6]
- msgpack [required: >=0.5.2, installed: 1.0.2]
- requests [required: Any, installed: 2.26.0]
- certifi [required: >=2017.4.17, installed: 2021.5.30]
- charset-normalizer [required: ~=2.0.0, installed: 2.0.6]
- idna [required: >=2.5,<4, installed: 3.2]
- urllib3 [required: >=1.21.1,<1.27, installed: 1.26.7]
- distro [required: Any, installed: 1.6.0]
- feedparser [required: Any, installed: 6.0.8]
- sgmllib3k [required: Any, installed: 1.0.0]
- lockfile [required: Any, installed: 0.12.2]
- packaging [required: Any, installed: 21.0]
- pyparsing [required: >=2.0.2, installed: 2.4.7]
- python-dateutil [required: Any, installed: 2.8.2]
- six [required: >=1.5, installed: 1.16.0]
- PyYAML [required: Any, installed: 5.4.1]
- requests [required: >=2.6.1, installed: 2.26.0]
- certifi [required: >=2017.4.17, installed: 2021.5.30]
- charset-normalizer [required: ~=2.0.0, installed: 2.0.6]
- idna [required: >=2.5,<4, installed: 3.2]
- urllib3 [required: >=1.21.1,<1.27, installed: 1.26.7]
- six [required: Any, installed: 1.16.0]
- tqdm [required: Any, installed: 4.62.3]
paramiko==2.7.2
- bcrypt [required: >=3.1.3, installed: 3.2.0]
- cffi [required: >=1.1, installed: 1.14.5]
- pycparser [required: Any, installed: 2.20]
- six [required: >=1.4.1, installed: 1.16.0]
- cryptography [required: >=2.5, installed: 3.4.8]
- cffi [required: >=1.12, installed: 1.14.5]
- pycparser [required: Any, installed: 2.20]
- pynacl [required: >=1.0.1, installed: 1.4.0]
- cffi [required: >=1.4.1, installed: 1.14.5]
- pycparser [required: Any, installed: 2.20]
- six [required: Any, installed: 1.16.0]
pip==21.2.4
pytest-testinfra==6.4.0
- pytest [required: !=3.0.2, installed: 6.2.5]
- attrs [required: >=19.2.0, installed: 21.2.0]
- iniconfig [required: Any, installed: 1.1.1]
- packaging [required: Any, installed: 21.0]
- pyparsing [required: >=2.0.2, installed: 2.4.7]
- pluggy [required: >=0.12,<2.0, installed: 1.0.0]
- py [required: >=1.8.2, installed: 1.10.0]
- toml [required: Any, installed: 0.10.2]
pywinrm==0.4.2
- requests [required: >=2.9.1, installed: 2.26.0]
- certifi [required: >=2017.4.17, installed: 2021.5.30]
- charset-normalizer [required: ~=2.0.0, installed: 2.0.6]
- idna [required: >=2.5,<4, installed: 3.2]
- urllib3 [required: >=1.21.1,<1.27, installed: 1.26.7]
- requests-ntlm [required: >=0.3.0, installed: 1.1.0]
- cryptography [required: >=1.3, installed: 3.4.8]
- cffi [required: >=1.12, installed: 1.14.5]
- pycparser [required: Any, installed: 2.20]
- ntlm-auth [required: >=1.0.2, installed: 1.5.0]
- requests [required: >=2.0.0, installed: 2.26.0]
- certifi [required: >=2017.4.17, installed: 2021.5.30]
- charset-normalizer [required: ~=2.0.0, installed: 2.0.6]
- idna [required: >=2.5,<4, installed: 3.2]
- urllib3 [required: >=1.21.1,<1.27, installed: 1.26.7]
- six [required: Any, installed: 1.16.0]
- xmltodict [required: Any, installed: 0.12.0]
readline==6.2.4.1
selinux==0.2.1
- distro [required: >=1.3.0, installed: 1.6.0]
- setuptools [required: >=39.0, installed: 57.4.0]
typing-extensions==3.10.0.2
yamllint==1.26.3
- pathspec [required: >=0.5.3, installed: 0.9.0]
- pyyaml [required: Any, installed: 5.4.1]
- setuptools [required: Any, installed: 57.4.0]
On voit bien l’arborescence des dépendances. Si vous voulez n’afficher que le premier niveau.
Si vous utilisez pyenv, cela va obliger d’indiquer le chemin de l’environnement à analyser :
pipdeptree --python ~/.pyenv/shims/python3
POur n’afficher que le premier niveau :
pipdeptree --python ~/.pyenv/shims/python3 |grep -v "^ "
ansible==4.8.0
ansible-lint==5.2.1
greenlet==0.4.16
lastversion==1.6.0
paramiko==2.7.2
pip==21.2.4
pytest-testinfra==6.4.0
pywinrm==0.4.2
readline==6.2.4.1
selinux==0.2.1
typing-extensions==3.10.0.2
yamllint==1.26.3
En virant les packages de dev vous avez une liste propre de dépendances.
pip-tools, pour construire des fichiers requirements cohérents’
pip-tools
est une bonne alternative poetry
et pipenv
Vous avez certainement rencontré des problèmes pour installer des packages avec pip, surtout qu’on utilise des librairies faisant appel à des librairies communes, mais pas forcément la même version.
On peut aussi avoir installé des packages pour tester un module, mais comment le désinstaller facilement avec toutes ses dépendances ?
Pip-tools propose des solutions à ces problèmes.
Installation de pip-tools
On va utiliser pipx
pipx install pip-tools
Utilisation de pip-compile
Le premier outil disponible avec pip-tools s’appelle pip-compile
. Cet
outil va prendre en entrée soit :
- des fichiers .in
- le fichier
setup.py
qui a ma préférence.
import setuptools
from mymodule import __version__
setuptools.setup(
name="mymodule",
version=__version__,
author="Me",
author_email="me@gmail.com",
description="Mon module",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires=">=3.9",
install_requires=[
"common-framework==2021.2.1",
"coreapi==2.3.3",
"Django==3.2.9",
"pyreadline==2.1",
"python-dateutil==2.8.2",
"Unidecode==1.3.2",
"watchdog==1.0.2",
"zeep==4.1.0",
],
extras_require={
"dev": [
"coverage",
"flake8",
"ipdb",
"pytest",
"twine",
]
},
)
Lançons pip-compile
sur ce projet (je vous conseille d’utiliser toujours l’option
-v pour comprendre pourquoi elle échoue) :
pip compile -v
Le contenu du fichier requirements.txt créé :
#
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile
#
asgiref==3.4.1
# via django
attrs==21.2.0
# via zeep
cached-property==1.5.2
# via zeep
certifi==2021.10.8
# via requests
charset-normalizer==2.0.7
# via requests
common-framework==2021.2.1
# via mymodule (setup.py)
coreapi==2.3.3
# via mymodule (setup.py)
coreschema==0.0.4
# via coreapi
django==3.2.9
# via mymodule (setup.py)
idna==3.3
# via requests
isodate==0.6.0
# via zeep
itypes==1.2.0
# via coreapi
jinja2==3.0.3
# via coreschema
lxml==4.6.4
# via zeep
markupsafe==2.0.1
# via jinja2
platformdirs==2.4.0
# via zeep
pyreadline==2.1
# via mymodule (setup.py)
python-dateutil==2.8.2
# via mymodule (setup.py)
pytz==2021.3
# via
# django
# zeep
requests==2.26.0
# via
# coreapi
# requests-file
# requests-toolbelt
# zeep
requests-file==1.5.1
# via zeep
requests-toolbelt==0.9.1
# via zeep
six==1.16.0
# via
# isodate
# python-dateutil
# requests-file
sqlparse==0.4.2
# via django
unidecode==1.3.2
# via mymodule (setup.py)
uritemplate==4.1.1
# via coreapi
urllib3==1.26.7
# via requests
watchdog==1.0.2
# via mymodule (setup.py)
zeep==4.1.0
# via mymodule (setup.py)
Pour installer les extra dev :
pip-compile --extra dev -v
Pour mettre à jour tous les packages (mais vous êtes fou, je préfère utiliser renovate) il suffit d’ajouter l’option upgrade :
pip-compile --upgrade-package django
Une autre option se nommant --pip-args
permet de passer des paramètres pip
pip-compile requirements.in --pip-args ' --retries 10 --timeout 30 '
Nettoyer vos environnements
Un autre outil se nommant pip-sync
se propose de gérer vos environnements
virtuels à partir du fichier requirements.txt. Si des librairies ont été retirées
ou ajouté, pip-sync
va les installer ou les désinstaller pour que
l’environnement corresponde à ce qui se trouve dans le fichier requirements.txt
pip-sync