Django, le wsgi et paste: status

Posté le 15/06/2008 sous django python afpy

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 !!!

Posté le 07/06/2008 sous afpy pycon python

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

Posté le 20/03/2008 sous python 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

Posté le 17/10/2007 sous zope python

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

Posté le 03/07/2007 sous 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é

Posté le 07/06/2007 sous python

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 !

Posté le 04/06/2007 sous afpy python

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

Posté le 20/05/2007 sous python afpy

Ca y est ! L'annonce des JPF est parue officiellement sur Linux fr.

Journees Python Francophones - 2 et 3 juin 2007 - Paris

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

Posté le 10/04/2007 sous python afpy

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

Posté le 13/03/2007 sous afpy zope3 python

Épris d'une étonnante motivation ces derniers jours, j'ai pondu deux tutoriels sur Zope 3. Un sur les adapters et un sur les widgets.

Ils sont aussi visible sur le site de l'AFPy, bien sûr !

Zope3 et XML avec lxml (suite)

Posté le 01/03/2007 sous zope3 python

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

Posté le 26/02/2007 sous zope3 python

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

Posté le 25/02/2007 sous geek lafonera wifi afpy python

J'ai fait une petite bidouille pour pouvoir afficher les logs du chan #fon-fr et ceux de l'#AFPy sur le www.

Histoire de conserver une trace des discussions parfois intéressantes qui s'y déroule.

Solution Linux 2007

Posté le 05/02/2007 sous geek unix afpy python

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

Posté le 29/01/2007 sous plone python product

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

Posté le 19/11/2006 sous plone python product
La vue d'une photo dans FlickrAlbum

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

Posté le 04/11/2006 sous python

J'ai fait ma BA du weken aujourd'hui. J'étais désespéré en voyant ce jeune pythoneur perdu dans ses remplacement de chaîne.

Je lui ai donc fait un petit tuto sur le sujet sur le site de l'AFPy.

Il est d'ailleurs aussi visible ici, sur ce site.

Owned by Bluetouff

Posté le 23/10/2006 sous geek python

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 ?

Posté le 27/09/2006 sous python javascript ajax

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 !

En bref, quand on aime le Python, on aime MochiKit :)