Aller au contenu principal

Geson des dépendances Python.

· 8 minutes de lecture
Stéphane ROBERT

Il 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