Tout projet de développement logiciel repose sur un équilibre délicat ; un assemblage de composants où chaque pièce doit s’emboîter parfaitement. En Python, cet assemblage est constitué de bibliothèques et de frameworks externes, des dépendances sans lesquelles notre code resterait lettre morte. Assurer la cohérence de ces dépendances d’une machine à l’autre et du développement à la production est un défi fondamental. Heureusement, le gestionnaire de paquets pip, un outil standard de l’écosystème Python, fournit un mécanisme simple et efficace pour photographier l’état d’un environnement et le reproduire fidèlement.
Le point de départ de toute bonne pratique commence par l’isolement. Travailler directement dans l’environnement Python global de votre système Linux est une recette pour le désastre. Vous risquez des conflits de versions entre différents projets ou pire, de corrompre des paquets dont votre système d’exploitation dépend. La solution est de créer un environnement virtuel pour chaque projet. C’est une bulle autonome, un bac à sable contenant sa propre version de Python et ses propres bibliothèques.
Imaginons un script qui a besoin de la bibliothèque Pillow pour manipuler des images. L’installation se fait via pip install :
pip install pillow
Le paquet est installé, mais cette information est pour l’instant volatile. Elle n’existe que dans notre environnement virtuel local. Pour la pérenniser et la rendre partageable, nous devons la consigner. C’est ici qu’intervient la commande pip freeze. Son rôle est de lister tous les paquets installés dans l’environnement actif, en spécifiant leur numéro de version exact. En redirigeant la sortie de cette commande, nous créons le fichier requirements.txt, la véritable fiche d’identité de notre projet.
pip freeze > requirements.txt
Le contenu de ce fichier sera sobre et précis, par exemple Pillow==10.4.0. Le double signe égal est crucial. Il « épingle » la dépendance à cette version spécifique, garantissant que toute personne qui reconstruira l’environnement utilisera exactement la même version de Pillow, évitant ainsi les bugs liés à des mises à jour imprévues. Ce fichier requirements.txt doit être versionné avec votre code source, il fait partie intégrante du projet.
La véritable puissance de cette approche se manifeste lors de la collaboration ou pendant déploiement du projet avec git. Un nouveau développeur rejoignant le projet, ou un serveur d’intégration continue, n’a qu’à cloner le dépôt, créer son propre environnement virtuel, l’activer, puis exécuter une unique commande pour installer toutes les dépendances nécessaires en une seule fois. Une fois le projet cloné et après avoir activé sont environnement virtuel n’a plus qu’à faire :
pip install -r requirements.txt
L’option -r instruit pip de lire le fichier fourni et de provisionner l’environnement à l’identique. La reproductibilité est ainsi assurée.
Le cauchemar des dépendances en cascade
L’approche consistant à geler son environnement est fonctionnelle, mais elle n’est pas infaillible. Elle cache une complexité sous-jacente qui ne tarde jamais à se manifester dans les projets qui vivent et grandissent. Le plus grand défi survient lorsque les exigences de versions de différentes bibliothèques entrent en collision. C’est à ce moment que pip peut afficher un message d’erreur déroutant, signalant que son résolveur de dépendances est dépassé par les événements.
Parfois, sur un projet, si vous utilisez une bibliothèque, il est possible qu’elle possède elle-même une dépendance qui pose problème dans votre projet. C’est-à-dire quelle demande une dépendance qui a été mise à jour lors de l’update général des dépendances, mais la bibliothèque veut une version plus ancienne pour fonctionner correctement.
Vous êtes donc trop en avance si vous avez tout mis à jour. Il faut être capable d’arbitrer, mais en général la solution la plus simple à court terme est de downgrade la dépendance concernée pour que cela ne casse pas le reste de votre projet. C’est pertinent quand vous utilisez des dépendances souvent mise à jour et vous savez que les développeurs vont prochainement mettre à jour. Sinon, c’est laisser des problèmes en suspens dans votre projet et commencer à accumuler de la dette technique …
Mettre à jour une dépendance dans une version avec pip
Normalement, le message d’erreur devrait être assez explicite sur la version attendue de la dépendance. Pour mettre à jour la dépendance dans une version particulière, il faut simplement préciser le numéro de version avec une commande :
pip install Pillow==10.3.0
Ce n’est clairement pas ce qu’il est conseillé de faire et il faudrait plutôt remonter le problème aux développeurs dans une issue sur le dépôt officiel de la librairie concernée. Néanmoins, cela permet à votre projet de tourner à court terme dans une phase de tests …
Vous pouvez aussi mètre à jour chaque dépendance une par une lorsque cela devient complexe avant de faire votre pip freeze à l’aide de la commande suivante :
pip install --upgrade pyfunceble
Je pense que vous voyez l’idée.
Les limites de pip freeze
Il est important de comprendre les limites de pip freeze. Sa force, qui est de tout lister, est aussi sa faiblesse. Il ne fait aucune distinction entre les dépendances que vous avez explicitement demandées (comme Pillow) et les dépendances de ces dépendances (les dépendances transitives). C’est une photographie brute, pas une liste d’ingrédients raisonnée. Il faut garder cette nuance à l’esprit pour l’utiliser à bon escient.
Pour les projets d’envergure ou pour ceux qui exigent une gestion des dépendances à toute épreuve, il devient pertinent d’adopter des outils plus spécialisés. Des solutions comme pip-tools ou Poetry proposent une philosophie de gestion plus structurée. Le principe n’est plus de geler l’état d’un environnement à un instant T. Il s’agit plutôt de déclarer ses dépendances directes dans un fichier source, souvent un requirements.in.
Ensuite, un outil comme pip-compile se charge de calculer l’arbre complet des dépendances, de résoudre les conflits potentiels et de générer un fichier de verrouillage (lock file), notre requirements.txt, qui contient la liste exhaustive et cohérente de tous les paquets avec des versions figées. L’installation se fait alors avec pip-sync, qui garantit que l’environnement virtuel est le reflet exact de ce fichier de verrouillage. Cette méthode apporte une clarté et une robustesse bien supérieures. Elle sépare ce que le projet requiert de ce qui est installé, offrant un contrôle bien plus fin sur le cycle de vie des dépendances.