Poetry est un gestionnaire de bibliothèques tierces Python qui permet de décrire ses besoins et de ne pas se préoccuper de gérer les dépendances, tout en assurant la possibilité de maîtriser totalement son environnement.
Python dispose, depuis la version 3.4, de l’outil PIP permettant d’installer facilement toute bibliothèque tierce. Avant cela, il existait l’outil easy_install. Cet excellent outil permet d’installer une bibliothèque ainsi que ses dépendances. Les deux soucis que l’on peut constater sont la gestion de ces dépendances en cas de conflits et la cohérence de l’installation sur différents systèmes. Poetry résout ces deux problématiques.
1. Installation
Cette partie est probablement la plus simple et la moins surprenante que puissiez lire :
Les briques existantes de Python font superbement leur travail. Rajoutons quelques liens, le premier vers le registre PIP [1] qui, au-delà d’être le point d’entrée de la bibliothèque, propose également une mini-documentation ainsi qu’une image animée qui propose mieux que mille mots ce dont l’outil est capable.
Le second lien pointe vers la documentation officielle [2] qui propose un tutoriel ainsi que toutes les références nécessaires pour avoir une maîtrise totale du produit.
2. Créer un nouveau projet
Si vous débutez un nouveau projet et que vous souhaitez le faire avec poetry, vous pourrez, après l’avoir installé de préférence dans un environnement virtuel dédié à votre projet, exécuter la commande suivante :
Cette action va créer un répertoire nommé mon_projet, qui contiendra un sous-répertoire de même nom qui est destiné à contenir le code Python du projet ainsi qu’un répertoire de test qui lui, est destiné à recevoir du code permettant de tester le projet (tests unitaires ou tests fonctionnels).
Au-delà de cette architecture, l’outil poetry crée également un fichier README.md qui contient tout ce que l’on a envie de dire sur notre projet. Pour rappel, ce fichier est un point d’entrée essentiel dans un dépôt comme Git, puisque dans GitHub ou dans GitLab, il est affiché lorsque l’on visualise le dépôt et il est ce que le développeur consulte en priorité lorsqu’il veut comprendre ce que le projet fait, ce à quoi il sert ou comment l’installer.
Enfin, le dernier fichier créé est le fichier pyproject.toml. On arrive là au point d’importance majeure pour cette première partie : ce fichier est destiné à remplacer les fichiers setup.py, requirements.txt, setup.cfg, MANIFEST.in ainsi que Pipfile, soit l’intégralité des fichiers que l’on avait coutume de créer ou de générer via divers outils pour packager notre code et le rendre installable.
Lorsque l’on tape la commande plus haut, le programme va nous poser de multiples questions permettant de récupérer des métadonnées sur notre projet. Ces données seront intégrées au fichier pyproject.toml et il vous sera possible d’en rajouter d’autres en vous inspirant, par exemple, de l’exemple donné dans la page du registre PIP.
Après avoir posé ces questions de base, le programme va aussi vous demander quelles sont les dépendances que vous souhaitez utiliser. Lorsque vous tapez un nom, il va faire une recherche et trouver tous les paquets qui ressemblent à ce nom (il garde une liste de 10 noms). Cela vous permet d’aller chercher un paquet même sans connaître son nom exact. Une fois le paquet choisi, on vous demande si vous avez des contraintes spécifiques.
Ainsi, * signifie que n’importe quelle version convient. Le symbole 1.* signifie que toute version 1.X.Y conviendra. Ce symbole est assez classique.
Le symbole >=X.Y signifie que toutes les versions X.Y.Z, X.(Y+1) ou encore (X+1).0 conviendront. De la même manière, on peut utiliser le symbole >=, < ou encore <=.
La notation ^X.Y correspond à >X.Y.0 et <= (X+1).0.0, ce qui peut paraître inhabituel. Par exemple, ^3 signifie >= 3.0.0 et < 4.0.0 alors que ^1.2.3 signifie >= 1.2.3 et < 2.0.0.
Il existe également le tilde, qui fonctionne ainsi : ~3 signifie, tout comme ^3 à >= 3.0.0 et < 4.0.0, mais ~1.2.3 signifie >= 1.2.3 et < 1.3.0. Ce symbole est similaire dans son fonctionnement à *, sauf qu’il y a une version minimale au sein de la branche.
Enfin, il existe le symbole @ qui signifie que l’on souhaite exactement une version précise. Et si le symbole est @latest, on dit que l’on veut la toute dernière version, au moment de l’installation.
Comprendre ces contraintes vous permettra de maîtriser votre environnement.
Tout ceci va nous permettre de préciser la manière dont on souhaite que nos paquets soient installés, mais aussi comment on souhaite qu’ils évoluent dans le futur. Ainsi, en précisant django ^4.0.5, je dis que je souhaite au minimum la version 4.0.5, mais que tant que l’on reste dans la branche 4.X au-dessus de la version demandée, je pourrais mettre à jour à toute version supérieure, idéalement la toute dernière. Par contre, le jour où Django 5.0 sortira, elle sera ignorée.
3. Installer les paquets
Une fois que j’ai précisé tous les paquets qui m’intéressent, je peux terminer le programme et, à cet instant-là, rien n’a réellement été installé. Notons que je n’ai pas besoin de me préoccuper des dépendances des programmes qui m’intéressent, je n’ai même pas besoin de les connaître. Je peux alors lancer la commande suivante :
À ce moment-là, poetry va analyser toutes les dépendances souhaitées, ainsi que leurs dépendances, et va essayer d’installer, pour toutes, leur dernière version à jour. S’il ne le peut pas, alors il va baisser les versions des paquets posant problème, jusqu’à trouver une solution qui fonctionne pour tous les paquets.
Exemple : j’ai un paquet A, dépendant de X à la version < 6.0 et j’ai un paquet B qui dans sa dernière version, la 1.4.2, a besoin du paquet X à la version >= 7.0. Ces deux paquets sont incompatibles. L’outil poetry va alors tester le paquet B à la version 1.4.1, puis 1.4.0, puis 1.3.6 (la toute dernière de la branche 1.3), puis à la version 1.3.5, etc., jusqu’à trouver une version qui a soit dans ses dépendances le module X à la version < 6.0, soit que le module X ne soit plus dans les dépendances.
Une fois que poetry a trouvé une solution qui permet d’installer tous les paquets et toutes leurs dépendances, en ayant trouvé les versions qui sont compatibles avec les besoins du projet, il va lister tout cela dans un fichier poetry.lock.
Lors de la prochaine installation, la simple existence de ce fichier fera que poetry n’essaiera plus de résoudre les dépendances, il ira directement installer les paquets listés dans ce fichier.
Il est à noter que ce fichier poetry.lock est à commiter dans le dépôt pour s’assurer que, quelle que soit la plateforme où le projet est installé, les mêmes versions des dépendances soient installées, ce qui évitera, par exemple, d’avoir en production des anomalies qui n’existaient pas en test, à cause d’un paquet qui a changé de version entre les dates des deux installations. Bien évidemment, ceci n’est vrai que si l’on parle d’un projet. Si votre dépôt concerne une bibliothèque destinée à être installée en tant que dépendance d’un projet final, le fichier poetry.lock n’a pas besoin d’être commité. Il vaut même mieux l’ignorer.
Auparavant, une des solutions était de faire un pip freeze et d’installer, sur toute nouvelle plateforme, le résultat de cette commande. Avec l’outil poetry, ceci est automatisé et mieux maîtrisé.
Pour terminer, il est possible également d’installer une version depuis un dépôt Git, voire un tag Git :
Ce petit détail est important à connaître. De même, il est possible, pour installer un paquet standard, de préciser un registre différent de pypi.org :
Si vous travaillez en entreprise et que les dépôts PIP usuels ne sont pas accessibles, vous en aurez besoin.
4. Rajouter une dépendance
À tout moment, il est possible de lancer la commande suivante pour rajouter une nouvelle dépendance :
À ce moment-là, la commande va faire une recherche sur le paquet, demander la contrainte à y appliquer, puis installer le paquet en résolvant les dépendances avec l’existant. Le résultat sera alors ajouté au fichier poetry.lock.
Au passage, on peut signaler qu’il est possible de supprimer une dépendance assez simplement :
Et pour avoir des informations sur un paquet en particulier, il est possible de faire :
Cela donnera le nom du paquet, la version installée, une description et les dépendances descendantes (dépendances dont le paquet a besoin) et ascendantes (autres paquets dont le paquet courant est une dépendance).
5. Groupes de dépendances
On a passé sous silence le fait que si vous avez testé la commande poetry new, on vous demande par deux fois de rajouter des dépendances. La première fois, c’est pour les dépendances du projet en général (comme Django, par exemple, le groupe main). La seconde fois, c’est plus spécifiquement pour un environnement de dev, ce qui permet d’installer des outils comme prospector qui, s’il a sa place en développement, n’a rien à faire en production. Il s’agit du groupe dev.
Vous pourrez également visualiser ceci dans le fichier pyptoject.toml, puisque les diverses dépendances sont réunies dans des sections distinctes.
Pour ajouter une dépendance dans un groupe différent du groupe principal, il suffit de le préciser :
Si le groupe n’existe pas au moment de la commande, il sera automatiquement créé.
Pour installer les dépendances d’un groupe particulier, il suffit aussi de le préciser :
Il existe également les options --without ou --only dont le nom est suffisamment explicite pour ne pas avoir à expliquer leur utilité.
Pour information, dans le fichier pyproject.toml, les dépendances principales, donc du groupe main, sont dans la section [tool.poetry.dependencies] et les dépendances du groupe dev sont dans la section [tool.poetry.dev-dependencies]. Elles sont ainsi clairement démarquées.
6. Intégrer à un projet existant
Si vous avez déjà un projet existant, que vous utilisez les fichiers requirements.txt et que vous souhaitez évoluer vers poetry, rien de plus simple. Il suffit d’utiliser la commande suivante, en étant positionné à la racine de votre projet :
La suite est exactement similaire à ce qu’il se passe pour un nouveau projet. Vous terminerez avec un fichier pyproject.toml, et pour installer réellement les paquets à l’aide de cette commande, il faudra faire :
Ce qui, comme précédemment, créera le fichier poetry.lock.
7. Mise à jour d’un projet
Maintenant, nous arrivons à un autre aspect très important. Régulièrement, à chaque cycle de release, il est intéressant de remettre à jour toutes les dépendances, bien entendu avant la mise en recette et non pas juste avant la mise en production, pour éviter des surprises désagréables. Pour cela :
Cette commande permet d’aller chercher toutes les nouvelles versions sorties depuis le dernier calcul des dépendances et de refaire ce calcul, en régénérant un nouveau fichier poetry.lock.
Rien de plus simple, donc. À noter qu’il est possible de ne faire qu’une mise à jour partielle :
Ceci est particulièrement utile si dans le fichier pyproject.toml, on a modifié les contraintes sur ces paquets. On peut aussi noter que les options --with, --without ou --only sont aussi disponibles pour cette commande.
8. Intégration à Docker
On partira ici du principe que vous avez déjà docker et docker-compose installés ainsi que toutes les connaissances nécessaires sur le sujet et l’on se contente ici de vous donner un exemple de fichier Dockerfile :
02:
03: SHELL ["/bin/bash", "-c"]
04:
05: ENV DEBIAN_FRONTEND noninteractive
06: ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
07: ENV PYTHONDONTWRITEBYTECODE 1
08: ENV PYTHONUNBUFFERED 1
09: ARG POETRY_VERSION=1.1.14
10:
11: RUN useradd pythoniste --create-home --user-group --groups sudo --shell /bin/bash
12:
13: WORKDIR /home/pythoniste
14:
15: # Mise à jour de PIP
16: RUN pip install --no-cache-dir --upgrade pip
17:
18: # Installation de Poetry
19: RUN pip install poetry==${POETRY_VERSION}
20: RUN poetry config virtualenvs.create false
21:
22: # Installation des dépendances
23: COPY pyproject.toml pyproject.toml
24: COPY poetry.lock poetry.lock
25: RUN poetry install --no-interaction --no-ansi
À la ligne 9, nous précisons la version de poetry que nous souhaitons installer.
À la ligne 16, nous mettons à jour pip à la dernière version, puis nous installons poetry à la ligne 19. Enfin, la ligne 20 est importante : elle permet de dire que nous ne sommes pas dans un environnement virtuel, mais que nous utilisons le Python du système, qui est celui du container Docker construit à partir de l’image Python précisée à la ligne 1.
Enfin, nous allons chercher les fichiers pyptoject.toml et poetry.lock, aux lignes 23 et 24, qui sont dans le dépôt de notre code pour pouvoir faire l’installation à la ligne 25.
Ainsi, en combinant un tel container Docker avec l’outil Poetry, on est capable d’avoir une maîtrise parfaite de notre environnement Python.
Conclusion
Poetry est l’outil idéal pour maîtriser son environnement et le répliquer entre différents postes de développeurs ou avec les plateformes de test, recette ou production. Il permet également de remettre à jour cet environnement en résolvant toutes les problématiques de dépendance.
En combinaison avec Docker, il permet au développeur de résoudre toutes les problématiques de gestion d’environnement, au sens large.
Références
[1] Registre PIP : https://pypi.org/project/poetry/
[2] Documentation officielle : https://python-poetry.org/docs/