28.4. "zipapp" --- Manage executable python zip archives
********************************************************

Nouveau dans la version 3.5.

**Code source :** Lib/zipapp.py

======================================================================

Ce module fournit des outils pour gérer la création de fichiers zip
contenant du code Python, qui peuvent être exécutés directement par
l'interpréteur Python.  Le module fournit à la fois une interface de
ligne de commande Interface en ligne de commande et une interface API
Python.


28.4.1. Exemple de base
=======================

L'exemple suivant montre comment l'interface de ligne de commande
Interface en ligne de commande peut être utilisée pour créer une
archive exécutable depuis un répertoire contenant du code Python.
Lors de l'exécution, l'archive exécutera la fonction "main" du module
"myapp" dans l'archive.

   $ python -m zipapp myapp -m "myapp:main"
   $ python myapp.pyz
   <output from myapp>


28.4.2. Interface en ligne de commande
======================================

Lorsqu'il est appelé en tant que programme à partir de la ligne de
commande, la syntaxe suivante est utilisée :

   $ python -m zipapp source [options]

Si *source* est un répertoire, une archive est créée à partir du
contenu de *source*.  Si *source* est un fichier, ce doit être une
archive et il est copié dans l'archive cible (ou le contenu de sa
ligne *shebang* est affiché si l'option "--info" est indiquée).

Les options suivantes sont disponibles :

-o <output>, --output=<output>

   Écrit la sortie dans un fichier nommé *output*.  Si cette option
   n'est pas spécifiée, le nom du fichier de sortie sera le même que
   celui de l'entrée *source*, avec l'extension ".pyz".  Si un nom de
   fichier explicite est donné, il est utilisé tel quel (une extension
   ".pyz" doit donc être incluse si nécessaire).

   Un nom de fichier de sortie doit être spécifié si la *source* est
   une archive (et, dans ce cas, la *sortie* ne doit pas être la même
   que la *source*).

-p <interpreter>, --python=<interpreter>

   Ajoute une ligne "#!" à l'archive en spécifiant *interpreter* comme
   commande à exécuter.  Aussi, sur un système POSIX, cela rend
   l'archive exécutable.  Le comportement par défaut est de ne pas
   écrire la ligne "#!" et de ne pas rendre le fichier exécutable.

-m <mainfn>, --main=<mainfn>

   Écrit un fichier "__main__.py" dans l'archive qui exécute *mainfn*.
   L'argument *mainfn* est de la forme « *pkg.mod:fn* », où «
   *pkg.mod* » est un paquet/module dans l'archive, et « *fn* » est un
   appelable dans le module donné. Le fichier "__main__.py" réalise
   cet appel.

   "--main" ne peut pas être spécifié lors de la copie d'une archive.

--info

   Affiche l'interpréteur intégré dans l'archive, à des fins de
   diagnostic.  Dans ce cas, toutes les autres options sont ignorées
   et SOURCE doit être une archive et non un répertoire.

-h, --help

   Affiche un court message d'aide et quitte.


28.4.3. API Python
==================

Ce module définit deux fonctions utilitaires :

zipapp.create_archive(source, target=None, interpreter=None, main=None)

   Crée une archive d'application à partir de *source*.  La source
   peut être de natures suivantes :

   * The name of a directory, or a "pathlib.Path" object referring to
     a directory, in which case a new application archive will be
     created from the content of that directory.

   * The name of an existing application archive file, or a
     "pathlib.Path" object referring to such a file, in which case the
     file is copied to the target (modifying it to reflect the value
     given for the *interpreter* argument).  The file name should
     include the ".pyz" extension, if required.

   * Un objet fichier ouvert pour la lecture en mode binaire.  Le
     contenu du fichier doit être une archive d'application et Python
     suppose que l'objet fichier est positionné au début de l'archive.

   L'argument *target* détermine où l'archive résultante sera écrite :

   * If it is the name of a file, or a "pathlb.Path" object, the
     archive will be written to that file.

   * S'il s'agit d'un objet fichier ouvert, l'archive sera écrite dans
     cet objet fichier, qui doit être ouvert pour l'écriture en mode
     octets.

   * Si la cible est omise (ou "None"), la source doit être un
     répertoire et la cible sera un fichier portant le même nom que la
     source, avec une extension ".pyz" ajoutée.

   L'argument *interpreter* spécifie le nom de l'interpréteur Python
   avec lequel l'archive sera exécutée.  Il est écrit dans une ligne
   *shebang* au début de l'archive.  Sur un système POSIX, cela est
   interprété par le système d'exploitation et, sur Windows, il sera
   géré par le lanceur Python.  L'omission de l'*interpreter*
   n'entraîne pas l'écriture d'une ligne *shebang*.  Si un
   interpréteur est spécifié et que la cible est un nom de fichier, le
   bit exécutable du fichier cible sera mis à 1.

   L'argument *main* spécifie le nom d'un appelable, utilisé comme
   programme principal pour l'archive.  Il ne peut être spécifié que
   si la source est un répertoire et si la source ne contient pas déjà
   un fichier "__main__.py".  L'argument *main* doit prendre la forme
   "pkg.module:callable" et l'archive sera exécutée en important
   "pkg.module" et en exécutant l'appelable donné sans argument.
   Omettre *main* est une erreur si la source est un répertoire et ne
   contient pas un fichier "__main__.py" car, dans ce cas, l'archive
   résultante ne serait pas exécutable.

   Si un objet fichier est spécifié pour *source* ou *target*, il est
   de la responsabilité de l'appelant de le fermer après avoir appelé
   "create_archive".

   Lors de la copie d'une archive existante, les objets fichier
   fournis n'ont besoin que des méthodes "read" et "readline" ou
   "write".  Lors de la création d'une archive à partir d'un
   répertoire, si la cible est un objet fichier, elle sera passée à la
   classe "zipfile.ZipFile" et devra fournir les méthodes nécessaires
   à cette classe.

zipapp.get_interpreter(archive)

   Renvoie l'interpréteur spécifié dans la ligne "#!" au début de
   l'archive.  S'il n'y a pas de ligne "#!", renvoie "None".
   L'argument *archive* peut être un nom de fichier ou un objet de
   type fichier ouvert à la lecture en mode binaire. Python suppose
   qu'il est au début de l'archive.


28.4.4. Exemples
================

Regroupe le contenu d'un répertoire dans une archive, puis l'exécute.

   $ python -m zipapp myapp
   $ python myapp.pyz
   <output from myapp>

The same can be done using the "create_archive()" functon:

   >>> import zipapp
   >>> zipapp.create_archive('myapp.pyz', 'myapp')

Pour rendre l'application directement exécutable sur un système POSIX,
spécifiez un interpréteur à utiliser.

   $ python -m zipapp myapp -p "/usr/bin/env python"
   $ ./myapp.pyz
   <output from myapp>

Pour remplacer la ligne *shebang* sur une archive existante, créez une
archive modifiée en utilisant la fonction "create_archive()" :

   >>> import zipapp
   >>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')

Pour mettre à jour le fichier sans créer de copie locale, effectuez le
remplacement en mémoire à l'aide d'un objet "BytesIO", puis écrasez la
source par la suite.  Notez qu'il y a un risque lors de l'écrasement
d'un fichier local qu'une erreur entraîne la perte du fichier
original.  Ce code ne protège pas contre de telles erreurs, assurez-
vous de prendre les mesures nécessaires en production.  De plus, cette
méthode ne fonctionnera que si l'archive tient en mémoire :

   >>> import zipapp
   >>> import io
   >>> temp = io.BytesIO()
   >>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
   >>> with open('myapp.pyz', 'wb') as f:
   >>>     f.write(temp.getvalue())


28.4.5. Spécification de l'interprète
=====================================

Notez que si vous spécifiez un interpréteur et que vous distribuez
ensuite votre archive d'application, vous devez vous assurer que
l'interpréteur utilisé est portable.  Le lanceur Python pour Windows
gère la plupart des formes courantes de la ligne POSIX "#!", mais il y
a d'autres problèmes à considérer :

* Si vous utilisez "/usr/bin/env python" (ou d'autres formes de la
  commande *python*, comme "/usr/bin/python"), vous devez considérer
  que vos utilisateurs peuvent avoir Python 2 ou Python 3 par défaut,
  et écrire votre code pour fonctionner dans les deux versions.

* Si vous utilisez une version explicite, par exemple "/usr/bin/env
  python3" votre application ne fonctionnera pas pour les utilisateurs
  qui n'ont pas cette version.  (C'est peut-être ce que vous voulez si
  vous n'avez pas rendu votre code compatible Python 2).

* Il n'y a aucun moyen de dire « python X.Y ou supérieur » donc faites
  attention si vous utilisez une version exacte comme "/usr/bin/env
  python3.4" car vous devrez changer votre ligne *shebang* pour les
  utilisateurs de Python 3.5, par exemple.

Normalement, vous devriez utiliser un "/usr/bin/env python2" ou
"/usr/bin/env python3", selon que votre code soit écrit pour Python 2
ou 3.


28.4.6. Création d'applications autonomes avec *zipapp*
=======================================================

En utilisant le module "zipapp", il est possible de créer  des
programmes Python qui peuvent être distribués à des utilisateurs
finaux dont le seul pré-requis est d'avoir la bonne version de Python
installée sur leur ordinateur. Pour y arriver, la clé est de regrouper
toutes les dépendances de l'application dans l'archive avec le code
source de l'application.

Les étapes pour créer une archive autonome sont les suivantes :

1. Créez votre application dans un répertoire comme d'habitude, de
   manière à avoir un répertoire "myapp" contenant un fichier
   "__main__.py" et tout le code de l'application correspondante.

2. Installez toutes les dépendances de votre application dans le
   répertoire "myapp" en utilisant *pip* :

      $ python -m pip install -r requirements.txt --target myapp

   (ceci suppose que vous ayez vos dépendances de projet dans un
   fichier "requirements.txt" — sinon vous pouvez simplement lister
   les dépendances manuellement sur la ligne de commande *pip*).

3. Si nécessaire, supprimez les répertoires ".dist-info" créés par
   *pip* dans le répertoire "myapp". Ceux-ci contiennent des
   métadonnées pour *pip* afin de  gérer les paquets et, comme vous
   n'utiliserez plus *pip*, ils ne sont pas nécessaires (c'est sans
   conséquence si vous les laissez).

4. Regroupez le tout à l'aide de :

      $ python -m zipapp -p "interpreter" myapp

Cela produira un exécutable autonome qui peut être exécuté sur
n'importe quelle machine avec l'interpréteur approprié disponible.
Voir Spécification de l'interprète pour plus de détails. Il peut être
envoyé aux utilisateurs sous la forme d'un seul fichier.

Sous Unix, le fichier "myapp.pyz" est exécutable tel quel.  Vous
pouvez renommer le fichier pour supprimer l'extension ".pyz" si vous
préférez un nom de commande « simple ».  Sous Windows, le fichier
"myapp.pyz[w]" est exécutable en vertu du fait que l'interpréteur
Python est associé aux extensions de fichier ".pyz" et ".pyzw" une
fois installé.


28.4.6.1. Création d'un exécutable Windows
------------------------------------------

Sous Windows, l'association de Python à l'extension ".pyz" est
facultative et, de plus, il y a certains mécanismes qui ne
reconnaissent pas les extensions enregistrées de manière «
transparente » (l'exemple le plus simple est que
"subprocess.run(['myapp'])" ne trouvera pas votre application — vous
devez explicitement spécifier l'extension).

Sous Windows, il est donc souvent préférable de créer un exécutable à
partir du *zipapp*.  C'est relativement facile bien que cela nécessite
un compilateur C.  L'astuce repose sur le fait que les fichiers zip
peuvent avoir des données arbitraires au début et les fichiers *exe*
de Windows peuvent avoir des données arbitraires à la fin.  Ainsi, en
créant un lanceur approprié et en rajoutant le fichier ".pyz" à sa
fin, vous obtenez un fichier unique qui exécute votre application.

Un lanceur approprié peut être aussi simple que ce qui suit :

   #define Py_LIMITED_API 1
   #include "Python.h"

   #define WIN32_LEAN_AND_MEAN
   #include <windows.h>

   #ifdef WINDOWS
   int WINAPI wWinMain(
       HINSTANCE hInstance,      /* handle to current instance */
       HINSTANCE hPrevInstance,  /* handle to previous instance */
       LPWSTR lpCmdLine,         /* pointer to command line */
       int nCmdShow              /* show state of window */
   )
   #else
   int wmain()
   #endif
   {
       wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
       myargv[0] = __wargv[0];
       memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
       return Py_Main(__argc+1, myargv);
   }

Si vous définissez le symbole du préprocesseur "WINDOWS" cela va
générer un exécutable IUG, et sans lui, un exécutable console.

Pour compiler l'exécutable, vous pouvez soit simplement utiliser les
outils standards en ligne de commande *MSVC*, soit profiter du fait
que *distutils* sait comment compiler les sources Python :

   >>> from distutils.ccompiler import new_compiler
   >>> import distutils.sysconfig
   >>> import sys
   >>> import os
   >>> from pathlib import Path

   >>> def compile(src):
   >>>     src = Path(src)
   >>>     cc = new_compiler()
   >>>     exe = src.stem
   >>>     cc.add_include_dir(distutils.sysconfig.get_python_inc())
   >>>     cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
   >>>     # First the CLI executable
   >>>     objs = cc.compile([str(src)])
   >>>     cc.link_executable(objs, exe)
   >>>     # Now the GUI executable
   >>>     cc.define_macro('WINDOWS')
   >>>     objs = cc.compile([str(src)])
   >>>     cc.link_executable(objs, exe + 'w')

   >>> if __name__ == "__main__":
   >>>     compile("zastub.c")

Le lanceur résultant utilise le « Limited ABI » donc il fonctionnera
sans changement avec n'importe quelle version de Python 3.x. Tout ce
dont il a besoin est que Python ("python3.dll") soit sur le "PATH" de
l'utilisateur.

Pour une distribution entièrement autonome vous pouvez distribuer le
lanceur avec votre application en fin de fichier, empaqueté avec la
distribution *embedded* Python.  Ceci fonctionnera sur n'importe quel
ordinateur avec l'architecture appropriée (32 bits ou 64 bits).


28.4.6.2. Mises en garde
------------------------

Il y a certaines limites à l'empaquetage de votre application dans un
seul fichier.  Dans la plupart des cas, si ce n'est tous, elles
peuvent être traitées sans qu'il soit nécessaire d'apporter de
modifications majeures à votre application.

1. Si votre application dépend d'un paquet qui inclut une extension C,
   ce paquet ne peut pas être exécuté à partir d'un fichier zip (c'est
   une limitation du système d'exploitation, car le code exécutable
   doit être présent dans le système de fichiers pour que le lanceur
   de l'OS puisse le charger). Dans ce cas, vous pouvez exclure cette
   dépendance du fichier zip et, soit demander à vos utilisateurs de
   l'installer, soit la fournir avec votre fichier zip et ajouter du
   code à votre fichier "__main__.py" pour inclure le répertoire
   contenant le module décompressé dans "sys.path". Dans ce cas, vous
   devrez vous assurer d'envoyer les binaires appropriés pour votre ou
   vos architecture(s) cible(s) (et éventuellement choisir la bonne
   version à ajouter à "sys.path" au moment de l'exécution, basée sur
   la machine de l'utilisateur).

2. Si vous livrez un exécutable Windows comme décrit ci-dessus, vous
   devez vous assurer que vos utilisateurs ont "python3.dll" sur leur
   PATH (ce qui n'est pas le comportement par défaut de
   l'installateur) ou vous devez inclure la distribution intégrée dans
   votre application.

3. Le lanceur suggéré ci-dessus utilise l'API d'intégration Python.
   Cela signifie que dans votre application "sys.executable" sera
   votre application et *pas* un interpréteur Python classique.  Votre
   code et ses dépendances doivent être préparés à cette possibilité.
   Par exemple, si votre application utilise le module
   "multiprocessing", elle devra appeler
   "multiprocessing.set_executable()" pour que le module sache où
   trouver l'interpréteur Python standard.


28.4.7. Le format d'archive d'application Zip Python
====================================================

Python est capable d'exécuter des fichiers zip qui contiennent un
fichier "__main__.py" depuis la version 2.6.  Pour être exécutée par
Python, une archive d'application doit simplement être un fichier zip
standard contenant un fichier "__main__.py" qui sera exécuté comme
point d'entrée de l'application.  Comme d'habitude pour tout script
Python, le parent du script (dans ce cas le fichier zip) sera placé
sur "sys.path" et ainsi d'autres modules pourront être importés depuis
le fichier zip.

Le format de fichier zip permet d'ajouter des données arbitraires à un
fichier zip.  Le format de l'application zip utilise cette possibilité
pour préfixer une ligne *shebang* POSIX standard dans le fichier
("#!/path/to/interpreter").

Formellement, le format d'application zip de Python est donc :

1. Une ligne *shebang* facultative, contenant les caractères "b'#!"
   suivis d'un nom d’interpréteur, puis un caractère fin de ligne
   ("b'\n'").  Le nom de l'interpréteur peut être n'importe quoi
   acceptable pour le traitement *shebang* de l'OS, ou le lanceur
   Python sous Windows.  L'interpréteur doit être encodé en UTF-8 sous
   Windows, et en "sys.getfilesystemencoding()" sur POSIX.

2. Des données *zipfile* standards, telles que générées par le module
   "zipfile".  Le contenu du fichier zip *doit* inclure un fichier
   appelé "__main__.py" (qui doit se trouver à la racine du fichier
   zip — c'est-à-dire qu'il ne peut se trouver dans un sous-
   répertoire).  Les données du fichier zip peuvent être compressées
   ou non.

Si une archive d'application a une ligne *shebang*, elle peut avoir le
bit exécutable activé sur les systèmes POSIX, pour lui permettre
d'être exécutée directement.

Vous pouvez créer des archives d'applications sans utiliser les outils
de ce module — le module existe pour faciliter les choses, mais les
archives, créées par n'importe quel moyen tout en respectant le format
ci-dessus, sont valides pour Python.
