Packager une application Python avec ses dépendances
Il y a quelques temps j’ai développé une application en Python. Pour situer le contexte, il s’agissait de créer un exécutable capable de déployer un docker-compose.yml en appliquant des règles métiers. Pour ne pas tout réécrire, j’ai créé un projet en Python pour pouvoir utiliser la librairie docker-compose qui est également en Python. Une fois fonctionnelle, mon application devait être buildée dans un unique fichier. Mon objectif était de générer un fichier binaire embarquant toutes ses dépendances (un peu à la manière Golang).
Attention ! Générer un fichier binaire ne vous dispense pas l’installation de Python sur votre machine. Le fichier binaire n’est pas une application compilée mais juste un package.
Sommaire
Architecture
Dans un premier temps vous allez créer un dossier foobar dans votre projet. Celui-ci contiendra tout votre travail.
1
2
3
4
5
6
|
project
|_ __main__.py
|_ foobar
|_ __init__.py
|_ __main__.py
|_ cli.py
|
Si vous êtes tombés sur cet article, c’est que vous connaissez à minima aussi bien Python que moi et que par conséquent, vous savez comment installer des dépendances de manière globale ou dans un virtualenv.
Personnellement, j’effectue mes développements dans un container Docker donc j’installe mes dépendances en globales.
Voici comment installer une dépendance.
1
|
$ pip install docopt
|
Par la suite vous pouvez donc travailler sur votre fichier cli.py. Voici un exemple avec l’utilisation de la librairie docopt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# project/foobar/cli.py
from docopt import docopt
version = « 1.0.0 »
help = « » »Foobar
Usage:
foobar version
Options:
-h –help Display help
Foobar is an fake open-source project developped by Baptiste Donaux.
« » »
def main():
arguments = docopt(help)
if arguments[« version »]:
print(« foobar version », version)
|
Un fichier pour lancer l’application en développement sera nécessaire (dans mon cas __init__.py est un fichier vide mais requis).
1
2
3
4
5
6
|
# project/foobar/__main__.py
from . import cli
if __name__ == « __main__ »:
cli.main()
|
Pour construire un package binaire, vous aurez besoin d’un point d’entrée (project/__main__.py).
1
2
3
4
5
6
|
# project/__main__.py
from foobar import cli
if __name__ == « __main__ »:
cli.main()
|
Maintenant vous pouvez exécuter votre application facilement.
1
2
|
$ python ./foobar/ version
(‘foobar version’, ‘1.0.0’)
|
Construire un binaire statique
Workflow
À partir d’un projet propre (sans dépendance…), voici les étapes qui vont être réalisées.
- Créer un virtualenv et l’activer
- Installer les dépendances et sortir du virtualenv
- Supprimer les fichiers et les dossiers qui sont présents dans le virtualenv/lib/python2.7/sites-packages qui correspondent à un dossier pip, un dossier de cache, à des fichiers compilés ou des fichiers d’informations.
- Créer un dossier pour construire le fichier final
- Copier les dépendances, les sources et le fichier d’entrée dans le dossier fraîchement créé.
- Créer un dossier compressé (.zip) du contenu du dossier de build.
- Créer le fichier “binaire” avec une en-tête pour spécifier l’environnement et concaténer à ce fichier le contenu du dossier compressé.
Hardcore
Voici les étapes techniques à réaliser.
1
2
3
4
5
6
7
8
9
10
11
12
|
$ virtualenv dependencies
$ . ./dependencies/bin/activate
$ pip install docopt
$ deactivate
$ rm -rf $(find ./dependencies/lib/python2.7/site-packages -print | egrep ‘(/pip/)|(__pycache__)|(.(pyc|dist-info|so)$)’)
$ mkdir build
$ cp -R ./dependencies/lib/python2.7/site-packages/* ./foobar ./__main__.py ./build/
$ cd ./build
$ zip -r ../release.zip *
$ echo ‘#!/usr/bin/env python’ > ../release
$ cat ../release.zip >> ../release
$ chmod +x ../release
|
Conclusion
Vous pouvez maintenant exécuter facilement votre binaire. Il embarque tout son contexte.
1
2
|
$ ./release version
(‘foobar version’, ‘1.0.0’)
|
Cette solution m’a été très pratique pour déployer une application avec un unique fichier. Vous pouvez télécharger l’archive des fichiers d’exemple pour reproduire ma démonstration.