Django, le wsgi et paste: status
Cette après-midi, j'ai retrouvé mon collègue de geekerie, Olivier. Pendant que lui tentait de faire apprendre à rêver à son cher Dr Gumby, je me suis atteler à faire fonctionner django avec paste. Pas une mince affaire à priori. J'avais vu une tentative pour faire un paste.django qui semblait évoluer péniblement. Mais bon, je suis plein de courage.
J'ai finalement été assez surpris. Déjà, on trouve dans le trunk de django un WSGIHandler. C'est la fête ! Il ne reste plus qu'as l'associer à paste. Bon, je me lance.
La première chose que j'ai faites a été de transformer l'application en egg. Je comprends pas que ce ne soit pas fait de fait dans la template django... Soit, un simple setup.py et mon application peut-être intégrée dans mon buidlout.
Ensuite, première tentative pour utiliser ce précieux handler: créer une usine qui renvois le WSGIHandler tel quel. Échec. Forcément, django attends son fameux module de settings. Soit, avec un peu de chance, en le plaçant dans l'environ utilisé par l'usine, on vas y arriver. Re-échec.
Pourquoi ? Tout bêtement parce que django vas chercher cette valeur dans os.environ. Et là, c'est le drame. Enfin non, c'est à moitié le drame. En effet, en initialisant correctement la variable qui vas bien, on arrive finalement à utiliser ce handler.
Second problème, les url générées par l'application sont toutes erronées. En effet, un bug fait que django ne tiens pas compte de la variable SCRIPT_NAME. C'est bien dommage, mais un bon vieux hack permet de fixer le problème relativement facilement. Il suffit d'initialiser le PATH_INFO en le précédent du SCRIPT_NAME et le tour est joué.
Voici le résultat:
# -*- coding: utf-8 -*-
import os
from paste.deploy.config import ConfigMiddleware
from django.core.handlers.wsgi import WSGIHandler
def factory(global_config, **local_config):
os.environ['DJANGO_SETTINGS_MODULE'] = 'jobsafpyorg.settings'
conf = global_config.copy()
conf.update(**local_config)
app = ConfigMiddleware(WSGIHandler(), conf)
def django_app(environ, start_response):
environ['PATH_INFO'] = environ['SCRIPT_NAME'] + environ['PATH_INFO']
return app(environ, start_response)
return django_app
On peut ensuite se servir de cette usine comme point d'entrée et l'utiliser dans un fichier de configuration paste. Notez que le ConfigMiddleware est indipensable. Le handler de django semble renvoyer des choses peu ordinaire de type class, parfois. Le middleware permet de corriger tout cela.
Problème, si on veut plusieurs instance django dans un seul environnement wsgi, ça devient problématique. Comment initialiser plusieurs configuration avec une seul clé du dictionnaire environ... Je me le demande.
Cela dit, c'est un problème auquel je vais être confronter car j'aimerais fair cohabiter les deux applications django qui ont été faites pout l'AFPy. Donc, la suite au prochaine épisode.
Il y a de la vie après PyCON !!!
Il y a peu, c'était Pycon FR, la récompense annuelle des efforts fournit dans cette belle association qu'est l'AFPy. Grande réussite de mon point de vue. La richesse et la diversité des conférences s'améliore, le public est plus nombreux. Python a de beaux jours devant lui.
J'y ai fait une conférence sur le WSGI. Ça à au moin eu le mérite de me faire réaliser que j'étais un bien piètre orateur. Probablement que l'AFPYro de la veille n'as rien fait pour arranger les choses, hin hin. En tout cas, j'espère que ça suscitera quelque vocations.
Je suis personnellement convaincu de l'intérêt de cette norme. Et j'ai maintenant un exemple concret à fournir. En effet, je bosse depuis plusieurs mois sur la nouvelle interface de gestion des membres de l'AFPy. Une petite application en Pylons qui permet d'administrer les utilisateurs de notre annuaire ldap et les inscription dans le Zope et les listes de diffusions. Elle est ici, pour ceux que ça intéresse. J'avais aussi fait une petite application compatible WSGI pour pouvoir afficher les photos avec un tag AFPy que les gens posent sur flickr. On avait aussi besoin d'un nouveau WIKI dans le cadre de la refonte du site. Le but était donc de brancher tout ce petit monde dans le site actuel. Heureusement, il y a findus^WWSGI.
Le paquet wsgi.afpy.org était né. Ça donne un petit fichier de configuration sympa qui dessers ces trois applications via un urlmap. Bien sur, le tout est géré avec zc.buildout, ce qui permet d'avoir un environnement python avec les paquets qui vont bien et de faire de l'administration ldap dans un shell python. Un exemple:
>>> from afpy.core import ldap
>>> user = ldap.getUser('gawel')
>>> user
<User dn:uid=gawel,ou=members,dc=afpy,dc=org>
>>> user.email
'gawel@afpy.org'
>>> ldap.getMembersOf('bureau')
[u'tarek', u'ogrisel', u'gwen', u'gawel', u'jpcw2002', u'ccomb']
C'est quand même super fun !!!
django monte en puissance
Je crois que django a de l'avenir. Google viens de sortir son app engine qui s'appuie en grande partie sur django et le WSGI (ça me fait penser qu'il faudrait que je prépare ma conf pour pycon un jour). C'est plutôt très bon signe pour django et pour python plus généralement, d'ailleurs. Google se lance dans le mass-hosting et c'est en python que ça se fait. Cet hébergement gratuit grand publique est-il le début de la vrai vulgarisation de python ? On ne peut que l'espérer ! Jusqu'à présent, objectis était le seul hébergeur gratuit (au monde ?) a proposer une solution python gratuite. Cela manquait vraiment.
Suite à cette annonce, j'ai eu aussi l'occasion de découvrir playlive.fm. Un site musico-social 2.0 basé sur django. Le résultat est joli (j'ai eu une invite, héhé) et j'ai été agréablement surpris de voir que Hocus-Pocus (je suis super fan, ils tuent. naoned représente !) est déjà présent sur la plate-forme.
svn: le futur du CMS !!!
Vous êtes comme moi je suis sûr. Editer des document dans des éditeur WYSIWYG, ça vous saoul au plus haut point. Personnellement, je préfère de loin un bon éditeur et du reStructuredText.
La solution je l'avais depuis longtemps. Stocker les billets de mon blog dans un svn et les publier avec un framework quelconque. Ca fait un moment maintenant que j'utilise Zope3 pour ça, mais le code était assez dégueulasse. Je me suis donc dit que si je mettais tout ça un peu au propre, cela pourrait servir à d'autres. Je me suis donc lancé dans un énorme week-end de geeking. le résultat est gp.svnfolder, un package Zope3 permettant de publier un répertoire svn contenant des documents en reStructuredText.
Voyons plutôt. Il nous faut une repository valide:
>>> import os
>>> from os.path import split
>>> curdir = split(split(os.getcwd())[0])[0]
>>> repos = os.path.join(curdir, 'tests', 'rstfolder')
Avec ça, on peut instancier un dossier svn:
>>> from gp.svnfolder.folder import SVNFolder
>>> folder = SVNFolder()
>>> folder.__name__ = 'blog'
>>> folder.path = u'file://%s' % repos
Et en voir le contenu:
>>> sorted(folder.keys())
[u'doc.rst', u'doc2.rst']
Et accéder aux fichiers:
>>> file = folder['doc.rst']
>>> print file.data
Document 1
==========
<BLANKLINE>
Ensuite il suffit d'utiliser les vues fournies pour rendre ces fichiers en html.
Voila. Je penses qu'avec un peu de motivation je pourrais facilement le rendre compatible avec Mercurial et Bazaar mais ça suffit à faire mon bonheur :)
Monter un environnement python avec zc.buildout
zc.buildout est un utilitaire créer par Jim Fulton, le papa de Zope. L'étendue des capacités de zc.buildout est énorme et mériterais un livre entier.
Cependant, voici un petit exemple pour se monter un environnement de paquets/binaire python avec un simple petit fichier de configuration.
Tout d'abord, nous allons nous créer un virtualenv afin de ne vraiment pas polluer notre distribution:
gawel@Stacy:~$ virtualenv --no-site-packages py New python executable in py/bin/python Installing setuptools.............done.
Si vous n'avez pas virtualenv, installez setuptools et virtualenv sur votre système. D'après moi, ce sont les deux seuls packages qui mérite d'être installé globalement, pour tout le système.
On va ensuite installer zc.buildout dans notre environnement:
gawel@Stacy:~$ cd py gawel@Stacy:~/py$ ./bin/easy_install zc.buildout Searching for zc.buildout ... Finished processing dependencies for zc.buildout
Parfait, maintenant, initialisons l'environnement de zc.buildout:
gawel@Stacy:~/py$ ./bin/buildout init Creating '/Users/gawel/py/buildout.cfg'. Creating directory '/Users/gawel/py/parts'. Creating directory '/Users/gawel/py/develop-eggs'. Generated script '/Users/gawel/py/bin/buildout'.
Puis on édite le fichier buildout.cfg pour qu'il ressemble à ça:
gawel@Stacy:~/py$ cat buildout.cfg
[buildout]
parts = eggs
[eggs]
recipe = zc.recipe.egg
eggs=
ipython
i18ndude
ZopeSkel
IngeniSkel
iw.releaser
Vous pouvez mettre absolument n'importe quel egg dans l'option eggs. La seule contrainte est que ceux-ci se trouvent sur pypi.
On lance la construction du buildout:
gawel@Stacy:~/py$ ./bin/buildout Installing eggs. ... Generated script '/Users/gawel/py/bin/ipython'. Generated script '/Users/gawel/py/bin/pycolor'. Generated script '/Users/gawel/py/bin/i18ndude'. Generated script '/Users/gawel/py/bin/project_deploy'. Generated script '/Users/gawel/py/bin/project_release'. Generated script '/Users/gawel/py/bin/project_diff'.
J'ai maintenant tout mes binaires dans le dossier bin/ de l'environnement:
gawel@Stacy:~/py$ ls bin/ activate easy_install-2.4* project_deploy* pycolor* buildout* i18ndude* project_diff* python* easy_install* ipython* project_release* python2.4@
Le must, c'est que je peux importer chacun de ses paquets dans mon ipython:
gawel@Stacy:~/py$ ./bin/ipython In [1]: import i18ndude In [2]: dir i18ndude ------> dir(i18ndude) Out[2]: ['__builtins__', '__doc__', '__file__', '__name__', '__path__']
Vous pouvez bien sur ajouter des eggs dans votre buildout.cfg puis relancer ./bin/buildout pour qu'il soit prit en compte.
Ceci est une simple introduction à zc.buildout. On peut faire bien mieux, je sais. Mais ça deviendrais compliquer de tout expliquer dans un simple post :)
Simplement, Je trouve ça tellement pratique pour tester rapidement un package que je voulais en faire bénéficier tous ceux qui ne connaissent même pas l'existence de zc.buildout.
Traduire un package Plone 3
Je n'ai pas creusé plus que ça, mais apparemment il y a de beaux conflits entre zope.i18n et PlacelessTranslationService. Du coup, tant que l'on veut ajouter des traductions à un domaine non existant, se servir de zope.i18n ne pose pas de problème. Par contre, si l'on désire ajouter des traductions au domaine plone par exemple, ça ce complique.
Le seul moyen que j'ai pu trouver après quelques heures d'introspection est d'enregistrer un .po qui se trouve hors du répertoire locales (si vous le mettez dedans, ça casse tout) lors de l'initialisation du paquet. Cela donne un bout de code horrible qui me ferais presque honte... Voyez plutôt le contenu de mon __init__.py:
def initialize(context):
"""Initializer called when used as a Zope 2 product."""
context._ProductContext__app.Control_Panel.TranslationService._load_i18n_dir(os.path.dirname(__file__)+'/i18n')
Alors, oui, c'est dégueulasse, mais ça marche.
Utiliser zope.sendmail dans Zope 2
Depuis que les sources de Zope3 sont incluse dans Zope2, il est vraiment dommage de ne pas utiliser au maximum ces possibilités.
Entre autre, il existe un package assez peu connu zope.sendmail qui permet d'envoyer des mails de façon asynchrone. Les mails sont stockés dans une mailbox puis envoyés par un thread séparé de Zope.
L'inconvénient de ce package est que tout est configurable, mais uniquement via du zcml. Le TODO indique que, oui, ça serait bien qu'un jour cela soit dans le zope.conf, mais en attendant...
Finalement, depuis qu'on a tendance à utiliser buildout (parce que quand même, ça arrache) il était super simple de créer une petite recette qui installe un bout de zcml dans le répertoire qui vas bien.
Le résultat est iw.recipe.sendmail. Simple, et efficace.
Un autre petit paquet, iw.mailhost, permet de patcher les produits MailHost et SecureMailHost afin d'utiliser zope.sendmail en lieu et place de smtplib. Il suffit de le placer dans un répertoire inclus dans le sys.path pour qu'il soit importé par le zcml généré par iw.recipe.sendmail
A noter que ceci a été éprouvé sous OSX, Linux et Windows. Enjoy !
Utiliser zope avec virtualenv
Virtualenv permets de créer un environement python indépendant de celui du système. C'est assez pratique lorsque l'on veut avoir plusieurs versions d'une même librairie.
Il est possible de l'utiliser avec Zope. C'est même très simple...
On install virtualenv avec easy_install:
~$ easy_install virtualenv
On s'extrait un Zope:
~$ tar xvzf Zope-2.X.X-final.tgz ~$ mv Zope-X.X.X-final myenv
On créer un virtualenv dans l'arborescence du Zope:
~$ virtualenv myenv New python executable in myenv/bin/python Installing setuptools......................done. ~$ source bin/activate (myenv)$ which python /home/gawel/myenv/bin/python
On utilise le python de l'environnement pour installer le Zope:
(myenv)~$ cd myenv (myenv)~/myenv$ ./configure --with-python=bin/python Using Python interpreter at bin/python Configuring Zope installation - Zope top-level binary directory will be /opt/Zope-2.9. - Makefile written. Next, run make.
On le compile:
(myenv)~/myenv% make && make inplace
On peut ensuite créer des instances comme d'habitude. Ces instances utiliserons notre virtualenv.
Quand la poisse d'acharne
Enfin, une nouvelle semaine commence. Je ne sais pas ce que j'ai fais à celle passée pour qu'elle s'acharne autant sur moi. Aujourd'hui, c'est la première fois depuis une semaine qu'il ne m'arrive pas de sale coup. Je crois que je n'aurais échappé à rien.
Des bornes vélib' pleines m'obligeant à faire 2km alors que, au départ, j'étais à 500m de chez moi. Non mais, ces foutues bornes sont toujours vide quand je veux en prendre un à coté de chez moi ! Bon, ça fait faire du sport...
Une jeune femme qui se gourre (ahem) en me filant son numéro de téléphone. En la rappelant une semaine plus tard, je tombe sur une gamine de 15 ans. Je m'excuse, et j'oublie. Par contre, s'en suit un harcellement téléphonique de 3 jours par pas moins de 4 gamines n'ayant rien de mieux à faire pendant leurs cours que de faire beeper mon téléphone. Elle aurait pû s'y mettre toute seule, ça n'aurais fait qu'un coup de fil toute les 5mn. Mais non, il faut qu'elle fasse participer 3 de ses pimbêches de copines ! Je vous assure qu'a cet âge, elles ont un sacré rendement sur la touche Bis. Tout ça à cause d'un mauvais numéro. Si c'est pas de la poisse, je sais pas ce que c'est.
Du vol. Cette semaine, j'aurais laissé planer dans la nature: un t-shirt laissé 10mn dans un panier de vélib' dans une rue déserte à 2h du mat'. Mon téléphone portable et mon précieux iPod resté dans mon manteau que j'avais laissé traîner à quelques pas de moi. La malédiction s'acharne.
La carte vélib' (ouais, encore lui. ahem) qui refuse de m'en délivré un. Là, mon imagination virevolte: "Mais, heu, le dernier que j'ai pris c'était y a 3 jours". Je me voyait déjà payé ces trois jours à raison 4 euros de l'heure. J'avoue ne pas avoir eu le courage de faire l'addition...
Vous me direz que mes problèmes sont ridicules. Oui ! Ce ne sont d'ailleurs même pas des problèmes et c'est bien le drame. Je crois qu'il est plus facile à vivre d'avoir de vrai problèmes et de savoir comment les aborder que de vivre de tels événement face auquels ont est totalement impuissant. Juste parce qu'on à la poisse !
C'est le genre de billet que j'ai horreur d'écrire. Je trouve que ça n'as aucun intérêt de raconter sa vie. Mais bon, la prochaine fois que je passe une semaine vraiment pourrie ça me rappellera que j'ai vécu pire !
Les bornes vélib' sont sales
Vendredi dernier il faisait beau et bonheur, j'ai une borne vélib' à 10 mètres de chez moi. Je me dis donc: "Allez, un petit coup de vélo !".
Je tapotes donc sur le clavier de la borne vélib' pour obtenir un ticket journalier. Arrive le moment où on me demande ma carte bleu. Je l'introduis donc puis je saisis mon code. Et là, c'est le drame ! Cette foutu borne plante, puis reboot. Impossible de retirer ma CB. Petit coup de flip. Bon, à défaut de pouvoir la retirer et fuir, je saisis mon téléphone et je shoot !

On s'en serait douté, pour que la borne reboot aussi inopinément, il ne pouvait s'agir que d'un saleos ! On se demande vraiment ou est l'intérêt de ne pas utiliser du libre alors que c'est probablement une application métier qui aurait très bien pu être écrite en C ou en python. Voila le résultat !