ARTICLES
pex
By Valentin Bremond
pex, pour Python EXecutable est un outil autour de Python qui vous permet de ne plus vous emmerder la vie avec le truc le plus relou de Python (de la grande majorité des langages en fait) : les librairies.
Python a une solution assez élégante pour la gestion des librairies : virtualenv. Un activate, un pip install et le problème est réglé. Sauf que déjà, vous devez répéter cette opération sur chaque machine qui veut utiliser votre code, mais surtout si vous commencez à récupérer du code sur vos machines de prod directement depuis Internet (lors de vos déploiements), vous ne maîtrisez pas totalement votre environnement et ça finira par vous retomber sur le coin de la tronche un jour ou l’autre.
Pourquoi ne pas packager directement votre code et les librairies dans un seul paquet ? pex vous permet de faire ça.
Avoir un shell Python “batteries included”
Mettons que vous vouliez tester un truc avec les librairies Flask et SQLAlchemy dans un shell Python classique. Sans pex, vous feriez :
1$ mkdir test && cd test
2$ virtualenv .
3$ source bin/activate
4$ pip install sqlalchemy flask
5$ python
6 blabla, puis
7$ deactivateAvec pex, vous feriez :
1$ pex sqlalchemy flaskVous avez remarqué les 5 étapes en moins ?
Exécuter un script
Mettons maintenant que vous vouliez exécuter un script Python (que nous appellerons toto.py) et qui a besoin des librairies définies dans requirements.txt. Sans pex, vous feriez :
1$ source bin/activate
2$ pip install -r requirements.txt
3$ ./toto.py
4$ deactivateAvec pex, vous feriez :
1$ pex -r requirements.txt -- ./toto.pyVous avez rem… oui, vous avez vu.
Packager un programme complet
Venons-en à la partie la plus intéressante : être capable de packager tout un programme en un seul paquet, librairies et autres fichiers Jinja (par exemple) inclus.
Mettons qu’on veuille créer une API REST toute bête qui récupère des informations en query params et renvoie du texte fonction de ces paramètres. Le texte en question sera stocké dans un fichier Jinja et on utilisera Flask comme framework REST.
Vous pouvez directement récupérer ce code source depuis GitHub : pex-example.
On a donc quelque chose comme ça :
1example/
2 api/
3 __init__.py
4 __main__.py
5 templates/
6 hello_world.j2
7 MANIFEST.in
8 requirements.txt
9 setup.pyAvec les contenus des fichiers suivants :
requirements.txt:
1flask
2jinja2setup.py:
1import setuptools
2
3install_requires = [
4 "flask",
5 "Jinja2",
6]
7
8setuptools.setup(
9 name="api",
10 version="1.0",
11 description="Simple API",
12
13 packages=setuptools.find_packages(),
14 include_package_data=True,
15
16 install_requires=install_requires,
17
18 entry_points={
19 "console_scripts": [
20 "api = api.__main__:main",
21 ],
22 },
23)MANIFEST.in (sert à faire savoir à Python qu’il doit inclure les fichiers de template dans le package):
1recursive-include api/templates *api/__main__.py (le script qui sera appelé en tant qu’entrypoint - il doit être exécutable) :
1#!/usr/bin/env python
2
3import api
4
5
6def main(*argv):
7 api.main()
8
9
10if __name__ == '__main__':
11 main()api/__init__.py (notre code utile) :
1import os
2
3import flask
4import jinja2
5
6# Because the files will be packaged in the pex, they're not where you would
7# think they are
8TEMPLATES_FOLDER = os.path.join(os.path.dirname(__file__), 'templates')
9
10
11def main():
12 app = flask.Flask(__name__)
13
14 @app.route('/')
15 def get():
16 firstname = flask.request.args.get('firstname')
17 lastname = flask.request.args.get('lastname')
18
19 template = ""
20
21 with open(os.path.join(TEMPLATES_FOLDER, 'hello_world.j2'), 'r') as f:
22 template = jinja2.Environment().from_string(f.read()).render(
23 firstname=firstname,
24 lastname=lastname
25 )
26
27 return template
28
29 app.run()Enfin, api/templates/hello_world.j2 (le texte qui sera renvoyé à l’utilisateur) :
1Salut {{ firstname }} {{ lastname }}, comment va ?On peut maintenant tout simplement packager notre application de la manière suivante :
1$ pex . -r requirements.txt --script api --output api.pex(notez ici que le --script api correspond au mot clé api qu’on a défini dans entry_points du setup.py)
(notez également que vous pourriez rajouter le paramètre --disable-cache si vous avez des problèmes de pex qui ne contiennent pas ce qu’ils devraient - ça arrive parfois à cause du cache qu’utilise pex)
Et maintenant, magie :
1$ ./api.pex
2 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
3
4(et dans un autre terminal)
5
6$ curl 'http://127.0.0.1:5000?firstname=Toto&lastname=Lafrite'
7Salut Toto Lafrite, comment va ?Notez qu’on n’a pas utilisé de virtualenv et qu’on n’a rien installé comme librairies sur le système. Vous n’avez qu’à avoir un interpréteur Python et le tour est joué.
Inconvénients
Il y a bien évidemment quelques inconvénients à utiliser pex :
- Étant donné que
pexva en réalité vous faire une genre d’archive auto-extraite par Python au moment du lancement, vous allez avoir un léger overhead à chaque lancement de votre application (en réalité,pexva extraire les fichiers dans le dossier~/.pexlors du lancement puis les exécuter). - Vous perdez une partie de la flexibilité des langages interprétés qui vous permettent de les modifier en live pour tester une feature ou fixer un bug (vu que vous ne pouvez pas modifier un
pexen live). Toutefois, vous pouvez toujours retrouver vos fichiers sources dans~/.pex(comme expliqué au-dessus) et les modifier, c’est juste un peu plus relou. pexest un projet assez jeune, vous ne trouverez probablement pas de package pour votre distro. Il faudra passer parpippour l’installer.
Conclusion
Globalement, pex est un outil génial que vous mettez dans votre CI et qui simplifie considérablement vos déploiements. Plus besoin de jongler entre les virtualenv pour développer et tester votre code. Et si vous rajoutez tox à côté pour gérer vos tests, vous devenez un champion Python. Mais ça, on en parlera dans un autre article !
Edit : bonne nouvelle : l’article en question est sorti !