L’enfer des paquets Python : la solution minimale (7 / 7)
Nous y sommes. Nous avons défait des nœuds, creusé jusqu’aux racines, décortiqué des formats, éparpillé des fichiers, ouvert une boîte à outils et défini des besoins. Il nous faudrait peut-être penser à faire un paquet maintenant !
💕💕💕
Cet article fait partie d’une série de 7 articles larmoyants sur la création de paquets Python :
- Le sac de nœuds
- Les racines du mal
- La folie des formats
- Des fichiers partout
- La boîte à outils
- L’expression des besoins
- La solution minimale
Avant toute autre chose, j’aimerais faire un gros bisou à l’ensemble des membres de l’équipe PyPA. Je me plains beaucoup dans cette suite d’articles, cela ne m’empêche pas d’avoir une immense et sincère dose de respect pour le travail sisyphéen déjà accompli.
Ceci étant dit : nous voilà (re)partis pour le chouinage 😭.
💕💕💕
C’est où qu’on met quoi ?
Nous avons vu dans l’article précédent tout ce que l’on peut mettre dans nos paquets. Ça fait une bonne dose de choses à inclure, mais on n’a pas encore vraiment déterminé ni où ni comment.
Comme cet article n’est qu’un exemple parmi tant d’autres, nous n’aurons aucun scrupule à proposer des avis purement subjectifs basés sur une sensibilité toute particulière. Ce n’est pas la vérité absolue, c’est juste un avis. Mais comme c’est le nôtre il est forcément bien, sinon vous seriez en train de lire autre chose.
Le paquet wheel
Le paquet wheel, c’est le niveau 1 du paquet. C’est non seulement le plus simple à créer, mais également celui qui sera le plus utilisé.
Le but de ce paquet est de fournir le code fonctionnel déjà tout prêt
pour la cible. Le pip
qui l’installera n’aura rien à
faire d’autre que de décompresser l’archive, récupérer quelques
informations (comme les dépendances) et mettre le dossier décompressé
sur le disque. Pas de compilation, pas d’exécution de code Python
arbitraire, pas de cas particulier pour telle ou telle plateforme.
Cela signifie accessoirement que, si votre code n’est pas le même selon la version de Python ou le système d’exploitation, vous devrez créer autant de paquets qu’il y a de combinaisons différentes d’ordinateurs sur lesquels installer votre superbe création. Faut bien bosser un peu, quand même.
Qu’importe : nous avons des outils à disposition pour faire
cela. L’important est de se mettre déjà d’accord sur ce que l’on veut
mettre dedans. À ce sujet, mon avis est simple : le code, les
métadonnées, et à la limite un ou deux fichiers comme le
README
ou la licence.
Le reste, ça dégage.
Pas d’états d’âme. Qui a déjà installé une wheel pour aller regarder le
CHANGELOG
à l’intérieur ? Qui a déjà installé une wheel
pour exécuter les tests unitaires ? Qui a déjà installé une wheel pour
reconstruire la documentation du module ? Si vous l’avez déjà fait, il
est grand temps pour vous de découvrir un outil formidable qui
s’appelle Internet. C’est plein de pages super jolies qui présentent la
documentation avec des couleurs, des images, et même des liens.
Plus sérieusement : les wheels n’ont pas d’autre vocation que de
s’installer. Peu importe comment elles fonctionnent, ce qu’elles
contiennent, l’important est qu’elles s’installent correctement avec
pip
. Tout le reste est secondaire. Vraiment. Adieu les
tests, adieu la doc, adieu les fichiers de configuration.
Le paquet source
Les sources, c’est la base, c’est la solution de rechange, c’est le dernier recours, c’est l’exhaustivité ultime, et c’est pour ça que les wheels n’enverront jamais dans l’oubli ce vénérable format qui a accompagné les utilisateurs de Python depuis les débuts de la diffusion de paquets.
Avant de déterminer ce que l’on mettra dedans, réglons tout de suite une question déterminante : à quoi va servir concrètement notre paquet source ? Je crois que la question, elle est vite répondue :
- à lire les sources,
- à créer des paquets pour d’autres gestionnaires de paquets (par exemple pour les distributions Linux),
- à installer des paquets dans les cas désespérés.
Dans ces cas-là, il est souvent utile d’avoir accès à un peu plus
d’informations que celles contenues dans le paquet wheel. On aime avoir
quelques fichiers à côté qui nous expliquent les changements à chaque
version, quelques tests pour voir comment ça marche, un peu de
documentation à lire avec un éditeur de texte… On peut avoir également
envie de voir comment le paquet est configuré, de jouer avec le
setup.py
, voire de s’aventurer à tripatouiller le code.
Le paquet source contiendra donc, à peu de choses près, le même contenu
que celui du dépôt versionné. On doit récupérer dans les grandes lignes
ce que l’on aurait récupéré avec un git clone
(ou l’équivalent avec votre logiciel de gestion de versions favori, on
n’est pas sectaires). On enlèvera évidemment le versionnement en
lui-même et quelques détails comme la configuration de l’intégration
continue.
À l’attaque
Avec toutes ces précisions, il nous est désormais possible de nous aventurer dans la création de paquets. Nous n’irons pas dans le détail de chaque ligne de configuration ou de code à écrire (il faut bien vous laisser un peu de liberté !), mais nous tâcherons au moins de mettre en place les bases nécessaires à la création de vos paquets.
À paquet simple, solution simple
Nous allons nous débarrasser comme ça, en quelques mots, d’un énorme non-dit qui nous encombre. Vous voulez faire un paquet ? Disons que votre code ne contient que du Python, et que vous voulez suivre les règles que nous avons fixées auparavant. Voilà, ça va déjà mieux.
Nous sommes d’accord ? Sinon, vous pouvez toujours vous consoler avec
un autre article, quelque part sur Internet, qui parle de
setuptools
. Je vous laisse chercher.
L’outil
Sans plus de suspense, l’outil que nous allons utiliser s’appelle Flit.
Flit, c’est le marteau de la création de paquets. C’est limité, ça ne fait qu’une chose, mais c’est clair, limpide, efficace. Nous voulons créer et diffuser des paquets avec des règles simples à suivre bêtement, c’est tout.
Flit, c’est aussi l’un des outils à la base des
PEP 517 et
PEP 518. Oui,
c’est entre autres grâce à son créateur Thomas Kluyver que nous avons
désormais la possibilité de nous affranchir de setuptools
et de setup.py
. Respect.
Flit, c’est avant tout la simplicité. Si vous ne voulez pas vous poser de questions sur la création de paquets, si vous ne voulez pas écrire de code autre que votre module, c’est le choix de la raison.
L’architecture
Oubliez les tonnes de fichiers et les configurations à n’en plus finir. Si l’on vise la simplicité, il va nous falloir éradiquer l’obésité des dossiers racines. Nous allons drastiquement alléger la page d’accueil de votre dépôt.
Nous avons par le passé pris quelques exemples d’ahurissantes overdoses. Sans nous replonger dans l’ensemble des projets que nous avons déjà cités, prenons seulement un exemple de ce que nous ne voulons pas : Requests.
(Requests n’est pas le mal absolu, attention, ne me faites pas dire ce que je n’ai pas écrit. Simplement, c’est un bon exemple de ce que nous ne voulons pas.)
Le dossier racine contient 22 fichiers et dossiers. On retrouve à
l’intérieur les suspects habituels de la création de paquets :
setup.py
, setup.cfg
, MANIFEST.in
,
Pipfile
… On retrouve également les jolis fichiers de
configuration des outils tiers : Tox, Coverage, Pytest. Quelques
métadonnées, quelques dossiers, voilà de quoi impressionner n’importe
quelle personne qui voudrait prendre ce projet très connu en exemple
pour comprendre comment fonctionnent les modules Python.
À l’opposé de ce projet, je vous propose 3 dossiers et 3 fichiers de base à inclure dans votre dossier racine :
- le dossier contenant le code de votre projet,
- un dossier
doc
, - un dossier
tests
, - un fichier
LICENSE
, - un fichier
README
, - un fichier
pyproject.toml
.
Bien sûr, ce n’est qu’une base que vous pourrez adapter à vos besoins. Mais se restreindre à garder un dossier racine léger et propre est également un bon prétexte pour réfléchir à l’hygiène et la structuration de son projet. Voyons de ce pas ce que nous pouvons faire entrer dans ces petites cases…
Les dossiers
Dans le dossier contenant le code de votre projet, vous allez avant tout mettre… le code de votre projet. Cela n’a l’air de rien, mais si l’on se tient à notre idée d’avoir une wheel minimaliste, on comprend rapidement que ce dossier sera celui qui sera prêt à être décompressé. Dans le simple but d’installer le module, le reste n’est que décoration.
Une conséquence de ce découpage est que ce dossier doit inclure les fichiers annexes nécessaires au fonctionnement du module. Les images de votre jeu ? À inclure dans ce dossier. Les listes de mots de passes connus pour votre outil de piratage de la NSA ? À inclure aussi.
Cela explique également pourquoi on n’inclut dans ce dossier ni tests, ni documentation. Tout le monde sait que les tests et la documentation ne servent à rien quand le code est limpide et dénué d’erreurs. Cependant, dans le doute, en attendant que tous les humains soient omniscients, en attendant de pouvoir parfaitement nous passer de ces broutilles, nous pourrions conserver toutes ces reliques, mais seulement dans le paquet source.
Les tests, s’ils suivent les conventions de nommage de votre outil
favori, seront automatiquement découverts. À cet égard,
tests
semble être un nom particulièrement adapté, à la
fois pour les humains et pour les outils (Flit ou Pytest, par
exemple). Après, libre à vous d’organiser vos tests comme bon vous
semble, mais vous aiderez tout le monde en les mettant tous dans le
même dossier, à la racine de votre projet.
La documentation a, elle aussi, de bonnes raisons d’être dans le paquet source. Vous donnez aux curieuses et aux curieux la possibilité d’aller fouiller dans les limpides explications sur votre projet, tout à côté de votre code, sans accès Internet requis. Vous donnez aux distributions Linux la possibilité d’inclure une appréciable présentation de votre outil, mais également d’éventuelles pages de manuel. En fait, vous donnez à n’importe qui la possibilité de faire n’importe quoi avec du contenu qui aide les gens, et dans ce cas-là vous n’êtes jamais à l’abri de bonnes surprises.
Cette documentation est également l’endroit idéal où stocker certaines
informations que l’on pourrait avoir envie de mettre à la racine, comme
un CHANGELOG
ou des exemples de configuration. Elles
seront ainsi disponibles dans un format agréable à lire, en plus d’être
toujours accessibles dans des fichiers texte. Les plateformes
d’hébergement de code proposent aussi des pages dédiées à certaines de
ces informations, rendant inutiles bon nombre de fichiers superflus à
la racine. Et rien ne vous empêche, si vous en avez réellement envie,
de mettre des liens dans votre README
pour
aiguiller les gens qui ne cherchent qu’à la racine.
Les fichiers
Le fichier README
est la base de votre projet. Dans un
format en pur texte ou avec un balisage léger, c’est la porte d’entrée
par laquelle une bonne partie des gens intéressés par la technique vont
venir. Il a également de bonnes chances d’être mis en avant dans votre
forge logicielle et sur PyPI.
Voilà donc une excellente raison de bien travailler votre
README
. Au-delà de la description du projet, vous devez
pointer toutes les informations nécessaires que l’on aime généralement
trouver rapidement quand on découvre un projet : la licence, les
versions de Python supportées, les moyens de contacter celles et ceux
qui s’occupent de la maintenance…
D’ailleurs, placer la licence à la racine du projet, dans un fichier à
part, est un choix extrêmement classique mais discutable. Après tout,
cette information juridique n’aurait-elle pas sa place dans la
documentation ? Une ligne dans le README
ne suffirait-elle
pas à indiquer la licence qui s’applique au projet ?
Si, sans doute. Mais beaucoup d’outils s’attendent à trouver à la racine ce fichier, voire même le lisent pour en déduire la licence du projet. S’il est facile de faire changer les habitudes des gens, qui se satisferaient sans doute d’une indication dans la documentation, il est plus complexe de faire évoluer les habitudes des machines. Alors… Disons que ce choix est un petit arrangement entre l’idéal et la réalité. Œuvrons pour que dans quelques années nous puissions nous en passer plus simplement.
Enfin, il reste bien sûr le plat de résistance : pyproject.toml
.
Ce fichier vous permet tout d’abord d’indiquer
tout ce qu’il faut pour la création du paquet.
Les choix par défaut de Flit étant particulièrement bons (en toute
objectivité), vous ne devriez pas avoir besoin de changer grand chose
aux valeurs proposées. Mais sachez que si l’envie s’en fait sentir,
vous trouverez une bonne liste d’options qui devraient satisfaire vos
idées les plus folles.
Avec Flit, pyproject.toml
va remplacer ce que vous pouvez
faire avec (au moins) setup.py
, setup.cfg
,
requirements.txt
et MANIFEST.in
. Évidemment,
les possibilités sont limitées, ne serait-ce que parce que vous ne
pouvez pas écrire de code Python pour commettre des atrocités exécutées
lors de la création ou l’installation d’un paquet. Mais ce n’est pas
une limitation, c’est une fonctionnalité : fini de jouer avec cette
idée initialement intéressante mais devenue plus qu’immonde, il serait
peut-être plus utile pour la postérité d’écrire votre module.
C’est également ce fichier qui va vous permettre de configurer une très grande partie des outils annexes que vous utilisez : Tox, Black, Pytest, Coverage.py, isort, Pylint… Oui, cela signifie que vous pouvez dire adieu aux montagnes de fichiers de configuration utilisant chacun leur convention de nommage et leur format ! La liste des outils supportés s’agrandit régulièrement, n’hésitez pas à jeter un coup d’œil de temps en temps pour voir si votre projet favori a osé sauter le pas.
De la création au déploiement
Ne vous attendez pas à un tutoriel dans lequel je vous tiendrais la main, j’écrirais vos fichiers de configuration, et je vous donnerais toutes les astuces pour utiliser Flit. L’article ne s’appelle pas « Les 7 trucs que vous ne connaissez pas sur Flit − le cinquième va vous surprendre » (pas sûr qu’on explose les statistiques de visites avec un titre comme ça, cela dit).
Pourquoi ? Simplement parce que la documentation de Flit est formidable. Vous y trouverez ce qu’il faut pour installer et utiliser Flit les yeux fermés (ou presque). C’est limpide, c’est rapide, et surtout ça marche.
init
, install
, build
,
publish
. C’est tout ce dont vous aurez besoin pour modeler
vos petits paquets avec amour. Plus besoin de subir ma prose
désobligeante, je vous laisse entre les mots délicats d’un outil qui
l’est tout autant.
Prenez plaisir à voler de vos propres ailes, laissez-vous porter au gré du vent.
Pour le reste…
Ne nous mentons pas : Flit ne résout pas tout. Nous avons déjà vu ses limitations et ses petites faiblesses, mais il n’est heureusement pas seul.
Vous voulez un autre outil qui vaut également largement le détour ? Poetry peut faire ce que fait Flit, mais il fait beaucoup plus encore : de la gestion d’environnements virtuels, de la résolution de dépendances, de l’installation de paquets, de la numérotation de versions… Si vous aimez les outils tout-en-un qui évitent l’écueil de devenir des mastodontes tentaculaires et mégalomanes impossibles à maintenir (je ne donnerai pas de nom), vous trouverez en Poetry un élégant remplaçant à Pipenv (oups, j’ai craqué, désolé).
Mais… Que ce soit sur Flit ou Poetry, une grande ombre plane :
l’abandon. Flit et Poetry ont beau être largement utilisés, ils n’en
restent pas moins des outils tiers qui ne sont pas supportés comme
l’est setuptools
. À l’instar de nombre de projets libres,
ils ont d’ailleurs déjà eu des
coups de pompe,
et ils en connaîtront encore d’autres.
Heureusement, les PEP sont maintenant largement adoptées, laissant la
porte ouverte à d’autres projets futurs. Sortis du carcan de
setuptools
, nous pouvons à loisir utiliser d’autres outils
basés sur pyproject.toml
. Les clés et valeurs des options
changeront, mais au moins n’aurons nous plus besoin de dépendre d’une
implémentation unique sclérosée par le poids de l’historique et le
besoin de rétrocompatibilité.
Les wheels resteront les wheels, les sources resteront les sources, et tout sera pour le mieux dans le meilleur des mondes.
Les outils passent mais les formats restent.