Au Plone 3 Paris Sprint en début d'année j'avais bossé avec Kaï et JF sur collective.buildbot. Du coup j'ai profité du Paris Bobün Sprint pour continuer.
J'ai ajouté une template Paste à collective.buildbot:
buildbot@cecilia:~$ paster create --list-template Available templates: basic_package: A basic setuptools-enabled package buildbot: A template for collective.buildbot ...
C'est assez pratique pour monter un buildbot rapidement et pas se prendre la tête en passant des heures à lire la doc.
C'est relativement simple. On utilise la template qui pose quelques questions:
buildbot@cecilia:~$ paster create -t buildbot gawel.org Selected and implied templates: collective.buildbot#buildbot A template for collective.buildbot Variables: egg: gawel.org package: gawelorg project: gawel.org Enter port (the port to use for internal communication) ['9050']: 6050 Enter wport (the port to use for web interface) ['9080']: 6080 Enter vcs (the vcs type. hg, bzr and git are supported.) ['svn']: Enter vcs_url (the url to checkout from) ['']: https://svn.gawel.org/gp.fileupload/trunk Creating template buildbot Creating directory ./gawel.org blabla...
Et voilà... On lance 2/3 commandes histoire d'avoir quand même un truc à faire. Sinon c'est un coup à devenir feignant:
buildbot@cecilia:~$ cd gawel.org/ buildbot@cecilia:~/gawel.org$ python bootstrap.py Downloading http://pypi.python.org/packages/2.4/s/setuptools/setuptools-0.6c9-py2.4.egg blabla... buildbot@cecilia:~/gawel.org$ ./bin/buildout -U Creating directory '/home/buildbot/gawel.org/eggs'. blabla... Generated script '/home/buildbot/gawel.org/bin/master'. blabla... Generated script '/home/buildbot/gawel.org/bin/cecilia'.
On a maintenant deux script qui correspondent au master et au slave. Si vous ignorez ce qu'est un master et un slave, reporté vous à la documentation buildbot.
Y a plus qu'a les lancer:
buildbot@cecilia:~/gawel.org$ ./bin/master start blabla... buildbot@cecilia:~/gawel.org$ ./bin/cecilia start blabla...
Le tout doit pas prendre plus de deux minutes. Et ça marche. Comme quoi des fois un titre à une signification.
Bien sur, on peut affiner la configuration, utiliser Mercurial ou git, etc. Mais il faut lire la doc.
Je viens de releaser gp.fileupload 0.5 qui fournis un ensemble de middlewares WSGI pour gérer l'upload de fichiers.
Et ceci de manière quasi transparente. On colle le gp.fileupload.FileUpload dans sa pile d'application et zou; les formulaire pourvu d'un enctype=multipart/form-data sont attraper au vol par du javascript et une barre de progression s'affiche à la soumission du formulaire. Il y a une petite démo (et une belle doc Sphinx) disponible pour les curieux.
En général, on ouvre une transaction, on attends 3 heures qu'un fichier de 300Mo arrive, on se choppe 40 conflits au vol, et avec un peu de chance, la transaction aboutit.
gp.fileupload.Storage catch les requêtes POST et attends d'avoir lu tout son contenu.
Le contenu de la requête est parsé pour en extraire les fichiers qui sont écrit sur le système de fichier dans un répertoire défini.
La requête originale est récrite en remplaçant le contenu original de chaque fichier trouvé par son chemin sur le système de fichier.
C'est seulement ensuite que l'application à la main, avec un POST qui ne dépassera pas le kilo octet. Ainsi le fichier est déjà stocké et la durée de la transaction minimale.
Une option encore pas trop testée permet de desservir toutes les requêtes non text/html depuis le middleware. Une fois que j'aurais un peu mieux testé ce machin, l'utilisation de ce middleware pourra devenir totalement transparente pour l'application.
Je n'ai rien inventé. C'est un système similaire à tramline. Peut être moins optimal car tramline utilise mod_python et est donc totalement indépendant du processus de l'application. Mais bon, je penses que c'est à la fois plus simple d'utilisation (car cela ne nécessite pas Apache et mod_python, justement) et plus transparent pour l'application.
En ce moment je bosse sur une application en Pylons. J'adore ce petit framework, mais y a un truc que je pouvais pas encadrer, c'est de faire des tests avec des TestCase. Je préfère de loin les doctests.
Me voilà donc partit à la recherche de docs pour pouvoir écrire mes tests comme j'aime les écrire. Pylons utilise nose comme framework de test. Je découvre alors avec joie que nose fournit un plugin pour parcourir les doctests. Chouet !
Le problème, c'est que ce plugin est carrément rudimentaire. En gros, il choppe vos doctest et les initialise ultra basiquement. Comprendre: impossible de passer des options telles que optionflag, setUp ou tearDown. En bref, ça pu. Comment je fais pour initialiser mon framework Pylons pour mes tests moi ? Hein ?
J'ai finalement trouvé une solution en surclassant la classe doctest.DocFileCase afin de faire ce que je veux. Voici le code en question. Il suffit de le placer dans le fichier tests/functional/test_docs.py de votre application Pylons:
# -*- coding: utf-8 -*-
import os
import doctest
import mypylonsapp
from mypylonsapp.tests import *
optionflags = (doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_ONLY_FIRST_FAILURE)
dirname = os.path.join(os.path.dirname(mypylonsapp.__file__), 'docs')
def build_testcase(filename):
name = os.path.splitext(filename)[0]
path = os.path.join(dirname, filename)
class Dummy(doctest.DocFileCase, TestController):
def __init__(self, *args, **kwargs):
# init pylons stuff
TestController.__init__(self, *args, **kwargs)
# get tests from file
parser = doctest.DocTestParser()
doc = open(self.path).read()
test = parser.get_doctest(doc, globals(), name, self.path, 0)
# init doc test case
doctest.DocFileCase.__init__(self, test, optionflags=optionflags)
def setUp(self):
"""init pylons stuff and make app available in doctest
"""
TestController.setUp(self)
test = self._dt_test
test.globs['app'] = self.app
def tearDown(self):
"""cleaning
"""
TestController.tearDown(self)
test = self._dt_test
test.globs.clear()
# generate a new class for the file
return ("Test%s" % name.title(),
type('Test%sClass' % name.title(), (Dummy,), dict(path=path)))
for filename in os.listdir(dirname):
if filename == '.svn':
continue
name, klass = build_testcase(filename)
exec "%s = klass" % name
# clean namespace to avoid test duplication
del build_testcase, filename, name, klass
Vous admirerez la ruse qui est de générer une nouvelle classe pour chaque fichier trouvé dans le répertoire contenant les doctests.
On peut ensuite créer un fichier texte dans docs/ et y écrire des tests du genre:
>>> response = app.get(url_for(controller='main', action="index"))
>>> print response
Response: 200
...
Ce qui est tout de même vachement plus convi qu'un test classique.
Bon nombre de gens utilise python pour faire de petits scripts. Le problème c'est que pour les distribuer, ensuite, c'est pas le top.
Heureusement il y a distutils !!
distutils est un paquet inclus dans les distributions python permettant de créer des paquet python.
Le principe est simple. On englobe un module python dans un paquet contenant un fichier setup.py
Le plus simple est d'utiliser paste pour créer son paquet. Renseignez bien les information demandées. Elles seront visible si vous décidez de distribuer votre paquet par la suite. Donc:
$ easy_install -U PasteScript $ paster create monscript $ cd monscript $ ls monscript/ monscript.egg-info/ setup.cfg setup.py
Ceci nous créer un répertoire monscript contenant un setup.py et un sous répertoire destiné à recevoir le code python.
Nous devons maintenant créer un point d'entrée pour notre script. Pour cela, nous allons modifier monscript/__init__.py pour qu'il ressemble à ça:
def main():
print 'Yeah !'
Ensuite, en modifiant le fichier setup.py, nous pouvons associer ce point d'entrée à un véritable script qui sera installé à l'installation du paquet. Modifiez la section entry_points du setup.py pour qu'il ressemble à quelque chose du du genre:
entry_points="""
# -*- Entry points: -*-
[console_scripts]
mon_super_script = monscript:main
""",
Voilà, le tour est joué. Alors, pourquoi tout cela pour un simple script ? C'est simple. Vous pouvez maintenant aisément le distribuer.
Voici les principales commandes qui vous serons utiles:
créer un tarball:
$ python setup.py sdist
créer un egg:
$ python setup.py bdist_egg
rendre le paquet disponible sur pypi:
$ python setup.py sdist bdist_egg register upload
Un utilisateur lambda pourra ensuite l'installer simplement:
via le tarball:
$ wget http://exemple.com/monscript-0.1.tar.gz $ tar monscript-0.1.tar.gz $ cd monscript $ python setup.py install
via pypi:
$ easy_install -U monscript
Moralité, distutils rends la vie plus facile.
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 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 !!!
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.
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 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 :)
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 !
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.
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 ;)
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.
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.
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.
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 ;)
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 !
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.
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 !!!
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 !