URL: https://linuxfr.org/news/l-installation-et-la-distribution-de-paquets-python-2-4 Title: L'installation et la distribution de paquets Python (2/4) Authors: jeanas BenoĂźt Sibaud, Nils Ratusznik et Ysabeau đŸ§¶ Date: 2023ćčŽ12月17æ—„T00:34:42+01:00 License: CC By-SA Tags: python, packaging, distribution et installation Score: 65 Cette dĂ©pĂȘche est la deuxiĂšme d’une sĂ©rie de quatre sur le *packaging* en Python : 1. [L’histoire du *packaging* Python](https://linuxfr.org/news/l-installation-et-la-distribution-de-paquets-python-1-4) 2. **Tour de l’écosystĂšme actuel** 3. Le casse-tĂȘte du code compilĂ© 4. La structure de la communautĂ© en question Je vais donc proposer un aperçu plus ou moins complet des diffĂ©rents outils, et de ce qu’ils font ou ne font pas, en essayant de les comparer. Mais je parlerai aussi des fichiers de configuration, des dĂ©pĂŽts oĂč les paquets sont publiĂ©s, des maniĂšres d’installer Python lui-mĂȘme, et de l’interaction de tout ceci avec les distributions Linux. En revanche, je laisse de cĂŽtĂ© pour l’instant les paquets Ă©crits en C, C++ ou Rust et la complexitĂ© qu’ils apportent. ---- [Guide officiel du packaging Python](https://packaging.python.org/) [Liste de projets (sur ce mĂȘme guide)](https://packaging.python.org/en/latest/key_projects/) ---- Commençons par un outil dont Ă  peu prĂšs tout utilisateur de Python a entendu parler : pip. # [Pip](https://pip.pypa.io) L’installeur de paquets pip est un outil fondamental et omniprĂ©sent. Son nom est un acronyme rĂ©cursif signifiant « Pip Installs Packages ». Il permet d’installer un paquet depuis le PyPI, mais aussi depuis une copie locale du code source, ou encore depuis un dĂ©pĂŽt Git. Il dispose, bien sĂ»r, d’un rĂ©solveur de dĂ©pendances pour trouver des versions Ă  installer qui soient compatibles entre elles. Il possĂšde aussi un certain nombre de fonctionnalitĂ©s standard pour un gestionnaire de paquets, comme dĂ©sinstaller un paquet, lister les paquets installĂ©s, etc. S’il y a un outil de packaging quasi universel, c’est bien pip. Par exemple, la page de chaque paquet sur PyPI ([exemple](https://pypi.org/project/numpy)) affiche une commande pour l’installer, Ă  savoir `pip install `. Quand la documentation d’un paquet donne des instructions d’installation, elles utilisent gĂ©nĂ©ralement pip. De plus, la distribution officielle de Python permet de *boostraper* trĂšs simplement pip avec la commande `python -m ensurepip`. Cela rend pip trĂšs facile Ă  installer, et lui donne un caractĂšre officiel, soutenu par les dĂ©veloppeurs de Python, caractĂšre que n’ont pas la plupart des autres outils que je vais mentionner. MĂȘme les autres outils qui installent aussi des paquets depuis le PyPI (comme pipx, Hatch, tox, etc.) le font presque tous en utilisant, en interne, pip (sauf Poetry qui est un peu Ă  part). Dans l’univers parallĂšle de Conda et Anaconda, les utilisateurs sont souvent obligĂ©s d’utiliser pip dans un environnement Conda parce qu’un paquet donnĂ© n’est pas disponible au format Conda (ce qui crĂ©e, d’ailleurs, des problĂšmes de compatibilitĂ©, mais c’est un autre sujet). ## Les dangers de pip sous Linux Malheureusement, sous Linux spĂ©cifiquement, l’interface en ligne de commande de pip a longtemps Ă©tĂ© un moyen trĂšs facile de se tirer une balle dans le pied. En effet, la commande simple ```shell pip install ``` tentait d’installer le paquet au niveau du systĂšme, de maniĂšre visible pour tous les utilisateurs de la machine (typiquement dans `/usr/lib/pythonX.Y/site-packages/`). Bien sĂ»r, il faut des permissions pour cela. Que fait Monsieur Toutlemonde quand il voit « permission denied error » ? ÉlĂ©mentaire, mon cher Watson : ```shell sudo pip install ``` Or, sous Linux, installer des paquets avec pip au niveau du systĂšme, c’est mal. Je rĂ©pĂšte : c’est **MAL**. Ou plutĂŽt, c’est valable dans 0,1% des cas et dangereux dans 99,9% des cas. J’insiste : ne faites **JAMAIS** `sudo pip install` ou `sudo pip uninstall`. (Sauf si vous savez *parfaitement* ce que vous faites et que vous avez scrupuleusement vĂ©rifiĂ© qu’il n’y a aucun conflit.) Le souci ? Les distributions Linux contiennent, elles aussi, des paquets Ă©crits en Python, qui sont installĂ©s au mĂȘme endroit que celui dans lequel installe la commande `sudo pip install`. Pip peut donc Ă©craser un paquet installĂ© par le systĂšme avec une version diffĂ©rente du mĂȘme paquet, potentiellement incompatible avec le reste, ce qui peut avoir des consĂ©quences catastrophiques. Il suffit de penser que DNF, le gestionnaire de paquets de Fedora, est Ă©crit en Python, pour avoir une idĂ©e des dĂ©gĂąts potentiels ! Aujourd’hui, heureusement, la commande `pip install ` (sans `sudo`), au lieu d’échouer avec une erreur de permissions, installe par dĂ©faut dans un emplacement spĂ©cifique Ă  l’utilisateur, typiquement `~/.local/lib/pythonX.Y/site-packages/` (ce qui devait auparavant se faire avec `pip install --user `, l’option `--user` restant disponible si on veut ĂȘtre explicite). De plus, pip Ă©met un avertissement sous Linux lorsqu’exĂ©cutĂ© avec les droits root ([source](https://github.com/pypa/pip/pull/9394)). Ainsi, `pip install ` est devenu beaucoup moins dangereux. Attention, j’ai bien dit *moins* dangereux... mais dangereux quand mĂȘme ! Pourquoi, s’il n’efface plus les paquets du systĂšme ? Parce que si un paquet est installĂ© Ă  la fois par le systĂšme, et par pip au niveau de l’utilisateur, la version de pip va prendre le dessus, car le dossier utilisateur a prioritĂ© sur le dossier systĂšme. Le rĂ©sultat est que le conflit, en rĂ©alitĂ©, persiste : il reste possible de casser un paquet systĂšme en installant une version incompatible avec pip au niveau utilisateur. Seulement, c’est beaucoup plus facile Ă  corriger (il suffit d’un `rm -rf ~/.local/lib/pythonX.Y/site-packages/*`, alors qu’un conflit dans le dossier systĂšme peut ĂȘtre quasi irrĂ©parable). La seule option qui soit sans danger est de ne jamais rien installer en dehors d’un environnement virtuel (voir plus bas pour les instructions). Pour finir, la [PEP 668](https://peps.python.org/668) a créé un mĂ©canisme pour qu’une distribution Linux puisse marquer les dossiers de paquets Python qu’elle contrĂŽle. Pip refuse (par dĂ©faut) de modifier ces dossiers et affiche un avertissement qui mentionne les environnements virtuels. Debian (Ă  partir de Debian Bookworm), Ubuntu (Ă  partir d’Ubuntu Lunar) et d’autres distributions Linux, ont choisi de mettre en place cette protection. Donc, dĂ©sormais, `sudo` ou pas, `pip install` en dehors d’un environnement virtuel donne une erreur (on peut forcer l’opĂ©ration avec l’option `--break-system-packages`). En revanche, Fedora [n’a pas implĂ©mentĂ© la protection](https://discussion.fedoraproject.org/t/status-of-marking-the-base-python-environment-as-externally-managed-pep-668), espĂ©rant rĂ©ussir Ă  crĂ©er un dossier pour pip qui soit au niveau systĂšme mais sĂ©parĂ© du dossier de la distribution Linux, pour que `pip install` soit complĂštement sĂ»r et qu’il n’y ait pas besoin de cette protection. Je recommande [la prĂ©sentation de Miro Hrončok](https://www.youtube.com/watch?v=7SBH4PkbMhw) Ă  la confĂ©rence PyCon polonaise en janvier 2023, qui explique le casse-tĂȘte dans les menus dĂ©tails. Petite citation en avant-goĂ»t : « The fix is quite trivial when you design it, and it only strikes back when you actually try to do it ». ## Pip est un outil de bas niveau Pip a une autre chausse-trappe qui est surprenant quand on est habituĂ© au gestionnaire de paquets d’une distribution Linux. Petite illustration : ```text $ python -m venv my-venv/ # crĂ©e un environnement isolĂ© vide pour la dĂ©monstration $ source my-venv/bin/activate # active l’environnement $ pip install myst-parser [...] Successfully installed MarkupSafe-2.1.3 Pygments-2.16.1 alabaster-0.7.13 [...] [...] $ pip install mdformat-deflist [...] Installing collected packages: markdown-it-py, mdit-py-plugins, mdformat, mdformat-deflist [...] ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. myst-parser 2.0.0 requires markdown-it-py~=3.0, but you have markdown-it-py 2.2.0 which is incompatible. myst-parser 2.0.0 requires mdit-py-plugins~=0.4, but you have mdit-py-plugins 0.3.5 which is incompatible. Successfully installed markdown-it-py-2.2.0 mdformat-0.7.17 mdformat-deflist-0.1.2 mdit-py-plugins-0.3.5 [...] $ echo $? 0 ``` Comme on peut le voir, *la rĂ©solution des dĂ©pendances par pip ne prend pas en compte les paquets dĂ©jĂ  installĂ©s dans l’environnement*. Autrement dit, pour installer un paquet X, pip va simplement regarder quelles sont les dĂ©pendances de X (y compris les dĂ©pendances transitives), trouver un ensemble de versions qui soient compatibles entre elles, et les installer. Pip ne vĂ©rifie pas que les versions des paquets sont aussi compatibles avec ceux qui sont dĂ©jĂ  installĂ©s. Ou plutĂŽt, il les vĂ©rifie, mais seulement *aprĂšs* avoir fait l’installation, Ă  un moment oĂč le mal est dĂ©jĂ  fait, et uniquement pour afficher un avertissement. Dans l’exemple ci-dessus, on installe d’abord `myst-parser`, dont la derniĂšre version dĂ©pend de `markdown-it-py` version 3.x, puis on installe `mdformat-deflist`, qui dĂ©pend de `markdown-it-py` version 1.x ou 2.x. En installant `mdformat-deflist`, Pip installe aussi, comme dĂ©pendance, `markdown-it-py` 2.x, ce qui casse le `myst-parser` installĂ© prĂ©cĂ©demment. Ceci n’est naturellement pas du goĂ»t de tout le monde (je me rappelle d’ailleurs d’une enquĂȘte utilisateur faite par les dĂ©veloppeurs de Pip il y a quelques annĂ©es, oĂč ils posaient la question de savoir ce que Pip devait faire dans cette situation). La morale est que pip est surtout un outil conçu pour crĂ©er un environnement virtuel oĂč se trouvent toutes les dĂ©pendances dont on a besoin, pas pour s’en servir comme de `apt` ou `dnf`, en installant et dĂ©sinstallant manuellement des dĂ©pendances. Et surtout, que `pip install X; pip install Y` **n’est absolument pas Ă©quivalent** Ă  `pip install X Y`, et c’est la seconde forme qui est correcte. # Les environnements virtuels : [venv](https://docs.python.org/3/library/venv.html), [virtualenv](https://virtualenv.pypa.io/), [pipx](https://pypa.github.io/pipx) Les environnements virtuels permettent de travailler avec des ensembles de paquets diffĂ©rents, installĂ©s de façon indĂ©pendante entre eux. L’outil d’origine pour les crĂ©er est virtualenv. NĂ©anmoins, le plus utilisĂ© aujourd’hui est venv, qui est une version rĂ©duite de virtualenv intĂ©grĂ©e Ă  la bibliothĂšque standard. Malheureusement, venv est plus lent et n’a pas toutes les fonctionnalitĂ©s de virtualenv, qui reste donc utilisĂ© Ă©galement... Pour crĂ©er un environnement virtuel (avec venv), on exĂ©cute : ```shell python -m venv nom-de-l-environnement ``` Cela crĂ©e un dossier `nom-de-l-environnement/`. Chaque environnement est donc stockĂ© dans un dossier. À l’intĂ©rieur de ce dossier se trouve notamment un sous-dossier `bin/` avec des exĂ©cutables : - un exĂ©cutable `python`, qui ouvre un interprĂ©teur Python ayant accĂšs aux paquets de l’environnement virtuel (et, par dĂ©faut, seulement eux), - un exĂ©cutable `pip`, qui installe les paquets Ă  l’intĂ©rieur de l’environnement. De plus, pour simplifier l’utilisation dans un shell, on peut « activer » l’environnement, avec une commande qui dĂ©pend du shell. Par exemple, sous les shells UNIX classiques (`bash`, `zsh`), on exĂ©cute ```shell source nom-de-l-environnement/bin/activate ``` Cette commande modifie la variable `PATH` pour y ajouter `nom-de-l-environnement/bin/` afin que (par exemple) la commande `python` invoque `nom-de-l-environnement/bin/python`. MalgrĂ© cela, les environnements virtuels restent un niveau de confort en dessous du Python du systĂšme, puisqu’il faut activer un environnement avant de s’en servir, ou Ă©crire Ă  chaque fois le chemin `dossier-environnement/bin/`. Bien sĂ»r, il faut aussi mĂ©moriser les commandes, et puis c’est si facile de faire `pip install` dans l’environnement global (non virtuel). Donc, beaucoup n’y prĂȘtent malheureusement pas attention et installent au niveau global, ce qui cause des conflits de dĂ©pendances (c’est maintenant refusĂ© par dĂ©faut sous Debian et dĂ©rivĂ©s, comme je l’expliquais dans la section prĂ©cĂ©dente, mais c’est toujours majoritaire sous macOS et Windows). C’est aussi pour rendre plus pratiques les environnements virtuels qu’existent plĂ©thore d’outils qui les crĂ©ent et/ou activent pour vous. Je termine avec l’un de ces outils, liĂ© Ă  la fois Ă  pip et aux environnements virtuels, j’ai nommĂ© pipx. À premiĂšre vue, pipx a une interface qui ressemble Ă  celle de pip, avec par exemple des sous-commandes `pipx install`, `pipx uninstall` et `pipx list`. Mais, Ă  la diffĂ©rence de pip, qui installe un paquet dans un environnement dĂ©jĂ  créé, pipx va, pour chaque paquet installĂ©, crĂ©er un nouvel environnement virtuel dĂ©diĂ©. Pipx est principalement destinĂ© Ă  installer des outils dont l’interface est en ligne de commande, pas sous forme d’un module importable en Python. Pipx utilise pip, pour ne pas trop rĂ©inventer la roue quand mĂȘme. Au final, ```shell $ pipx install pycowsay ``` revient Ă  quelque chose comme ```shell $ python -m venv ~/.local/pipx/pycowsay/ $ ~/.local/pipx/pycowsay/bin/pip install pycowsay $ ln -s ~/.local/pipx/pycowsay/bin/pycowsay ~/.local/bin/pycowsay ``` Pour rĂ©sumer, pipx permet d’installer des outils en ligne de commande, de maniĂšre isolĂ©e, qui n’interfĂšrent pas avec le systĂšme ou entre eux, sans avoir Ă  gĂ©rer les environnements virtuels soi-mĂȘme. # L’invocation d’un build backend : [build](https://pypa-build.readthedocs.io) Pour dĂ©poser son projet sur PyPI, il faut d’abord obtenir deux fichiers : une *sdist* (*source distribution*), qui est essentiellement une archive `.tar.gz` du code avec des mĂ©tadonnĂ©es ajoutĂ©es, et un paquet installable au format *wheel*, d’extension `.whl`. L’outil build sert Ă  gĂ©nĂ©rer ces deux fichiers. Il s’invoque comme ceci, dans le dossier du code source : ```shell python -m build ``` Petit exemple dans le dĂ©pĂŽt de Sphinx (l’outil de documentation le plus rĂ©pandu dans le monde Python) : ```text $ python -m build * Creating venv isolated environment... * Installing packages in isolated environment... (flit_core>=3.7) * Getting build dependencies for sdist... * Building sdist... * Building wheel from sdist * Creating venv isolated environment... * Installing packages in isolated environment... (flit_core>=3.7) * Getting build dependencies for wheel... * Building wheel... Successfully built sphinx-7.3.0.tar.gz and sphinx-7.3.0-py3-none-any.whl $ ls dist/ sphinx-7.3.0-py3-none-any.whl sphinx-7.3.0.tar.gz ``` Comme on peut le comprendre, build est un outil trĂšs simple. L’essentiel de sa documentation tient en [une courte page](https://pypa-build.readthedocs.io). Il crĂ©e un environnement virtuel pour installer le build backend, en l’occurrence Flit, puis se contente d’invoquer celui-ci. # Le transfert sur PyPI : [twine](https://twine.readthedocs.io) À l’image de build, twine est un outil fort simple qui remplit une seule fonction et la remplit bien : dĂ©poser la *sdist* et le *wheel* sur PyPI (ou un autre dĂ©pĂŽt de paquets). En continuant l’exemple prĂ©cĂ©dent, on Ă©crirait : ```shell twine upload dist/* ``` AprĂšs avoir fourni un login et mot de passe, le projet est publiĂ©, il peut ĂȘtre installĂ© avec pip, et possĂšde sa page `https://pypi.org/project/nom-du-projet`. # La configuration d’un projet : le fichier `pyproject.toml` `pyproject.toml` est le fichier de configuration adoptĂ© par Ă  peu prĂšs tous les outils de packaging, ainsi que de nombreux outils qui ne sont pas liĂ©s au packaging (par exemple les linters comme Ruff, les auto-formateurs comme Black ou le mĂȘme Ruff, etc.). Il est Ă©crit dans le langage de configuration [TOML](https://toml.io). On a besoin d’un `pyproject.toml` pour n’importe quel projet publiĂ© sur PyPI, et mĂȘme, souvent, pour les projets qui ne sont pas distribuĂ©s sur PyPI (comme pour configurer Ruff). Dans ce fichier se trouvent trois sections possibles — des « tables », en jargon TOML. La table `[build-system]` dĂ©termine le build backend du projet (je reviens plus bas sur le choix du build backend). La table `[project]` contient les informations de base, comme le nom du projet, la version, les dĂ©pendances, etc. Quant Ă  la table `[tool]`, elle est utilisĂ©e via des sous-tables `[tool.]` : tout outil peut lire de la configuration dans sa sous-table dĂ©diĂ©e. Rien n’est standardisĂ© par des spĂ©cifications PyPA dans la table `[tool]`, chaque outil y fait ce qu’il veut. Avant qu’on me [dise](https://linuxfr.org/news/l-installation-et-la-distribution-de-paquets-python-1-4#comment-1940643) que `pyproject.toml` est mal documentĂ©, ce qui a pu ĂȘtre vrai, je prĂ©cise que des efforts ont Ă©tĂ© faits Ă  ce niveau dans les derniĂšres semaines, par moi et d’autres, ce qui donne un [guide du pyproject.toml](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/) normalement complet et comprĂ©hensible, ainsi qu’une [explication](https://packaging.python.org/en/latest/discussions/setup-py-deprecated/) sur ce qui est dĂ©prĂ©ciĂ© ou non concernant `setup.py` et un [guide](https://packaging.python.org/en/latest/guides/modernize-setup-py-project/) sur la migration de `setup.py` vers `pyproject.toml`. Tout ceci rĂ©side sur [packaging.python.org](https://packaging.python.org), qui est un site officiel de la PyPA rassemblant des tutoriels, guides et spĂ©cifications techniques. # Les build backends pour code Python pur Le build backend est chargĂ© de gĂ©nĂ©rer les sdists et les wheels que l’on peut ensuite mettre sur PyPI avec twine ou autre. Il est spĂ©cifiĂ© dans le fichier `pyproject.toml`. Par exemple, pour utiliser Flit, la configuration est : ```toml [build-system] requires = ["flit_core>=3.7"] build-backend = "flit_core.buildapi" ``` `requires` est la liste des dĂ©pendances (des paquets sur PyPI), et `build-backend` est le nom d’un module (qui doit suivre une interface standardisĂ©e). Il peut sembler Ă©trange qu’il faille, mĂȘme pour un projet simple, choisir son build backend. Passons donc en revue les critĂšres de choix : de quoi est responsable le build backend ? D’abord, il doit traduire les mĂ©tadonnĂ©es du projet. En effet, dans les sdists et wheels, les mĂ©tadonnĂ©es sont encodĂ©es dans un format un peu Ă©trange, Ă  base de MIME, qui est conservĂ© au lieu d’un format plus moderne comme TOML ou JSON, pour des raisons de compatibilitĂ©. La plupart des build backends se contentent de prendre les valeurs dans la table `[project]` du `pyproject.toml` et de les copier directement sous la bonne forme, mais setuptools permet aussi de configurer les mĂ©tadonnĂ©es via `setup.py` ou `setup.cfg`, Ă©galement pour prĂ©server la compatibilitĂ©, et il y a aussi des build backends comme Poetry qui n’ont pas adoptĂ© la table `[project]` (j’y reviens dans la section sur Poetry). De plus, les build backends ont souvent des façons de calculer dynamiquement certaines mĂ©tadonnĂ©es, typiquement la version, qui peut ĂȘtre lue depuis un attribut `__version__`, ou dĂ©terminĂ©e Ă  partir du dernier tag Git. C’est aussi le build backend qui dĂ©cide des fichiers du projet Ă  inclure ou exclure dans la sdist et le wheel. En particulier, on trouve gĂ©nĂ©ralement des options qui permettent d’inclure des fichiers autres que `.py` dans le wheel (c’est le wheel qui dĂ©termine ce qui est installĂ© au final, alors que la sdist peut aussi contenir les tests etc.). Cela peut servir, par exemple, aux paquets qui doivent ĂȘtre distribuĂ©s avec des icĂŽnes, des donnĂ©es en JSON, des templates Django... Enfin, s’il y a des extensions en C, C++, Rust ou autre, le build backend est chargĂ© de les compiler. Il existe aujourd’hui de nombreux build backends. Beaucoup sont spĂ©cifiques Ă  un type d’extensions compilĂ©es, ils sont prĂ©sentĂ©s dans la troisiĂšme dĂ©pĂȘche. Voici les *build backends* principaux pour du code Python pur. ## [setuptools](https://setuptools.pypa.io) C’est le build backend historique. Il reste trĂšs largement utilisĂ©. Avant qu’arrive `pyproject.toml`, il n’y avait qu’un build backend, `setuptools`, et il Ă©tait configurĂ© soit par le `setup.py`, soit par un fichier en syntaxe INI, nommĂ© `setup.cfg` (qui est l’ancĂȘtre de `pyproject.toml`). Ainsi, il existe aujourd’hui trois maniĂšres diffĂ©rentes de configurer `setuptools`, Ă  savoir `setup.py`, `setup.cfg` et `pyproject.toml`. On rencontre les trois dans les projets existants. La façon recommandĂ©e aujourd’hui est `pyproject.toml` pour tout ce qui est statique, sachant que `setup.py`, qui est Ă©crit en Python, peut toujours servir s’il y a besoin de configuration programmable. Aujourd’hui, setuptools ne se veut plus qu’un build backend, mais historiquement, en tant que descendant de distutils, il a *beaucoup* de fonctionnalitĂ©s, dĂ©sormais dĂ©prĂ©ciĂ©es, pour installer des paquets ou autres. On peut se faire une idĂ©e de l’ampleur des Ă©volutions qui ont secouĂ© le *packaging* au fil des annĂ©es en parcourant l’abondante [documentation des fonctionnalitĂ©s obsolĂštes](https://setuptools.pypa.io/en/latest/deprecated/index.html), notamment [cette page](https://setuptools.pypa.io/en/latest/deprecated/easy_install.html), [celle-ci](https://setuptools.pypa.io/en/latest/deprecated/commands.html) ou encore [celle-lĂ ](https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html). ## [Flit](https://flit.pypa.io) Flit est l’exact opposĂ© de setuptools. C’est le build backend qui vise Ă  ĂȘtre le plus simple et minimal possible. Il n’y a pratiquement pas de configuration autre que la configuration standardisĂ©e des mĂ©tadonnĂ©es dans la table `[project]` du `pyproject.toml`. Flit se veut volontairement inflexible (« opinionated »), pour qu’il n’y ait pas de choix Ă  faire. Avec Flit, un projet appelĂ© `nom-projet` doit obligatoirement fournir un module et un seul, soit `nom_projet.py`, soit `nom_project/`. De mĂȘme, il est possible d’inclure des fichiers autres que `.py`, mais ils doivent obligatoirement se trouver tous dans un dossier dĂ©diĂ© au mĂȘme niveau que le `pyproject.toml`. Flit dispose aussi d’une interface en ligne de commande minimale, avec des commandes `flit build` (Ă©quivalent de `python -m build`), `flit publish` (Ă©quivalent de `twine upload`), `flit install` (Ă©quivalent de `pip install .`), et `flit init` (qui initialise un projet). ## [Hatchling](https://hatch.pypa.io) Hatchling est le build backend associĂ© Ă  Hatch, un outil tout-en-un dont il sera question plus loin. Contrairement Ă  setuptools, il est plutĂŽt facile d’utilisation, et il fait plus souvent ce qu’on veut par dĂ©faut. Contrairement Ă  Flit, il offre aussi des options de configuration plus avancĂ©es (comme pour inclure plusieurs modules dans un paquet), ainsi que la possibilitĂ© d’écrire des plugins. ## [PDM-Backend](https://backend.pdm-project.org/) De mĂȘme que hatchling est associĂ© Ă  Hatch, PDM-Backend est associĂ© Ă  PDM. Je n'en ai pas d'expĂ©rience, mais Ă  lire sa documentation, il me semble plus ou moins Ă©quivalent en fonctionnalitĂ©s Ă  hatchling, avec des options un peu moins fines. ## [Poetry-core](https://python-poetry.org/docs/pyproject/) Comme les deux prĂ©cĂ©dents, Poetry-core est associĂ© Ă  un outil plus vaste, Ă  savoir Poetry. Par rapport Ă  hatchling et PDM-backend, il est moins sophistiquĂ© (il ne permet pas de lire la version depuis un attribut dans le code ou depuis un tag Git). # La gestion des versions de Python : [pyenv](https://github.com/pyenv/pyenv) L’une des difficultĂ©s du *packaging* Python est que l’interprĂ©teur Python lui-mĂȘme n’est gĂ©nĂ©ralement pas compilĂ© *upstream* et tĂ©lĂ©chargĂ© depuis le site officiel, du moins pas sous Linux (c’est davantage le cas sous Windows, et plus ou moins le cas sous macOS). L’interprĂ©teur est plutĂŽt fourni de l’extĂ©rieur, Ă  savoir, sous Linux, par le gestionnaire de paquets de la distribution, ou bien, sous macOS, par XCode, Homebrew ou MacPorts. Cela peut aussi ĂȘtre un Python compilĂ© Ă  partir du code source sur la machine de l’utilisateur. Ce modĂšle est diffĂ©rent d’autres langages comme Rust, par exemple. Pour installer Rust, la plupart des gens utilisent Rustup, un script qui tĂ©lĂ©charge des exĂ©cutables statiques compilĂ©s *upstream* (le fameux `curl | bash` tant dĂ©criĂ©...). Le but de `pyenv` est de simplifier la gestion des versions de Python. On exĂ©cute, par exemple, `pyenv install 3.10.2` pour installer Python 3.10.2. Comme `pyenv` va compiler le code source, il faut quand mĂȘme installer soi-mĂȘme les dĂ©pendances (avec leurs en-tĂȘtes C). # Un outil de test et d’automatisation : [tox](https://tox.wiki) À partir du moment oĂč un projet grossit, il devient souvent utile d’avoir de petits scripts qui automatisent des tĂąches courantes, comme exĂ©cuter les tests, mettre Ă  jour tel fichier Ă  partir de tel autre, ou encore compiler des catalogues de traduction en format MO Ă  partir des fichiers PO. Il devient Ă©galement nĂ©cessaire de tester le projet sur diffĂ©rentes versions de Python, ou encore avec diffĂ©rentes versions des dĂ©pendances. Tout cela est le rĂŽle de `tox`. Il se configure avec un fichier `tox.ini`. Voici un exemple [tirĂ© de Pygments](https://github.com/pygments/pygments/blob/8f3bec7/tox.ini): ``` [tox] envlist = py [testenv] description = run tests with pytest (you can pass extra arguments for pytest, e.g., "tox -- --update-goldens") deps = pytest>= 7.0 pytest-cov pytest-randomly wcag-contrast-ratio commands = pytest {posargs} use_develop = True ``` On peut avoir plusieurs sections `[testenv:xxx]` qui dĂ©finissent des environnements virtuels. Chaque environnement est créé avec une version de Python ainsi qu’une certaine liste de dĂ©pendances, et peut dĂ©clarer des commandes Ă  exĂ©cuter. Ces commandes ne passent pas par un shell, ce qui garantit que le `tox.ini` reste portable. # Interlude : vous avez dit lock file? Pour faire simple, un lock file est un fichier qui dĂ©crit de maniĂšre exacte un environnement de sorte qu’il puisse ĂȘtre reproduit. Prenons un exemple. Imaginons une application Web dĂ©ployĂ©e sur plusieurs serveurs, qui a besoin de la bibliothĂšque `requests`. Elle va dĂ©clarer cette dĂ©pendance dans sa configuration. Éventuellement, elle fixera une borne sur la version (par exemple `requests>=2.31`), pour ĂȘtre sĂ»re d’avoir une version compatible. Mais le paquet `requests` a lui-mĂȘme des dĂ©pendances. On souhaiterait que l’environnement soit vraiment reproductible — que des serveurs diffĂ©rents n’aient pas des versions diffĂ©rentes des dĂ©pendances, mĂȘme si les environnements sont installĂ©s Ă  des moments diffĂ©rents, entre lesquels des dĂ©pendances publient des nouvelles versions. Sinon, on risque des bugs difficiles Ă  comprendre qui ne se manifestent que sur l’un des serveurs. La mĂȘme problĂ©matique se pose pour dĂ©velopper une application Ă  plusieurs. Sauf si l’application doit ĂȘtre distribuĂ©e dans des environnements variĂ©s (par exemple empaquetĂ©e par des distributions Linux), il ne vaut pas la peine de s’embarrasser de versions diffĂ©rentes des dĂ©pendances. Il est plus simple de fixer toutes les versions pour tout le monde. Dans la vraie vie, une application peut avoir des centaines de dĂ©pendances, dont quelques-unes directes et les autres indirectes. Il devient trĂšs fastidieux de maintenir Ă  la main une liste des versions exactes de chaque dĂ©pendance. Avec un lock file, on s’assure de geler un ensemble de versions de tous les paquets qui sera le mĂȘme pour tous les contributeurs, et pour tous les dĂ©ploiements d’une application. On sĂ©pare, d’un cĂŽtĂ©, la dĂ©claration des dĂ©pendances directes minimales supposĂ©es compatibles avec l’application, Ă©crite Ă  la main, et de l’autre cĂŽtĂ©, la dĂ©claration des versions exactes de toutes les dĂ©pendances, gĂ©nĂ©rĂ©e automatiquement. ConcrĂštement, Ă  partir de la contrainte `requests>=2.31`, un gĂ©nĂ©rateur de lock file pourrait Ă©crire un lock file qui fixe les versions `certifi==2023ćčŽ11月17æ—„`, `charset-normalizer==3.3.2`, `idna==3.4`, `requests==2.31.0`, `urllib3==2.1.0`. À la prochaine mise Ă  jour du lock file, certaines de ces versions pourraient passer Ă  des versions plus rĂ©centes publiĂ©es entre-temps. Le concept de lock file est en revanche beaucoup plus discutable pour une bibliothĂšque (par opposition Ă  une application), Ă©tant donnĂ© qu’une bibliothĂšque est faite pour ĂȘtre utilisĂ©e dans d’autres projets, et que si un projet a besoin des bibliothĂšques A et B, oĂč A demande `requests==2.31.0` alors que B demande `requests==2.30.0`, il n’y a aucun moyen de satisfaire les dĂ©pendances. Pour cette raison, une bibliothĂšque doit essayer de minimiser les contraintes sur ses dĂ©pendances, ce qui est fondamentalement opposĂ© Ă  l’idĂ©e d’un lock file. Il existe plusieurs outils qui permettent de gĂ©nĂ©rer et d’utiliser un lock file. Malheureusement, l’un des plus gros problĂšmes actuels du packaging Python est le manque criant, sinon d’un outil, d’un *format* de lock file standardisĂ©. Il y a eu une tentative avec la [PEP 665](https://peps.python.org/665), rejetĂ©e par manque de consensus (mais avant qu’on soupire qu’il suffisait de se mettre d’accord : il y a de vraies questions techniques qui se posent, notamment sur l’adoption d’un standard qui ne permet pas de faire tout ce que font certains outils existants, qui risquerait de fragmenter encore plus au lieu d’aider). # Un gestionnaire de lock file : [pip-tools](https://pip-tools.readthedocs.io) pip-tools est un outil assez simple pour gĂ©nĂ©rer et utiliser un lock file. Il se compose de deux parties, `pip-compile` et `pip-sync`. La commande `pip-compile`, prend un ensemble de dĂ©clarations de dĂ©pendances, soit dans un `pyproject.toml`, soit dans un fichier spĂ©cial `requirements.in`. Elle gĂ©nĂšre un fichier `requirements.txt` qui peut ĂȘtre installĂ© par `pip`. Quant Ă  la commande `pip-sync`, c’est simplement un raccourci pour installer les dĂ©pendances du `requirements.txt`. Les locks files sont donc des fichiers `requirements.txt`, un format pas si bien dĂ©fini puisqu’un `requirements.txt` est en fait essentiellement une sĂ©rie d’arguments et d’options en ligne de commande Ă  passer Ă  pip. # Les outils « tout-en-un » Face Ă  la prolifĂ©ration d’outils Ă  installer et mĂ©moriser, certains ont essayĂ© de crĂ©er une expĂ©rience plus cohĂ©rente avec des outils unifiĂ©s. Malheureusement, ce serait trop simple s’ils s’étaient accordĂ©s sur un projet commun... ## [Poetry](https://python-poetry.org) Poetry est un outil un peu Ă  part qui fait Ă  peu prĂšs tout par lui-mĂȘme. Poetry se destine aux dĂ©veloppeurs de bibliothĂšques et d’applications. Toutefois, en pratique, il est plutĂŽt orientĂ© vers les applications. La configuration se fait entiĂšrement dans le fichier `pyproject.toml`. Poetry s’utilise toujours avec son build backend Poetry-core, donc la partie `[build-system]` du `pyproject.toml` est configurĂ©e comme ceci : ```toml [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" ``` En revanche, contrairement Ă  la plupart des autres build backends, Poetry n’accepte pas la configuration dans la table `[project]`. À la place, il faut Ă©crire les mĂ©tadonnĂ©es sur le projet (nom, version, mainteneurs, etc.) dans la table `[tool.poetry]`, dans un format diffĂ©rent du format standard. La particularitĂ© de Poetry qui le rend Ă  part est d’ĂȘtre centrĂ© profondĂ©ment sur le concept de lock file, d’insister fortement sur le [Semantic Versioning](https://semver.org), et d’avoir son propre rĂ©solveur de dĂ©pendances. Poetry n’installe jamais rien dans l’environnement virtuel du projet avant d’avoir gĂ©nĂ©rĂ© un lock file qui trace les versions installĂ©es. Sa configuration a aussi des raccourcis typiques du semantic versioning, comme la syntaxe `nom_du_paquet = "^3.4"`, qui dans la table `[project]` s’écrirait plutĂŽt `"nom_du_paquet>= 3.4, < 4`. (Ironiquement, les versions de Poetry lui-mĂȘme [ne suivent pas le Semantic Versioning](https://python-poetry.org/docs/faq/#why-does-poetry-not-adhere-to-semantic-versioning).) Je ne vais pas prĂ©senter les commandes de Poetry une par une car cette dĂ©pĂȘche est dĂ©jĂ  bien trop longue. Je renvoie Ă  la [documentation](https://python-poetry.org/docs/). Disons simplement qu’un projet gĂ©rĂ© avec Poetry se passe de pip, de build, de twine, de venv et de pip-tools. ## [PDM](https://pdm-project.org) Je l’avoue, je connais mal PDM. D’aprĂšs ce que j’en ai compris, il est assez semblable Ă  Poetry dans son interface, mais suit tout de mĂȘme plus les standards, en mettant sa configuration dans la table `[project]`, et en utilisant le rĂ©solveur de dĂ©pendances de pip. (Je parlerai tout de mĂȘme, dans la quatriĂšme dĂ©pĂȘche, de la motivation Ă  l’origine du dĂ©veloppement de PDM, qui cache toute une histoire. Pour ceux qui ont compris, oui, c’est bien rĂ©sumĂ© par un nombre qui est multiple de 97.) ## [Hatch](https://hatch.pypa.io) Hatch est plus rĂ©cent que Poetry et PDM. (Il n’est pas encore au niveau de Poetry en termes de popularitĂ©, mais loin devant PDM.) Il est encore en dĂ©veloppement rapide. ComparĂ© Ă  Poetry et PDM, il ne gĂšre pas, pour l’instant, les lock files. (Ici aussi, il y a une histoire intĂ©ressante : l’auteur a d’abord voulu attendre que les discussions de standardisation aboutissent Ă  un format commun, mais vu l’absence de progrĂšs, il a fait savoir rĂ©cemment qu’il allait implĂ©menter un format non standardisĂ©, comme Poetry et PDM le font dĂ©jĂ .) En contrepartie, Hatch gĂšre aussi les versions de Python. Il est capable d’installer ou de dĂ©sinstaller une version trĂšs simplement, sachant que, contrairement Ă  pyenv, il ne compile pas sur la machine de l’utilisateur mais tĂ©lĂ©charge des versions prĂ©compilĂ©es (beaucoup plus rapide, et aucune dĂ©pendance Ă  installer soi-mĂȘme). Il a aussi une commande `fmt` pour reformater le projet (plutĂŽt que de dĂ©finir soi-mĂȘme un environnement pour cela dans Poetry ou PDM), et il est [prĂ©vu](https://github.com/pypa/hatch/issues/1053) qu’il gagne bientĂŽt des commandes comme `hatch test` et `hatch doc` Ă©galement. De plus, dans Poetry, lorsque l’on dĂ©clare, par exemple, un environnement pour compiler la documentation, avec une dĂ©pendance sur `sphinx>= 7`, cette dĂ©pendance est rĂ©solue *en mĂȘme temps que les dĂ©pendances principales du projet*. Donc, si votre gĂ©nĂ©rateur de documentation demande une certaine version, mettons, de Jinja2 (ou n’importe quel autre paquet), vous ĂȘtes forcĂ© d’utiliser la mĂȘme version pour votre propre projet, mĂȘme si l’environnement pour exĂ©cuter votre projet n’a rien Ă  voir avec l’environnement pour gĂ©nĂ©rer sa documentation. C’est la mĂȘme chose avec PDM. Je trouve cette limitation assez frustrante, et Hatch n’a pas ce dĂ©faut. # La crĂ©ation d’exĂ©cutables indĂ©pendants et installeurs : PyInstaller, cx_freeze, briefcase, PyOxidizer (etc.) Distribuer son projet sur PyPI est bien beau, mais pour installer un paquet du PyPI, il faut d’abord avoir Python et savoir se servir d’un terminal pour lancer pip. Quid de la distribution d’une application graphique Ă  des gens qui n’ont aucune connaissance technique ? Pour cela, il existe une plĂ©thore d’outils qui crĂ©ent des installeurs, contenant Ă  la fois Python, une application, ses dĂ©pendances, une icĂŽne d’application, et en bref, tout ce qu’il faut pour satisfaire les utilisateurs qui n’y comprennent rien et rĂ©clament juste une « application normale » avec un « installeur normal », un `appli-setup.exe` ou `Appli.dmg`. Les plus connus sont [PyInstaller](https://pyinstaller.org) et [py2exe](https://pypi.org/project/py2exe). Plus rĂ©cemment est aussi apparu [briefcase](https://briefcase.readthedocs.io). Il y a aussi d’autres outils qui ne vont pas jusqu’à crĂ©er un installeur graphique, mais se contentent d’un exĂ©cutable qui peut ĂȘtre lancĂ© en ligne de commande. Ce sont notamment [cx\_freeze](https://cx-freeze.readthedocs.io) et [PyOxidizer](https://pyoxidizer.readthedocs.io), mais il y en a [bien d’autres](https://pyoxidizer.readthedocs.io/en/stable/pyoxidizer_comparisons.html). Malheureusement, toute cette classe d’usages est l’un des gros points faibles de l’écosystĂšme actuel. PyInstaller, par exemple, est fondĂ© sur des principes assez douteux qui datent d’une Ă©poque oĂč le packaging Ă©tait beaucoup moins Ă©voluĂ© qu’aujourd’hui (voir notamment [ce commentaire](https://discuss.python.org/t/installer-creation-based-on-distributions/34576/4)). Pour faire simple, PyInstaller dĂ©tecte les `import` dans le code pour trouver les fichiers Ă  inclure dans l’application, au lieu d’inclure toutes les dĂ©pendances dĂ©clarĂ©es par les mainteneurs. Il semble que briefcase soit meilleur de ce point de vue. De maniĂšre plus gĂ©nĂ©rale, embarquer un interprĂ©teur Python est techniquement compliquĂ©, notamment Ă  cause de l’interaction avec des bibliothĂšques systĂšme (comme OpenSSL), et chacun de ces projets rĂ©sout ces difficultĂ©s d’une maniĂšre diffĂ©rente qui a ses propres limitations. # Conda, un univers parallĂšle Comme expliquĂ© dans la premiĂšre dĂ©pĂȘche sur l’historique du packaging, Conda est un outil entiĂšrement indĂ©pendant de tout le reste. Il ne peut pas installer de paquets du PyPI, son format de paquet est diffĂ©rent, ses environnements virtuels sont diffĂ©rents. Il est dĂ©veloppĂ© par une entreprise, Anaconda Inc (mais publiĂ© sous une licence libre). Et surtout, bien que chacun puisse publier des paquets sur anaconda.org, il reste principalement utilisĂ© Ă  travers des *dĂ©pĂŽts de paquets* comprenant plusieurs milliers de paquets, qui sont gĂ©rĂ©s non pas par les auteurs du code concernĂ©, mais par des mainteneurs propres au dĂ©pĂŽt de paquets, Ă  la maniĂšre d’une distribution Linux, ou de Homebrew et MacPorts sous macOS. En pratique, les deux dĂ©pĂŽts principaux sont Anaconda, qui est maintenu par Anaconda Inc, et conda-forge, maintenu par une communautĂ© de volontaires. Quelques outils gravitent autour de Conda (mais beaucoup moins que les outils compatibles PyPI, car Conda est plus unifiĂ©). Je pense notamment Ă  [Condax](https://mariusvniekerk.github.io/condax), qui est Ă  Conda ce que pipx est Ă  pip. Il y a aussi [conda-lock](https://conda.github.io/conda-lock) pour les lock files. GrĂące Ă  son modĂšle, Conda permet une distribution trĂšs fiable des extensions C et C++, ce qui constitue son atout principal. Un inconvĂ©nient majeur est le manque de compatibilitĂ© avec PyPI, qui reste la source unique pour la plupart des paquets, Conda n’ayant que les plus populaires. # Petite comparaison des rĂ©solveurs de dĂ©pendances Les rĂ©solveurs de dĂ©pendances sont des composants invisibles, mais absolument cruciaux des systĂšmes de packaging. Un rĂ©solveur de dĂ©pendances prend un ensemble de paquets, et de contraintes sur ces paquets, de la forme « l’utilisateur demande le paquet A version X.Y au minimum et version Z.T au maximum », ou « le paquet A version X.Y dĂ©pend du paquet B version Z.T au minimum et U.V au maximum ». Il est chargĂ© de dĂ©terminer un ensemble de versions compatibles, si possible rĂ©centes. Cela paraĂźt simple, et pourtant, le problĂšme de la rĂ©solution de dĂ©pendances est NP-complet (c’est facile Ă  dĂ©montrer), ce qui signifie que, sauf Ă  prouver fausse l'[hypothĂšse du temps exponentiel](https://en.wikipedia.org/wiki/Exponential_time_hypothesis) (et si vous le faites, vous deviendrez cĂ©lĂšbre et couronnĂ© de gloire et du prix Turing), il n’existe pas d’algorithme pour le rĂ©soudre qui ait une complexitĂ© meilleure qu’exponentielle. Les algorithmes utilisĂ©s en pratique se fondent soit sur des heuristiques, soit sur une traduction en problĂšme SAT et appel d’un SAT-solveur. Le bon rĂ©solveur est celui qui rĂ©ussira Ă  rĂ©soudre efficacement les cas rencontrĂ©s en pratique. Pour revenir Ă  Python, il y a aujourd’hui trois rĂ©solveurs de dĂ©pendances principaux pour les paquets Python. Le premier est celui de pip, qui est implĂ©mentĂ© dans [resolvelib](https://github.com/sarugaku/resolvelib). Il utilise des heuristiques relativement simples. Historiquement, il s’est construit sur une contrainte forte : jusqu’à rĂ©cemment ([PEP 658](https://peps.python.org/658)), il n’y avait aucun moyen sur PyPI de tĂ©lĂ©charger seulement les mĂ©tadonnĂ©es d’un paquet sans tĂ©lĂ©charger le paquet entier. Donc, il n’était pas possible d’obtenir tout le graphe de dĂ©pendances entier avant de commencer la rĂ©solution, car cela aurait nĂ©cessitĂ© de tĂ©lĂ©charger le code entier de *toutes* les versions de chaque dĂ©pendance. Or, il n’y a aucun solveur SAT existant (Ă  ma connaissance) qui permette de modifier incrĂ©mentalement le problĂšme. Par consĂ©quent, pip Ă©tait de toute façon forcĂ© d’adopter une stratĂ©gie ad-hoc. La contrainte a Ă©tĂ© levĂ©e, mais l’algorithme est restĂ©. Le deuxiĂšme rĂ©solveur est celui de Conda. (En fait, le rĂ©solveur est en train de changer, mais l’ancien et le nouveau sont similaires sur le principe.) Contrairement Ă  pip, Conda tĂ©lĂ©charge Ă  l’avance un fichier qui donne les dĂ©pendances de chaque version de chaque paquet, ce qui lui permet de traduire le problĂšme de rĂ©solution entier en problĂšme SAT et d’appliquer un solveur SAT. Enfin, le troisiĂšme rĂ©solveur fait partie de Poetry. Si j’ai bien compris [ceci](https://github.com/pypa/pip/issues/7406), il utilise l’algorithme PubGrub, qui ne traduit pas le problĂšme en SAT, mais le rĂ©sout plutĂŽt avec une mĂ©thode inspirĂ©e de certains solveurs SAT. En pratique, dans mon expĂ©rience, le solveur de pip se montre rapide la plupart du temps (sauf dans les cas vraiment complexes avec beaucoup de dĂ©pendances et de contraintes). Toujours dans mon expĂ©rience, la rĂ©solution de dĂ©pendances dans Conda est en revanche franchement lente. À sa dĂ©charge, je soupçonne que le rĂ©solveur lui-mĂȘme n’est pas spĂ©cialement lent (voire, plus rapide que celui de pip ? je ne sais pas), mais comme Conda a pour principe de ne prendre quasiment rien dans le systĂšme, en ayant des paquets comme `wget`, `freetype`, `libxcb`, `pcre2`, etc. etc. etc., certains paquets ont un nombre absolument effrayant de dĂ©pendances. Par exemple, il y a quelque temps, j’ai eu Ă  demander Ă  `conda-lock` un environnement satisfaisant les contraintes suivantes : ``` - pyqt=5.15.9 - sip=6.7.11 - pyqt-builder=1.15.2 - cmake=3.26.4 - openjpeg=2.5.0 - jpeg=9e - compilers=1.6.0 - boost-cpp - setuptools=68.0.0 - wheel ``` Sur mon ordinateur, il faut environ 7 minutes pour que Conda calcule l’environnement rĂ©sultant — j’insiste sur le fait que rien n’est installĂ©, ce temps est passĂ© uniquement dans le rĂ©solveur de dĂ©pendances. Le lock file créé contient environ 250 dĂ©pendances (!). À titre illustratif : « Conda has gotten better by taking more shortcuts and guessing things (I haven't had a 25+ hour solve in a while) » — [Henry Schreiner](https://iscinumpy.dev/post/bound-version-constraints/#solver) Quant au rĂ©solveur de Poetry, mĂȘme si je n’ai jamais utilisĂ© sĂ©rieusement Poetry, je crois savoir que sa lenteur est l’une des objections les plus frĂ©quentes Ă  cet outil. Voir par exemple [ce bug](https://github.com/python-poetry/poetry/issues/2094) avec 335 👍. (Je trouve aussi rĂ©vĂ©lateur que sur les premiers rĂ©sultats renvoyĂ©s par une recherche Google de « poetry dependency resolver », une moitiĂ© environ se plaigne de la lenteur du rĂ©solveur.) D’un autre cĂŽtĂ©, le solveur de Poetry n’est appelĂ© que lorsque le lock file est mis Ă  jour, donc beaucoup moins souvent que celui de pip ou mĂȘme Conda. Il y a un vrai compromis Ă  faire : le rĂ©solveur de Poetry se veut plus prĂ©cis (il est censĂ© trouver plus souvent une solution avec des versions rĂ©centes), mais en contrepartie, la mise Ă  jour du lock file peut prendre, apparemment, une dizaine de minutes dans certains cas. # Conclusion et avis personnels Je termine en donnant mes choix trĂšs personnels et partiellement **subjectifs**, avec lesquels tout le monde ne sera pas forcĂ©ment d’accord. D’abord, il faut une maniĂšre d’installer des outils en ligne de commande distribuĂ©s sous forme de paquets Python. Il est sage de donner Ă  chacun son environnement virtuel pour Ă©viter que leurs dĂ©pendances n’entrent en conflit, ce qui peut arriver trĂšs vite. Pour cela, on a essentiellement le choix entre pipx, ou crĂ©er Ă  la main un environnement virtuel Ă  chaque fois. Sans hĂ©siter, je choisis pipx. (Il y a un problĂšme de *boostrap* parce que pipx est lui-mĂȘme un outil du mĂȘme genre. La solution consiste Ă  l’installer avec un paquet systĂšme, que la plupart des distributions fournissent, normalement sous le nom `python3-pipx`.) Ensuite, pour travailler sur un projet, on a le choix entre utiliser build et twine Ă  la main pour gĂ©nĂ©rer la sdist et le wheel et les distribuer sur PyPI, ou bien utiliser un outil plus unifiĂ©, soit Flit, soit Poetry, soit PDM, soit Hatch. Dans le premier cas, on peut utiliser n’importe quel build backend, dans le deuxiĂšme, on est potentiellement restreint au build backend associĂ© Ă  l’outil unifiĂ© (c’est le cas avec Flit et Poetry, mais pas avec PDM, et plus avec Hatch depuis trĂšs rĂ©cemment). Parlons d’abord du build backend. À vrai dire, lorsque les builds backends ont Ă©tĂ© introduits (par la PEP 517, voir dĂ©pĂȘche prĂ©cĂ©dente), la motivation Ă©tait largement de permettre l’émergence d’alternatives Ă  setuptools au vu du triste Ă©tat de setuptools. L’objectif est atteint, puisqu’il y a dĂ©sormais des alternatives mille fois meilleures. L’ennui, c’est qu’il y a aussi un peu trop de choix. Donc, comparons. D’abord, il y a setuptools. Sa configuration est franchement compliquĂ©e, par exemple je ne comprends pas prĂ©cisĂ©ment les douze mille [options de configuration](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html) qui contrĂŽlent les modules qui sont inclus dans les wheels. De plus, setuptools est vraiment excessivement verbeux. Dans le dĂ©pĂŽt de Pygments, un module dont je suis mainteneur, `python -m build | wc -l` comptait 4190 lignes de log avec setuptools, Ă  comparer Ă  10 lignes depuis que nous sommes passĂ©s Ă  hatchling. Mais surtout, le problĂšme avec setuptools, c’est qu’il y a mille et une fonctionnalitĂ©s mutantes dont on ne sait pas trĂšs bien si elles sont obsolĂštes, et la documentation est, pour moi, tout simplement incomprĂ©hensible. Entendons-nous bien : j’ai beaucoup de respect pour les gens qui maintiennent setuptools, c’est absolument essentiel vu le nombre de paquets qui utilisent setuptools parce que c’était historiquement le seul outil, mais aujourd’hui, peu contestent que setuptools est moins bon qu’à peu prĂšs n’importe quel build backend dans la concurrence, et on ne peut pas le simplifier, justement Ă  cause de toute la compatibilitĂ© Ă  garder. Alors... Flit ? Pour les dĂ©butants, ce n’est pas mal. Mais la force de Flit, son inflexibilitĂ©, est aussi son dĂ©faut. Exemple [ici](https://linuxfr.org/news/l-installation-et-la-distribution-de-paquets-python-1-4#comment-1940663) et [lĂ ](https://lists.sr.ht/~lioploum/offpunk-devel/%3C4bf5c83950eb5a7f81007b1c6cea15db3e7c0d60.camel%40abou-samra.fr%3E) oĂč, suite Ă  un commentaire de Ploum sur la dĂ©pĂȘche prĂ©cĂ©dente, je l’ai aidĂ© Ă  rĂ©soudre son problĂšme de packaging, dont la racine Ă©tait que Flit ne permet tout simplement pas d’avoir plusieurs modules Python dans un mĂȘme paquet. Alors... Poetry-core ? PDM-backend ? Hatchling ? Les diffĂ©rences sont moins marquĂ©es, donc parlons un peu des outils unifiĂ©s qui leur sont associĂ©s. D’abord, que penser de Poetry ? Je prĂ©cise que ne l’ai jamais utilisĂ© moi-mĂȘme sur un vrai projet. À ce que j’en ai entendu, la plupart de ceux qui l’utilisent l’apprĂ©cient et le trouvent plutĂŽt intuitif. Par ailleurs, comme dĂ©crit plus haut, il a son propre rĂ©solveur de dĂ©pendances, et celui-ci est particuliĂšrement lent au point que la gĂ©nĂ©ration du lock file peut prendre du temps. Soit. Mais je suis un peu sceptique Ă  cause de points plus fondamentaux, notamment le fait que les dĂ©pendances de votre gĂ©nĂ©rateur de documentation contraignent celles de votre projet, ce que je trouve assez idiot. Je recommande aussi ces deux posts trĂšs dĂ©taillĂ©s sur le blog d’Henry Schreiner : [Should You Use Upper Bound Version Constraints?](https://iscinumpy.dev/post/bound-version-constraints) et [Poetry versions](https://iscinumpy.dev/post/poetry-versions). Pour faire court, Poetry incite fortement Ă  mettre des contraintes de version maximum (comme `jinja2 < 3`), ce qui est problĂ©matique quand les utilisateurs de Poetry se mettent Ă  en abuser sans s’en rendre compte. Et il a aussi des opinions assez spĂ©ciales sur la rĂ©solution de dĂ©pendances, par exemple il vous force Ă  mettre une contrainte `< 4` sur Python lui-mĂȘme dĂšs qu’une de vos dĂ©pendances le fait, alors que tout projet Poetry le fait par dĂ©faut. J’ajoute le fait qu’on ne peut pas utiliser un autre build backend avec Poetry que Poetry-core. En corollaire, on ne peut pas utiliser Poetry sur un projet si tout le projet n’utilise pas Poetry, ce qui implique de changer des choses pour tous les contributeurs (alors que PDM et Hatch peuvent fonctionner avec un build backend diffĂ©rent). C’est pour moi un gros point noir. Alors... PDM ? HonnĂȘtement, je n’en ai pas assez d’expĂ©rience pour juger vraiment. Je sais qu’il corrige la plupart des dĂ©fauts de Poetry, mais garde le « dĂ©faut du gĂ©nĂ©rateur de documentation ». Alors... Hatch ? C’est celui que j’utilise depuis quelques mois, et jusqu’ici, j’en suis plutĂŽt satisfait. C’est un peu dommage qu’il n’ait pas encore les lock files, mais je n’en ai pas besoin pour mes projets. Je n’utilise pas pyenv. DĂ©jĂ  avant Hatch, je trouvais qu’il reprĂ©sentait trop de travail Ă  configurer par rapport au service rendu, que je peux facilement faire Ă  la main avec `./configure && make` dans le dĂ©pĂŽt de Python. Et depuis que j’utilise Hatch, il le fait pour moi, sans avoir Ă  compiler Python. De mĂȘme, je n’utilise plus tox, je fais la mĂȘme chose avec Hatch (avec la nuance que si j’ai dĂ©jĂ  tannĂ© mes co-mainteneurs pour remplacer `make` par tox, j’hĂ©site Ă  re-changer pour Hatch...). J’ai fini par me mĂ©fier du format INI, qui est piĂ©geux (c’est subjectif) et mal spĂ©cifiĂ© (il y en a mille variantes incompatibles). Donc, en rĂ©sumĂ©, j’utilise seulement deux outils, qui sont pipx, et Hatch. (Et [j’espĂšre](https://github.com/pypa/hatch/issues/1105) n’avoir bientĂŽt plus besoin de pipx non plus.) Mais si vous avez besoin de lock files, vous pourriez remplacer Hatch par PDM. Je termine la comparaison avec un mot sur Conda. À mon avis, entre Ă©cosystĂšme Conda et Ă©cosystĂšme PyPI, le choix est surtout pragmatique. Qu’on le veuille ou non, l’écosystĂšme Python s’est historiquement construit autour de PyPI, qui reste prĂ©dominant. Malheureusement, la rĂ©ponse de Conda Ă  « Comment installer dans un environnement Conda un paquet PyPI qui n’est pas disponible au format Conda ? » est « Utilisez pip, mais Ă  vos risques et pĂ©rils, ce n’est pas supportĂ© », ce qui n’est pas fantastique lorsque l’on a besoin d’un tel paquet (cela arrive trĂšs vite). D’un autre cĂŽtĂ©, pour le calcul scientifique, Conda peut ĂȘtre plus adaptĂ©, et il y a des pans entiers de ce domaine, comme [le gĂ©ospatial](https://pypackaging-native.github.io/key-issues/native-dependencies/geospatial_stack/), qui fonctionnent avec Conda et ne fonctionnent pas du tout avec PyPI. J’espĂšre que ce trĂšs long panorama Ă©tait instructif et aidait Ă  y voir plus clair dans l’écosystĂšme. Pour la troisiĂšme dĂ©pĂȘche, je vous proposerai un focus sur la distribution des modules d’extension Ă©crits dans des langages compilĂ©s, sur ses difficultĂ©s inextricables, et sur les raisons pour lesquelles le modĂšle de Conda en fait une meilleure plateforme que PyPI pour ces extensions.

AltStyle ă«ă‚ˆăŁăŠć€‰æ›ă•ă‚ŒăŸăƒšăƒŒă‚ž (->ă‚ȘăƒȘă‚žăƒŠăƒ«) /