Stella

Pour vous accompagner dans vos choix numériques

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 :

  1. Le sac de nœuds
  2. Les racines du mal
  3. La folie des formats
  4. Des fichiers partout
  5. La boîte à outils
  6. L’expression des besoins
  7. 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

Mandatory Related XKCD™
Le XKCD obligatoire. Et si la construction de votre wheel échoue, n’abandonnez pas, la routourne va tourner.

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 :

  1. à lire les sources,
  2. à créer des paquets pour d’autres gestionnaires de paquets (par exemple pour les distributions Linux),
  3. à 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

Un marteau
Envie de planter un clou ? Utilisez donc un marteau ! Cet outil est parfait pour planter des clous, et… C’est tout, en fait. Ça plante des clous, ça ne fait que ça, mais ça le fait bien.

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 :

  1. le dossier contenant le code de votre projet,
  2. un dossier doc,
  3. un dossier tests,
  4. un fichier LICENSE,
  5. un fichier README,
  6. 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.

Des autruches
Oui, des autruches. Quand on ne trouve pas d’image de papillon ou de libellule, on fait ce qu’on peut avec ce qu’on a…

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.