Logo Blog perso d'Ozwald

Mes frameworks python du moment

Par Oz le - Informatique
bottle peewee python wsgi

Fin 2012, je vous avait parlé de deux modules standards de la librairie python que j'aimais beaucoup : logging et argparse. Presque deux ans plus tard, il est temps de rendre hommage à deux modules n'appartenant pas à la librairie standard python mais que j'aime tout particulièrement : peewee et bottle.

Swiss Knife - Creative Common by "focusforaword" on Flickr

Sqlite, MySQL, PostgreSQL ? Les trois !

Nombre des scripts python que j'écris ont besoin, lorsqu'ils atteignent une certaine importance, de stocker des informations de façon stable. Dans les cas les plus rustres je me contente d'un gros coup de pickle dans un fichier qui sera relu plus tard mais, parfois, le stockage et l'accès à mes données doit être plus performant et l'utilisation d'un outil dédié (à savoir : une base de donnée) semble trop évidente pour être ignorée. Avant que je ne découvre peewee, je réglais systématiquement ces besoins par la librairie sqlite31, qui a le bon gout d'être standard et, donc, de ne pas rajouter de dépendance à mes scripts (les laissant ainsi le plus portable possibles d'une machine à une autre). Malheureusement, cette approche avait deux défauts majeurs pour moi :

  • Si sqlite s'avérait, au bout de plusieurs semaines/mois d'utilisation, trop léger pour traiter mon volume de donnée je n'avais plus qu'à tout reprendre de zéro pour passer à une solution plus robuste

  • Je suis mauvais en SQL, donc taper les requêtes SQL moi-même était à la fois désagréable et peu efficace.

C'est pourquoi, lorsque j'ai découvert peewee, j'ai tout de suite adhéré au concept : c'est un orm (donc pas de SQL à écrire) capable d'utiliser un backend sqlite, mysql, ou postgresql sans avoir à changer le code :) ! En plus, je connais quelqu'un qui m'a dit récemment avoir testé peewee sur un backend sqlite avec une dizaine de thread concurrent sans le moindre problème. Étant assez fan du multithread et ayant déjà eu des problèmes avec sqlite ça serait pour moi la cerise sur le gateau si cette robustesse en multithread se confirmait :) ! En tout cas, j'ai déjà utilisé peewee sur une demi douzaine de projets (dont un qui commence à devenir assez imposant) et c'est toujours un plaisir de travailler avec cet ORM : je le conseille donc vivement.

Seul tout petit bémol, pour terminer le paragraphe : j'ai eu UN problème avec peewee. Je voulais utiliser un modèle ou une table A référence (foreign key) une table B; sachant que la table B doit, elle aussi, avoir une foreign key vers la table A. Oui, je sais, ça fait des références circulaires et ce n'est généralement pas le signe d'une bonne conception mais dans mon cas d'usage c'est ce que je voulais faire et il n'y avait pas vraiment d'autres alternative plus propre. Comme peewee crée les table en base au fur et à mesure qu'il lit la déclaration des objets, ce cas d'usage pose problème puisqu'au moment de créer la première table peewee n'a pas (encore) connaissance de la seconde et il échoue plante lors de la création de la foreign key vers une table qui n'existe pas encore. Si une seule des deux tables référençait l'autre, il me suffirait d'inverser l'ordre de déclaration des tables pour que peewee découvre la table cible en premier, mais comme j'ai une référence dans les deux sens cette astuce ne me sauvera pas. Tout n'est pas perdu pour autant : peewee prévoit ce cas de figure et propose d'utiliser un objet "proxy" qui sera la cible de la foreign key lors de la création des tables puis qui sera remplacé par les tables réellement ciblées une fois qu'elles auront toutes été créées. En pratique, ça marche probablement...SAUF quand le backend est sqlite.

En effet, pour réaliser ce tour de passe-passe, peewee crée les tables sans les foreign key lorsqu'il découvre la déclaration des objets, puis il utilise l'opération SQL "ALTER TABLE" pour ajouter les "CONSTRAINT" de foreign key au moment où l'objet proxy est remplacé par les bonnes tables une fois que ces dernières ont toutes été créées. Sauf que sqlite ne supporte pas cette fonctionnalité du SQL. Dans ce cas, il n'y a que deux solutions :

  • abandonner sqlite
  • créer (et gérer) soit même la relation de foreign key, en utilisant de simples champ "INTEGER" référençant un ID dans la table cible (c'est ce que j'ai choisi).

Nginx ? Apache ? IIS ? Aucun des trois !

Une autre fonction classique de mes script python lorsqu'il prennent de l'ampleur, c'est d'avoir une interface web (plus ou moins simpliste). Pour ce faire, j'ai longtemps surchargé BaseHTTPServer. J'ai bien tenté d'utiliser cherrypy ou django quand je prévoyais que mes scripts allaient demander un peu plus de complexité, mais j'ai vite abandonné2. Bref : j'utilisais toujours BaseHTTPServer, et j'étais contraint de ne faire que de petits sites un peu minables...

Puis, un jour, j'ai découvert wsgi sur l'excellent site de bortzmeyer. En résumé : c'est une spécification, dans un esprit assez similaire à CGI, qui définit une interface python très simple pour fabriquer des sites web. Pour offrir une interface web à votre script python il vous suffit donc de créer une classe d'objet "application" compatible avec la spécification WSGI, et à donner cette classe en argument à un serveur web compatible (par exemple, le "simple_server" de la librairie standard wsgiref). L'énorme avantage, c'est que si vous avez besoin de performance plus tard il vous suffit de changer le serveur web sans changer une seule ligne de votre code python (un peu comme lorsque vous changer de backend peewee). Par exemple, vous pouvez remplacer le serveur inclus dans la librairie standard par werkzeug ou cherrypy et vous obtenez immédiatement le support du multithread (qui est quand même un must pour tout ce qui est web).

Le seul inconvénient de WSGI (qui m'est tombé dessus assez vite), c'est que ça reste quand même assez rustre. On est à un niveau d'abstraction supérieure à BaseHTTPServer, mais il manque quand même des raccourcis pour réaliser certaines tâches classiques (comme mettre des fichiers statiques à dispositions, types CSS ou JS). On se retrouve donc rapidement à devoir passer du temps de développement sur des fonctions "inutiles" vis-à-vis de l'objet du script mais nécessaires au fonctionnement de l'interface web (et servir des fichiers static peut prendre beaucoup de temps de développement, en particulier quand on est paranoïaque et qu'on veut faire attention aux LFI et autres farces). C'est pourquoi, je conseille bottle ! Bottle est une librairie python non-standard typiquement dans l'esprit de ce que j'aime : elle tient en un seul fichier python (et vous pouvez donc l'embarquer partout sans sacrifier la portabilité de votre script :)). C'est un wrapper mince autour de la spécification WSGI qui vous offre des facilité pour tout un tas de besoins classiques, par exemple :

De plus, comme bottle est un wrapper mince, on ne perd pas la puissance de WSGI puisqu'on peut passer d'un simple serveur embarqué écrit en python à des choses plus musclées comme gunicorn ou bjoern sans avoir à ré-écrire son code ! Cerise (inutile mais agréable) sur le gateau : bottle accepte d'utiliser un serveur "auto" signifiant qu'il va, tout seul, sélectionner le serveur compatible WSGI le plus performant actuellement installé sur votre machine (sympa quand vous avez cherrypy sur vos machines de prod mais pas sur les machines de dev par exemple).

  1. et, par moments, pysqlite qui permet d'optimiser un peu l'utilisation du moteur sqlite
  2. Je conserve de ces tests une légère rancœur envers django, qui m'a laissé la très mauvaise impression de sortir une nouvelle version tout les 6 mois tellement incompatible avec les versions précédentes que même les tutoriaux "Hello World" ne fonctionnaient subitement plus