Gestion ennuyeuse des dépendances Python

Voici les idées importantes d'un billet de James Bennett intitulé Boring Python: dependency management que j'ai trouvé très bon.

Il y décrit une gestion des dépendances Python "ennuyeuse", c'est à dire :

Je veux me préoccuper le moins possible de ce genre de choses une fois la mise en place terminée, ce qui signifie aussi ne pas être un primo adoptant des autres outils.

L'écosystème Python est vaste dans ce domaine mais l'auteur préconise de s'en tenir presque exclusivement aux outils d'empaquetage standards de Python, excepté pip-tools (optionnel et permettant d'automatiser des process manuels de pip).

L'objectif est de pouvoir reproduire un environnement existant à l'identique ailleurs.

Packaging

Trois aspects dans le packaging (empaquetage) :

  1. définir et produire un artefact distribuable à partir d'un code fonctionnel
  2. utiliser l'artefact pour que le code soit fonctionnel ailleurs
  3. pouvoir exécuter et travailler en même temps sur plusieurs projets ayant des dépendances conflictuelles

L'écosystème Python dispose d'outils par défaut pour ces trois aspects :

  1. setuptools
  2. pip
  3. environnements virtuels

Remarques :

  • setuptools ne sera pas abordé car on est dans un contexte de déploiement de services web et il n'y a pas d'avantage à en faire des paquets distribuables via PyPI
  • le mot artefact, souvent utilisé dans la littérature traitant du déploiement en conteneur, nécessite un éclaircissement

requirements

Les requirements sont une liste des paquets Python à installer via pip.

La recommandation est de créer une configuration pip multi-fichiers :

  • création d'un répertoire de premier niveau requirements/
  • y placer les fichiers des requirements par environnements
    • test
    • dev
    • prod
    • etc.

Il suffit ensuite de lancer l'installation d'un fichier cible via pip install -r requirements/target.

Pour une gestion encore plus fine, il est possible de faire référence à une autre liste directement dans un fichier de requirements via -r ./base.txt.

Arbre des dépendances

Deux possibilités pour obtenir un arbre entier des dépendances (directes et indirectes) épinglées aux versions exactes :

  1. copier la sortie de pip freeze dans les requirements
  2. utiliser pip-compile fourni avec pip-tools

Empreinte numérique

Il s'agit d'obtenir une garantie supplémentaire en cas de compromission des paquets sur PyPI :

Faites confiance, mais vérifiez

Pour obtenir cette garantie, on vérifie que tout fonctionne, puis on génère une empreinte numérique des paquets installés.

Deux possibilités :

  1. utiliser le Hash-checking Mode de pip
    • pip download pour télécharger tous les paquets localement
    • pip hash sur chacun d'eux pour obtenir leur empreinte (il est possible de spécifier plusieurs empreintes pour les paquets compilés)
  2. utiliser pip-compile --generate-hashes fourni avec pip-tools

Une fois le hachage en place, une installation échouera en cas d'empreintes non concordantes.

Environnement virtuel

L'auteur préconise de toujours créer un environnement virtuel même en cas de déploiement dans un conteneur dont vous savez qu'il ne contient qu'un seul interpréteur Python.

Je trouve ce point sujet à débat dans le cas d'un déploiement dans un conteneur car :

  • il introduit une possibilité de pouvoir se tromper d'environnement au moment d'une invocation
  • et nécessite donc de modifier le $PATH pour le préfixer du chemin vers les binaires de l'environnement virtuel

Générer les requirements

Pour générer les requirements :

python -m pip install --upgrade pip-tools
python -m pip-tools compile --generate-hashes requirements/dev.in --output-file requirements/dev.txt
python -m pip-tools compile --generate-hashes requirements/prod.in --output-file requirements/prod.txt

Facile à intégrer dans le Makefile d'un projet !

L'option m passée à l'interpréteur Python ci-dessus est utile si vous avez plusieurs environnements virtuels et que vous souhaitez vous prémunir d'un problème potentiel de $PATH qui ciblerait une mauvaise version de pip.

Rester à jour

Deux raisons importantes de rester à jour régulièrement :

  • colmater régulièrement des failles critiques de sécurité
  • éviter une mise à niveau risquée de toutes les dépendances en même temps (y'en a qu'ont essayé, ils ont eu des problèmes)

L'idéal est de faire preuve de discipline en appliquant les mises à jour régulièrement, explicitement et une par une, plutôt qu'implicitement et par lots.

Pour aider :

  • Dependabot (GitHub) comprend le style pip-tools et peut proposer des pull requests dès qu'un changement de version est disponible
  • des services tiers comme pyup permettent d'obtenir uniquement les mises à jour de sécurité (et non chaque changement de version)

Avant Plein texte avec colonne générée PostgreSQL dans Django

Tag Kemar Joint