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 !!!
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.
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.
Les exceptions python
Les exceptions python sont toutes simple. Vous pouvez facilement créer les votre:
>>> class MonException(Exception):
... """ Une exception """
Puis les propager:
>>> raise MonException('Bouh !')
Traceback (most recent call last):
...
MonException: Bouh !
Ca, c'est la base. Mais il y a des trucs plus rigolo à faire. Par exemple garder la trace d'une suite d'exceptions:
>>> class MultiException(Exception):
... """ une exception contstitué de plusieurs autre """
... def __init__(self):
... Exception.__init__(self)
... self.data = []
... def __nonzero__(self):
... return self.data and True or False
... def __str__(self):
... return ', '.join(['%s: %s' % (e.__class__.__name__,str(e)) for e in self.data])
Et voila ce que cela donne à l'utilisation. On fait une boucle dans laquelle une ou plusieurs exceptions peuvent se produire:
>>> exceptions = MultiException()
>>> for i in [1, 3, 0, 7, 1, 0]:
... try:
... a = 2/i
... except Exception, e:
... exceptions.data.append(e)
Puis on propage notre exception si elle n'est pas vide:
>>> if exceptions:
... raise exceptions
Traceback (most recent call last):
...
MultiException: ZeroDivisionError: integer division or modulo by zero, ZeroDivisionError: integer division or modulo by zero
On voit bien ainsi que deux exceptions se sont produites et non une seule. On pourrait faire mieux. Par exemple conserver la pile de chaque exception, etc.
En fait, je n'ai rien inventé. C'est le principe utilisé par exemple dans Zope pour gérer la validation de formulaire (zope.app.form). Je trouve que c'est plutôt un bon concept. Après tout, une erreur de saisie c'est quelque chose qui engendre une erreur. Donc une exception :)
gawel.org testé et approuvé
Comme vous le savez peut-être, les données de ce site sont entièrement stockées dans un svn. Perso, éditer des documents dans un éditeur web, ça me saoul.
Bon, ça, c'était bien, mais j'y ai trouvé un autre avantage. En suivant les diverses conférences sur les tests unitaire aux JFP je me suis dis: "Hey, t'es con, tu pourrais tester tout ton site !"
En effet, il m'as suffit d'écrire ce petit bout de code:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import doctest
import unittest
import os
import sys
import glob
import zope.component
import zope.interface
flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_ONLY_FIRST_FAILURE)
exts = ['.rst','.txt']
def test_suite(args=[]):
suite = []
dirname = os.path.dirname(__file__) or '.'
for root, dirs, files in os.walk(dirname):
if '/.' in root:
continue
for filename in files:
f,ext = os.path.splitext(filename)
if ext in exts:
path = os.path.join(root, filename)
if not os.path.isfile(path):
continue
locales = dict(template_dir=root)
suite.append(doctest.DocFileTest(path,
optionflags=flags,
globs=dict(globals(),**locales)))
return unittest.TestSuite(suite)
if __name__ == '__main__':
sys.argv[0] = __file__ = 'tests.py'
unittest.main(defaultTest='test_suite')
Un peu gruik, je l'avoue. Mais ça me permet de tester l'ensemble des exemples écrit en doctest sur ce site. Ceci implique que tout les bouts de code que vous trouverez ici seront 100% valide et à jour:
gawel@cecilia:~/svn/rst% python tests.py ......................................... ---------------------------------------------------------------------- Ran 41 tests in 0.717s OK
Elle est pas belle la vie ? Même les annonces des AFPyro vont être testées !
Ah ! Les JPF ! Bonheur !
Bon, bah voilà, c'est fini. Je crois que tout le monde à passé un super weken à la vilette. Franchement, super réussite les Journées Francophone Python. Tous les retours sont positifs. Extra.
Même l'AFPyro super à l'arrache c'est super bien passé. Bon j'ai eu un peu de mal à me levé le dimanche... J'ai raté quelques conf super convi. Heureusement, on devrait bientôt avoir les slides/vidéos de tout ça sur http://journees.afpy.org. Je suis sauvé !
Les journées ont même été suivis par une petite vingtaine de personnes sur Second Life. C'est tout de même impressionnant. J'espère qu'au moins ils avait chopé un FONSpot au soleil :)
Bref, super réussite pour une première. Je tiens à remercier tous les participants, Thomas (super disponible quand les caméras plantaient, et vraiment sympa), Emilie et Sophie pour avoir manié le manche des caméras avec tant de délicatesse, Benoit pour avoir fêter son année à l'AFPyro :), milo pour avoir tenté de choper le numéro d'une serveuse pour moi et tous les gens de l'AFPy surtout les nouveaux adhérents ;)
Une chose est sur, à l'année prochaine ! On ne s'arrêtera pas devant un tel succès.
Journées Python Francophones
Ca y est ! L'annonce des JPF est parue officiellement sur Linux fr.
Le programme est lui aussi disponible. A mon avis, il faut s'attendre à deux jours de pure bonheur.
Michael Scherer nous fait une conf sur Twisted. Ca, je kif à mort. J'adore ce framework et je le connais que trop peu. (au passage, ils ont refait leurs site. Il est super joli leurs trac)
Je serais particulièrement attentif à la conf de Kamon Ayeva sur Grog. Je n'ai pas encore trop joué avec et ça à plutôt l'air prometteur.
L'atelier ipython de mon jeune collègue Olivier Lauzanne devrait me permettre d'améliorer l'utilisation que j'en fait.
En fait, tout promets d'être intéressant !
Et bon, j'ai ouïe dire qu'un AFPyro anarchique serait organisé le samedi soir par un petit nombre de pythoneurs sur gonflés.
J'avais à un moment envisagé de faire une présentation "Publier votre base de donnée sur le web avec python". Finalement, le manque de temps aidant, j'ai abandonné l'histoire. Cela fera peut-être l'objet d'un Lightning Talk. Qui sait ;)
Journées Python Francophones
L'AFPy organise les Journées Python Francophones le week-end du 2 et 3 juin 2007. Deux jours entiers ou Python sera à l'honneur.
Au programme: ateliers et conférences diverses, petit déj convivial, etc. Si vous désirez y participer, inscrivez vous ici. Le tout est gratuit !
Cet événement est gracieusement hébergé par la Cyber-base de la Cité des Sciences et de l'Industrie.
Zope 3 adapters et widgets
Zope3 et XML avec lxml (suite)
Comme on le sait tous, écrire des expressions python dans des ZPT, c'est mal. J'ai donc voulu aller plus loin afin de pouvoir utiliser les tree xml sans avoir à faire des expressions python.
Avec Zope3, il est possible de rajouter des espaces de nom au expressions TAL (ah, les joies de la component architecture :). J'ai donc implémenté deux espaces de nom supplémentaire me permettant d'accéder aux méthodes find et findall d'un élément xml.
Ma template a donc maintenant cette jolie bouille:
<dl class="lastfm" tal:define="root view/xml">
<dt class="title" tal:content="view/title" />
<dd tal:repeat="track root">
<a tal:attributes=" href track/find:url/text">
<span tal:content="track/find:name/text" />
<span class="small"
tal:content="track/find:artist/text" />
</a>
</dd>
</dl>
Ce qui est tout de même plus agréable à regarder. Ce qui est déconcertant c'est la facilité d'implémentation. Il suffit de quelques lignes de code pour obtenir ce résultat.
Zope3 et XML avec lxml
Ce soir j'ai joué avec Zope3 et lxml. Le résultat est plutôt intéressant. On trouve en combinant les ZPT et lxml une bonne alternative au xslt.
Voyez plutôt. Une petite classe de vue:
# -*- coding: utf-8 -*-
import os
import random
from lxml import etree
from zope.publisher.browser import BrowserView
PREFIX = '/tmp'
def getxml(filename):
"""
return a ElementTree parsed from the file
"""
fd = open(os.path.join(PREFIX,filename))
doc = etree.parse(fd)
fd.close()
return doc.getroot()
class LastfmView(BrowserView):
"""
a view to render the xml
"""
views = ((u'Coup de coeur','recentlovedtracks.xml'),
(u'Ecouté récemment','recenttracks.xml'))
def __call__(self):
self.title, filename = random.choice(self.views)
self.xml = getxml(filename)
return super(LastfmView,self).__call__()
Associée à une petite template toute bête:
<dl class="lastfm" tal:define="root view/xml">
<dt class="title" tal:content="view/title" />
<dd tal:repeat="track root">
<a tal:attributes=" href python: track.find('url').text">
<div tal:content="python: track.find('name').text" />
<div class="small"
tal:content="python: track.find('artist').text" />
</a>
</dd>
</dl>
Et hop, vous obtenez le portlet lastfm de mon site :)
Vous pouvez voir le source complet ici avec le script bash qui dowload les xml.
Log afpy,fon-fr at freenode.org
Solution Linux 2007
Il n'y a qu'une chose agréable à la Défense. Chaque année, pendant 3 jours, c'est la fête du logiciel libre.
Cette année, pour cause de vacances, j'ai tenu le stand de l'AFPy du début à la fin (ou presque, réveil difficile oblige).
Le bilan est plutôt positif pour l'AFPy. Les gens connaissent Python et l'utilise ou se demande si il ne devrait pas. Bah, si bien sûr :)
Le nombre d'entreprises cherchant des coders Python est grandissant. Et pas que dans le domaine Zope/Plone ce qui est plutôt bon signe ! Citons par exemple gandi qui refond son back-orifice dans ce charmant langage. Vincent m'as avoué qu'après avoir acheté un bouquin sur le sujet pour pouvoir contribuer aux trolls, il est tombé amoureux ! La décision était prise quelques jours plus tard: "Les gars, on refait tout en Python !"
Pour ce qui est des événements marquant, on notera la désinfection du stand M$ par la joyeuse équipe du GCU. En effet, "LE MALIN EST PARMIS NOUS !" il fallait remédier à celà. On ne les en remerciera jamais assez !
Voilà voilà, vivement l'année prochaine, je vous le dis ! Je crois que je vais me mettre un crontab pour faire une demande automatique de congés toute les fins janvier ;)
Sortie de LetsPay 1.0
Citation de bluetouff:
MAIS #$@! DE $@#! Y A PAS UN PRODUIT DE COMMERCE SUR PLONE !!
En effet, il existe bien des solutions mais elles sont relativement délicates à mettre en place pour un user lambda.
J'ai donc pris l'inatiative de créér un produit simple pour que les utilisateurs de Plone puisse enfin faire du e-commerce.
Cette solution, c'est LetsPay. Un petit produit qui vous permets d'ajouter des types de contenu dérivés des images et documents classique de Plone. Le visiteur peut ainsi ajouter ces objets dans son caddy puis régler via PayPal.
Enjoy !
Sortie de FlickrAlbum 1.0
Après un long moment de gestation, FlickrAlbum est enfin disponible sur le site de Plone. FlickrAlbum est un produit permetant de faire du miroring de photos stockées sur Flickr. Ainsi, rien n'est stocké dans votre ZODB.
Il permets de créer des albums à partir de set ou de recherche par tag.
FlickrAlbum est entièrement basé sur le javascript. Il dispose d'un diaporama et ne fait aucun reload de page entre les transitions de photos.
Vous pouvez télécharger la version 1.0 ici.
Un bon samedi matin
Owned by Bluetouff
Lorsque bluetouff, le célèbre animateur de Toonux, déclaras dans cet article qu'il entrait en guerre contre Python, je ne l'ai pas vraiment pris au sérieux. J'aurais dû !
En effet, ce soir, en ouvrant ma boite mail je découvre ceci:
Jtè h4CK3R !!! -- blu3t0uff
Il y avait aussi ce screenshot prouvant en effet qu'il avait frappé une fois de plus dans l'univers des sites codé en Python.
Il fallait que ça tombe sur moi !!!
MochiKit, un framework javascript en python ?
Les concepteurs de MochiKit ne le cache pas, ils aiment Python et s'en inspire largement dans la conception de leur framework.
On s'en aperçoit très vite en essayant leur interpréteur. Ecrire du javascript à la façon Python n'as rien de désagréable je trouve... Un petit exemple:
>> carre = function(x) { return x*x }
function (x) { return x * x; }
>> array = [11,35,69]
[11, 35, 69]
>> map(carre,array)
[121, 1225, 4761]
C'est tout de même très pythonique, non ? :)
Bon, certes, il y a des fonctionnalités de Python que l'ont ne retrouvera jamais en javascript. Mais, dans ce cas, on tentes de s'en approcher.
Un autre exemple démontre la façon qu'a MochiKit de reproduire le comportement de la fonction Python repr():
>> // les représentation d'objet ne sont jamais parlante en javascript
>> // avec MochiKit, cela ce passe déjà mieux
>> array = ['python','javascript']
["python", "javascript"]
>> repr(array)
"[\"python\", \"javascript\"]"
>> //
>> // il est possible d'implementer sa propre fonction repr dans une class
>> TestClass = function() {}
function () { }
>> TestClass.prototype = { repr: function() { return 'Je suis un objet' }, }
Je suis un objet
>> test = new TestClass()
Je suis un objet
>> TestClass = function() {}
function () { }
>> TestClass.prototype = { repr: function() { return 'Je suis un objet' }, }
Je suis un objet
>> repr(TestClass)
"function () {\n}"
>> test = new TestClass()
Je suis un objet
>> repr(test)
"Je suis un objet"
Ce sont des exemples parmi tant d'autres. En parcourant l'api de cette librairie, on se rends vite compte que les comparaisons possible avec Python sont nombreuses !