« Distribuer un projet en python » : différence entre les versions

Contenu supprimé Contenu ajouté
Aucun résumé des modifications
Ligne 1 :
La plus simple manière de partager un projet écrit en python est de le paqueter dans une archive zip, et de laisser chacun extraire et installer votre projet là où c'est nécessaire. Ce procédé fastidieux a été automatisé par des librairies tierces, intégrées dans la librairies standard de python dès Python 1.6. Parmi tous les outils qui sont apparus pas la suite on peut citer distutils, easy_install, pkg_resources, setuptools, pip, buildout... La manière de paqueter son projet et de le distribuer ne cesse d'évoluer, ce livre essaieraessaie de vous aider à vous repérer dans cet univers. Une solide connaissance du langage python est nécessaire avant de continuer, vous pouvez lire ce livre pour cela : [[Programmation Python]].
 
== Module tiers ==
 
Dans le vocabulaire python, le projet que vous chercher à paqueter et distribuer est appelé un « '''module tiers''' » (''third-party module''). Quel que soit l'outil que vous utilisez (easyinstall, pip...), l'endroit où vos fichiers s'installeront doit être trouvable par le module <code>site</code> de python., Voussitué pouvez lire ce fichier vous même (fichierà <code>Lib/site.py</code>), il est assez court, pour comprendre comment il fonctionne. Pour simplifier, ce fichier va mettre à jour la propriété <code>sys.path</code> avec le dossier <code>site-packages</code> pour que vous puissiez simplement <code>import ''module tiers''</code> dans vos projets, et que ceux-ci soient centralisés dans un unique dossier utilisable par tous vos projets. C'est très utile pour les librairies lourdes. Pour savoir comment site.py va altérer votre environnement, tapez simplement <code>python -m site</code>. Pour empêcher python d'exécuter ce script, lancez-le avec l'option -S, c'est à dire <code>python -S</code>, cela vous laisseralaisse loisir de modifier l'environnement d'exécution puis d'importer vous même ce module via <code>import site</code>, sachant qu'il va faire beaucoup de lectures de dossiers et peut ralentir le démarrage de votre script python.
 
Pour résumer après avoir exécuté site.py, on peut simplement importer tout module tiers dans ses projets, sans avoir besoin d'avoir ces dits modules dans la même arborescence que le projet en question, mais au lieu de cela dans un environnement virtuel, dans un dossier utilisateur (/home sur unixUnix, AppData sur windows...Windows…), ou dans un dossier de python lui même.
Non seulement d'inclure les site-packages, site.py essaiera de détecter si l'instance de python courante s'exécute dans un « '''environnement virtuel''' » (dit aussi venv, pour ''virtual environment''). Il détermine cela si il existe un fichier <code>pyenv.cfg</code> dans le répertoire immédiatement au dessus de l'exécutable python en cours. Si c'est le cas, le fichier site-packages de l'environnement virtuel est placé tout en haut de la liste sys.path afin que les imports soient en priorité cherchés dans cet environnement, et utiliser le site-packages de l'utilisateur, puis le site-packages de l'installation python en deuxième recours, à moins que le fichier pyenv.cfg contienne la ligne <code>include-system-site-packages = false</code>, auquel cas tout import de module ne peut réussir que si il existe bien dans l'environnement virtuel. Cette ligne est indispensable si on veut s'assurer que son environnement de travail nécessaire au fonctionnement de son module soit reproductible au mieux par les tierces personnes avec qui vous partagerez les ''plans'' de votre environnement virtuel.
 
On peut aussi noter que le module <code>site.py</code> définit quelques extras, comme les fonctions <code>exit</code> et <code>quit</code> comme alias aux mêmes fonctions du module <code>sys</code>, définit le texte qui s'affiche avant le prompt, les informations de copyright, etc.
 
Pour résumer après avoir exécuté site.py, on peut simplement importer tout module tiers dans ses projets, sans avoir besoin d'avoir ces dits modules dans la même arborescence que le projet en question, mais au lieu de cela dans un environnement virtuel, dans un dossier utilisateur (/home sur unix, AppData sur windows...), ou dans un dossier de python lui même.
 
== Environnement virtuel ==
 
Non seulement d'inclure les site-packages, site.py essaieraessaie de détecter si l'instance de python courante s'exécute dans un « '''environnement virtuel''' » (dit aussi venv, pour ''virtual environment''). Il détermine cela si il existe un fichier <code>pyenv.cfg</code> dans le répertoire immédiatement au dessus de l'exécutable python en cours. Si c'est le cas, le fichier site-packages de l'environnement virtuel est placé tout en haut de la liste <code>sys.path</code> afin que les imports soient en priorité cherchés dans cet environnement, et utiliser le site-packages de l'utilisateur, puis le site-packages de l'installation python en deuxième recours, à moins que le fichier <code>pyenv.cfg</code> contienne la ligne <code>include-system-site-packages = false</code>, auquel cas tout import de module ne peut réussir que si il existe bien dans l'environnement virtuel. Cette ligne est indispensable si on veut s'assurer que son environnement de travail nécessaire au fonctionnement de son module soit reproductible au mieux par les tierces personnes avec qui vous partagerez les ''plans'' de votre environnement virtuel.
Nous avons vu que le système de site-packages est compatible avec les environnement virtuels par le biais du script site.py, et qu'il est fortement conseillé que cet environnement contienne la totalité de ce qui est nécessaire pour que votre module soit exécutable par des tiers une fois ''pip''é, ''easy-install''é, etc. Pour s'assurer de cela la ligne <code>include-system-site-packages = false</code> dans le fichier pyenv.cfg peut éviter des maladresses et laisser à croire que votre module fonctionne, mais qu'il dépende d'un environnement extérieur au module.
 
La manière dont vous décrivez ces environnement est propre à chaque outil, nous décrirons plus loin celui de pip.
La manière dont vous décrivez ces environnement est propre à chaque outil, nous allons décrire celui de pip, qui est l'outil recommandé par python et désormais inclus par défaut dans les distributions python, si vous ne l'avez pas, vous pour l'obtenir avec la commande <code>python get-pip.py</code>, vous pouvez soit taper taper la commande <code>pip</code> directement dans une invite de commande, soit, ce qui est préférable le lancer via python <code>python -m pip</code>. Si vous lancez pip sans argument vous verrez une liste de commandes qu'il peut exécuter, celles qui nous intéressent sont <code>install</code> et <code>freeze</code>. <code>freeze</code> affiche la liste des modules tiers dans le site-packages en cours, dans un format que la commande <code>pip install</code> peut comprendre. La manière la plus pratique pour conserver une trace de ces noms de modules est la syntaxe suivant <code>python -m pip freeze > '''requirements.txt'''</code>. Ce nom de fichier est normalisé et il est recommandé de toujours l'utiliser. Un utilisateur tiers pourra alors imiter votre environnement simplement en tapant la commande <code>python -m pip install -r requirements.txt</code>, de préférence en ayant créé et activé un environnement virtuel au préalable, dans le même souci d'avoir un environnement reproductible, et de ne pas polluer la commande <code>pip freeze</code> si il souhaite lui aussi participer à votre module. Pour que cela soit possible il faut que le fichier requirements.txt – qui n'a de sens que pour pip – soit inclus avec votre module, on parlera alors plutôt de « '''paquetage''' », en anglais ''package'', car notre projet contient autre chose qu'un simple script en python.
 
== Le paquetage ==
Ligne 21 ⟶ 17 :
Pour utiliser votre module, il faut donc l'inclure dans un paquetage qui contient en outre les noms d'autre modules tiers que votre module utilise dans un fichier texte, ce fichier peut être utilisé pour établir un arbre de dépendances, et il est déconseillé de le modifier à la main mais de laisser <code>pip freeze</code> s'en occuper.
 
Maintenant comment construire ce paquetage ? Encore une fois le faire à la main peut être fastidieux, surtout si votre module contient beaucoup de fichiers, des fichiers non en python, des fichiers que vous ne souhaitez pas partager, etc. Python contient un module spécialement dédié à cette fin, nommé « '''distutils''' ». Si vous êtes curieux, vous le trouverez dans Lib/distutils, et sa documentation est à l'adresse install/index.html de la documentation officielle. On voit que la finalité première de ce module est de produire des paquetages dans le sens de linux, c'est à dire des fichier .rpm. En ce qui nous intéresse, dans un premier temps, c'est de créer une archive .zip, .tar.gz, que <code>pip install</code> est capable de lire (si vous tapez <code>python -m pip install --help</code>, vous verrez qu'en outre de lire un fichier de requirements il sait lire une archive zip, en sus de savoir chercher un module sur pipypypi, d'un dépôt git où d'un simple chemin vers une arborescence locale). Ce que pip va faire dans tous les cas, plutôt que de recopier tout le contenu du répertoire, déport distant, archive, etc. est d'exécuter un fichier dont le nom est normalisé : <code>'''setup.py'''</code> avec l'argument <code>install</code>, c'est ce fichier qui va donc chapeauter ce qui seraest copié ou non dans le dossier <code>site-packages</code> via pip.
 
Ce fichier peut être utilisé par soi même de multiples manières : construire une archive, un paquetage linux, voire même un installeur windowsWindows (même si cela est déconseillé de nos jours et non plus officiellement supporté), mais également des outils tiers comme pip.
 
Dans sa forme la plus simple le fichier <code>setup.py</code> contient un import de la librairie setuptools, et un appel a la fonction <code>setup()</code> de celle-ci qui liralit le contenu de la ligne de commande et exécuteraexécute les taches nécessaires à la copie de fichiers, la compilation de modules, de paquetage dans une archive, etc.
 
# from distutils.core import setup # vieille façon
Ligne 45 ⟶ 41 :
== Distutils ==
 
L'arborescence du projet doit contenir un fichier setup.py, qui contient le code python nécessaire à l'installation de votre module. Cette installation, doit aboutir à la copie des fichiers nécessaires dans le répertoire site-packages le plus pertinent. Ce procédé se fait automatiquement si le fichier <code>setup.py</code> contient un appel à la fonction <code>setup()</code> de <code>distutils.core</code>. Cette fonction lit la ligne de commandescommande utilisée pour invoquer le script puis exécute la commande appelée. <code>python ./setup.py toto --arg1=2</code> appelle la commande toto avec l'argument bar<code>arg1</code> valant <code>1</code>. Un fichier <code>'''setup.cfg'''</code> peut contenir des valeurs d'arguments par défaut pour chaque commande. Si il est ainsi
# setup.cfg
[toto]
arg1=3
arg2=1
La commande <code>toto</code> s'exécuteraexécute avec <code>arg1=2</code> at <code>arg2=1</code>, la ligne de commande prévaut sur le fichier de configuration.
 
Ne reconnaissant pas la commande toto un message d'erreur s'affiche avec la liste des commandes acceptées. Leur code source peut se trouver dans <code>Libs/distutils/commands</code>. Si vous lisez ces fichiers, et notamment la méthode <code>run</code>, vous remarquerez que beaucoup de commandes en appellent d'autres. Par exemple <code>install</code> appelle <code>build</code>, <code>build</code> peut appellerappeler <code>build_ext</code>, <code>build_py</code>..., que chaque commande a des arguments différents qui peut altérer la course de l'installation, et des valeurs par défaut.
 
Les principales commandes sont <code>build</code>, <code>install</code>, <code>sdist</code>, <code>bdist</code> et <code>bdist_*</code>.
Ligne 58 ⟶ 54 :
La commande <code>build</code> copie les fichiers python dans un répertoire, par défaut <code>build/lib*</code>, compile les modules en C/C++ et les copie également, par simplicité lorsqu'on parle de librairie plus loin dans ce cours, c'est de ce dossier qu'on parlera. La commande <code>install</code> recopie tout ce qui se trouve dans le répertoire peuplé par <code>build</code> dans le dossier <code>site-packages</code>, la commande <code>sdist</code> produit une archive zip avec tout ce qu'il faut pour que la commande bdist réussisse. Sachant cela il apparait que la fonction install peut fonctionner à partir de la commande build, sdist et bdist, la commande bdist à partir de la commande <code>build</code> et <code>sdist</code>, mais les commandes <code>build</code> et <code>sdist</code> ne peuvent pas fonctionner à partir d'une archive faite par <code>bdist</code>. setup/build fonctionnent à partir d'un répertoire local, sdist/bdist servent à partager le répertoire local, bdist avec le minimum vital (fichiers pyc, pyd, ...), et sdist avec les tests, fichiers source (py, pyx, c, c++, ...).
 
Notre objectif premier était de produire un répertoire zip facilement installable avec pip, donc doit-on utiliser ici sdist ou bdist? Cela dépend du contenu de notre paquetage, si il ne contient que des fichiers en python, les deux commandes sont assez similaires, mais si ils contients du code en cython, ou des modules C/C++ natifs, alors dans le cas de sdist les clients de votre librairie doivent posséder un compilateur C (qui occupe un espace disque jusque 30 gigaoctetsgiga-octets) et les librairies python de dev, par défaut non installées. Si vous optez pour bdist, les modules binaires doivent être compatible avec chaque machine, c'est à dire qu'une distribution bdist faite en 64 bits ne fonctionnera pas sur une machine 32 bits, un module fait avec une librairie python 3.7 probablement ne fonctionnera pas dans un environnement 3.6 (même si l'usage de l'ABI stable de python peut mitiger cela<ref>https://docs.python.org/fr/3/c-api/stable.html</ref>), une librairie construite sous mac, windows ou linux ne fonctionnera pas sur les autres machines. Nous verrons cette problématique bien plus tard car elle nécessite une machinerie complexe hors de notre propos, mais dont le mot-clé est ''continous integration'' (CI), et nous concentrons sur sdist dans la suite du livre, et sur la forme de bdist la plus simple : celle faite par et pour soi.
 
=== <code>Build</code> ===
 
Qu'on utilise la commande <code>install</code>, <code>sdist</code> ou <code>bdist</code>, la commande <code>build</code> seraest exécutée dans tous les cas, pour qu'elle sache quoi mettre dans la librairie, on utilise l'argument <code>py_modules</code> et <code>package</code> de <code>setup()</code>. Ceux-ci ont d'abord été mutuellement exclusifs, avant qu'il devienne possible de les utiliser en même temps. <code>py_modules</code> accepte une liste de chaines de caracterescaractères, qui doivent correspondre à la manière dont les fichiers seront importés, par exemple un fichier <code>toto.py</code> seraest importé ainsi
setup(py_modules=['toto'])
Si vous avez un autre fichier dans un sous-répertoire <code>a > b</code>, celui-ci doit contenir un fichier <code>__init__.py</code>, et vous le déclarez ainsi
setup(py_modules=['toto', 'a.b.toto'])
Cela inclurainclut automatiquement le fichier __init__, pas besoin d'écrire <code>setup(py_modules=['toto', 'a.b.toto', 'a.b.__init__])</code>. Ces noms d'import doivent obligatoirement référencer des fichiers qui existent et se terminent par .py, de préférence se trouvent dans un répertoire contenant un fichier <code>__init__</code> (sinon un warning s'affiche). <code>py_modules</code> n'est pas compatible avec des fichiers non python (readme, ...) ou compressés (pyo, pyc) ou des paquetages PEP420.
 
L'option <code>packages</code> est plus complexe à écrire, mais permet d'inclure tout type de fichier et d'utiliser une syntaxe de <code>glob</code><ref name="">https://docs.python.org/3/library/glob.html#glob.glob</ref>ing pour inclure rapidement des répertoires entiers de fichiers dans la librairie. Si on veut inclure les mêmes fichiers qu'avec <code>py_modules</code>, mais en plus un README, et des fichier en python compilés et de typage dans le répertoire a > b, on écrit
Ligne 100 ⟶ 96 :
)
Un fichier <code>'''MANIFEST.in'''</code> peut indiquer des fichiers supplémentaires à inclure dans la distribution, mais si ces fichiers ne sont pas spécifiés dans <code>package_data</code>, ils ne seront pas dans les distributions binaire et laabsents commandede <code>install</code>la nefeuille lesde trouvera pasroute, le fonctionnement de <code>build</code> ignore en effet les fichiers de ''manifest'', qui ne servent que pour <code>sdist</code>, l'idée est que les extras comme la documentation peut être consultés dansvia l'archivela zipligne de commande (<code>help()</code>), et n'ont pas besoin d'être dans le répertoire exécutable de python. ''setuptools'' change cette interaction, et ''distutils'' lui même été changé à de nombreuses reprises, si bien que de nombreuses choses non concordante ont été écrits à ce sujet<ref>https://docs.python.org/fr/3/distutils/sourcedist.html#specifying-the-files-to-distribute</ref>.
 
=== <code>bdist</code> ===
 
Les fonctionnalités de <code>bdist</code> de <code>distutils</code> est largement obsolète, par défaut il produit un installeur qui copie les fichiers vers des chemins absolus, si <code>bdist</code> a été exécuté dans <code>/usr/local/python</code>, alors <code>install</code> va copier votre librairie vers ce même dossier pour les clients. Ce n'est pas compatible avec venv, ou tout systemesystème non unix. Autrement bdist peut aussi produire des paquatagespaquetages rpm, des installeurs windows de type .msi ou .exe qui sont eux aussi largement obsolètes. C'est avec <code>setuptools</code> et le format egg que la commande bdist peut être réellement utilisée.
 
Une archive bdist de base ne contient pas le script setup.py, de PKG-INFO, et ignore les MANIFEST et autres fichiers inclus par sdist, c'est en effet à l'outil final (apt-get, .msi, ...) que revient la tâche de faire l'installation. Il produit cependant un fichier d'extension *.egg-info qui contient les mêmes données que PKG-INFO.
Ligne 137 ⟶ 133 :
)
 
<code>find_namespace_packages()</code> sert à inclure les sous-paquetages PEP420.
 
== Pip ==
 
LaPip manièreest dontl'outil vousrecommandé décrivezpar cespython environnementpour estinstaller proprerapidement àun chaquepaquetage outil,en nouspython allonset décrireremplace celui''easy_install''. de pip, quiIl est l'outil recommandé par python et désormais inclus par défaut dans les distributions python, si vous ne l'avez pas, vous pour l'obtenir avec la commande <code>python get-pip.py</code>, vous pouvez soit taper taper la commande <code>pip</code> directement dans une invite de commande, soit, ce qui est préférable le lancer via python <code>python -m pip</code>. Si vous lancez pip sans argument vous verrez une liste de commandes qu'il peut exécuter, celles qui nous intéressent sont <code>install</code> et <code>freeze</code>. <code>freeze</code> affiche la liste des modules tiers dans le site-packages en cours, dans un format que la commande <code>pip install</code> peut comprendre. La manière la plus pratique pour conserver une trace de ces noms de modules est la syntaxe suivant <code>python -m pip freeze > '''requirements.txt'''</code>. Ce nom de fichier est normalisé et il est recommandé de toujours l'utiliser. Un utilisateur tiers pourra alors imiter votre environnement simplement en tapant la commande <code>python -m pip install -r requirements.txt</code>, de préférence en ayant créé et activé un environnement virtuel au préalable, dans le même souci d'avoir un environnement reproductible, et de ne pas polluer la commande <code>pip freeze</code> si il souhaite lui aussi participer à votre module. Pour que cela soit possible il faut que le fichier requirements.txt – qui n'a de sens que pour pip – soit inclus avec votre module, on parleraparle alors plutôt de « '''paquetage''' », en anglais ''package'', car notre projet contient autre chose qu'un simple script en python.
La commande <code>'''pip'''</code> sert à remplacer l'utilitaire <code>easy_install</code>. Il a contribué au format wheel (<code>.whl</code>) pour remplacer le format <code>egg</code> , il fonctionne de manière similaire sinon. Il permet de facilement établir un arbre de dépendances grâce à la commande <code>freeze</code>.
 
Pip a contribué au format wheel (<code>.whl</code>) pour remplacer le format <code>egg</code>.
 
== Pep517 ==
 
Dans les années 2010 des outils qui ne sont pas des extensions de distutils voient le jour et ont pour objectif de remplacer pip, on peut citer <code>bento</code> (obsolète), <code>flit</code> ou <code>poetry</code>. Flit est le seul outils qui permette la création de distributions binairement reproductibles<ref>https://reproducible-builds.org/</ref>, quand à poetry il permet d'une simple commande de créer un environnement virtuel, télécharger un ''wheel'' et de l'installer. Ces nouveaux outils en plus d'utiliser le format ''wheel'' savent lire le format <code>sdist</code> dirigé par MANIFEST.in. Ils n'assurent pas toutes les fonctionnalités rendues possibles par setuptools, notamment la commande <code>setup.py develop</code> (ou assimilé). Il est possible de déclarer son paquetage de plusieurs manières grâce à un fichier <code>'''pyproject.toml'''</code> de sorte qu'il est possible d'accomoderaccommoder plusieurs systèmes d'installation. On peut forcer pip à utiliser le nouveau ou l'ancien système avec l'option <code>--pep517</code> et <code>--no-pep517</code> selon qu'on veuille les fonctionnalités de setuptools ou bien celles des nouveaux systèmes, par exemple <code>pip install --editable --no-pep517</code>.
 
== Terminologie ==
* '''Projet''', '''paquetage''', '''distribution''' : dans le vocabulaire il s'agit d'un module tiers, on l'utilise dans ce livre dans le sens de linux, comme produit fini, archive installable qui contient éventuellement des sous paquetages en python (appellésappelés simplement paquetage pour python).
* '''Sous-paquetage''', '''sous-module''' : dans le vocabulaire python il s'agit d'un paquetage ou ''package''.
* '''Distribution source''' : Une archive contenant le nécessaire au développement, il s'agit de l'équivalent d'un git clone, contenu dans une archive. Les outils mentionnés dans le livre existaient avant que les systèmes de gestion décentralisés soient populaires.
Ligne 157 ⟶ 155 :
* '''Arbre de dépendances''' : schémas descriptif et abstrait de python qui permet à l'outillage de connaitre les dépendances de son projet et des les installer récursivement.
* '''''Manifest''''', '''feuille de route''' : fichier qui décrit les étapes à suivre de manière abstraite à python pour installer ou paqueter une distribution source, et permet ainsi sa désinstallation automatique et suivant les les étapes à l'envers.
 
== Ressources ==
(enlever les espaces)
* Ressources officielles
** packaging.python. org/ Guides et tutoriaux à jour
** www.pypa. io/en/latest/ Spécifications
** docs.python. org/fr/3/distributing/index.html Documentation
* Documentation historique de distutils
** docs.python. org/fr/3/distutils/index.html
** Anciennes : docs.python. org/release/1.6/inst/inst.html, docs.python. org/release/1.6/dist/dist.html
* Documentation de setuptools : setuptools.readthedocs. io/en/latest/index.html
* Documentation de pip : pip.pypa. io/en/stable/
 
== Références ==