Gawel's blurbhttp://www.gawel.org/weblog/fr/Fri, 26 Sep 2008 19:49:00 -0000Monter un buildbot en 2mn avec collective.buildbothttp://www.gawel.org/weblog/fr/2008/09/collective.buildbot-template<p>Au <a class="reference external" href="http://www.openplans.org/projects/plone-3-paris-sprint/">Plone 3 Paris Sprint</a> en début d'année j'avais bossé avec Kaï et JF sur <a class="reference external" href="http://pypi.python.org/pypi/collective.buildbot">collective.buildbot</a>. Du coup j'ai profité du <a class="reference external" href="http://www.openplans.org/projects/paris-bobun-sprint/project-home">Paris Bobün Sprint</a> pour continuer.</p> <p>J'ai ajouté une template <cite>Paste</cite> à <a class="reference external" href="http://pypi.python.org/pypi/collective.buildbot">collective.buildbot</a>:</p> <pre class="literal-block"> buildbot&#64;cecilia:~$ paster create --list-template Available templates: basic_package: A basic setuptools-enabled package buildbot: A template for collective.buildbot ... </pre> <p>C'est assez pratique pour monter un <a class="reference external" href="http://buildbot.net/trac">buildbot</a> <strong>rapidement</strong> et pas se prendre la tête en passant des heures à lire la doc.</p> <p>C'est relativement simple. On utilise la template qui pose quelques questions:</p> <pre class="literal-block"> buildbot&#64;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... </pre> <p>Et voilà... On lance 2/3 commandes histoire d'avoir quand même un truc à faire. Sinon c'est un coup à devenir feignant:</p> <pre class="literal-block"> buildbot&#64;cecilia:~$ cd gawel.org/ buildbot&#64;cecilia:~/gawel.org$ python bootstrap.py Downloading http://pypi.python.org/packages/2.4/s/setuptools/setuptools-0.6c9-py2.4.egg blabla... buildbot&#64;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'. </pre> <p>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 <a class="reference external" href="http://buildbot.net/trac">buildbot</a>.</p> <p>Y a plus qu'a les lancer:</p> <pre class="literal-block"> buildbot&#64;cecilia:~/gawel.org$ ./bin/master start blabla... buildbot&#64;cecilia:~/gawel.org$ ./bin/cecilia start blabla... </pre> <p>Le tout doit pas prendre plus de deux minutes. Et ça <a class="reference external" href="http://buildbot.gawel.org/waterfall">marche</a>. Comme quoi des fois un titre à une signification.</p> <p>Bien sur, on peut affiner la configuration, utiliser Mercurial ou git, etc. Mais il faut lire la <a class="reference external" href="http://pypi.python.org/pypi/collective.buildbot">doc</a>.</p> <!-- Links --> <!-- - - - - - --> Fri, 26 Sep 2008 19:49:00 -00000collective.buildbot-templateUpload de fichiers et WSGIhttp://www.gawel.org/weblog/fr/2008/08/wsgi-file-upload<p>Je viens de releaser <a class="reference external" href="http://pypi.python.org/pypi/gp.fileupload/">gp.fileupload</a> 0.5 qui fournis un ensemble de middlewares <a class="reference external" href="http://wsgi.org/wsgi/">WSGI</a> pour gérer l'upload de fichiers.</p> <div class="section" id="premi-re-utilit-afficher-une-barre-de-progression"> <h1>Première utilité: afficher une barre de progression</h1> <p>Et ceci de manière quasi transparente. On colle le <cite>gp.fileupload.FileUpload</cite> dans sa pile d'application et zou; les formulaire pourvu d'un <cite>enctype=multipart/form-data</cite> sont attraper au vol par du javascript et une barre de progression s'affiche à la soumission du formulaire. Il y a une petite <a class="reference external" href="http://www.gawel.org/docs/gp.fileupload/demo.html">démo</a> (et une belle doc Sphinx) disponible pour les curieux.</p> </div> <div class="section" id="deuxi-me-utilit-limiter-le-temps-des-transactions"> <h1>Deuxième utilité: limiter le temps des transactions</h1> <p>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.</p> <p><cite>gp.fileupload.Storage</cite> catch les requêtes <cite>POST</cite> et attends d'avoir lu tout son contenu.</p> <p>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.</p> <p>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.</p> <p>C'est seulement ensuite que l'application à la main, avec un <cite>POST</cite> qui ne dépassera pas le kilo octet. Ainsi le fichier est déjà stocké et la durée de la transaction minimale.</p> <p>Une option encore pas trop testée permet de desservir toutes les requêtes non <cite>text/html</cite> 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.</p> <p>Je n'ai rien inventé. C'est un système similaire à <a class="reference external" href="http://pypi.python.org/pypi/tramline">tramline</a>. Peut être moins optimal car <a class="reference external" href="http://pypi.python.org/pypi/tramline">tramline</a> utilise <a class="reference external" href="http://www.modpython.org/">mod_python</a> 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.</p> <!-- Links --> <!-- - - - - - --> </div> Thu, 28 Aug 2008 20:36:00 -00000wsgi-file-uploadFaire du virtualhosting avec zope façon wsgihttp://www.gawel.org/weblog/fr/2008/07/zope-virtualhosting-the-wsgi-way<p>J'en avais ras le bol que <a class="reference external" href="http://wiki.zope.org/zope3/Zope3Wiki">Zope</a> nécessite des url complètement tordues pour faire du virtual hosting. Ça m'empêchais entre autre d'utiliser <a class="reference external" href="http://pythonpaste.org/modules/urlmap.html">Paste#urlmap</a> pour dispatcher certaines url sur d'autres applis que <a class="reference external" href="http://wiki.zope.org/zope3/Zope3Wiki">Zope</a>.</p> <p>Du coup, j'ai tenté un truc tout con: plutôt que d'utiliser les <cite>RewriteRule</cite> d'<a class="reference external" href="http://httpd.apache.org/">Apache</a>, récrire le <cite>PATH_INFO</cite> en englobant l'application <a class="reference external" href="http://wiki.zope.org/zope3/Zope3Wiki">Zope</a> dans une autre. Et ça marche. Fiesta !</p> <p>Voilà donc à quoi ça ressemble. J'utilise <a class="reference external" href="http://pypi.python.org/pypi/zopeproject">zopeproject</a>. J'ai donc modifier le machin qui créer l'application <a class="reference external" href="http://wiki.zope.org/zope3/Zope3Wiki">Zope</a>. A savoir le fichier <cite>startup.py</cite> comme ceci:</p> <div class="highlight"><pre><span class="k">def</span> <span class="nf">application_factory</span><span class="p">(</span><span class="n">global_conf</span><span class="p">,</span> <span class="n">conf</span><span class="o">=</span><span class="s">&#39;zope.conf&#39;</span><span class="p">,</span> <span class="n">vhost</span><span class="o">=</span><span class="s">&#39;www.gawel.org&#39;</span><span class="p">):</span> <span class="n">vhost</span> <span class="o">=</span> <span class="s">&#39;/++vh++http:</span><span class="si">%s</span><span class="s">:80/++&#39;</span> <span class="o">%</span> <span class="n">vhost</span> <span class="n">zopeapp</span> <span class="o">=</span> <span class="n">zope</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">wsgi</span><span class="o">.</span><span class="n">getWSGIApplication</span><span class="p">(</span><span class="n">zope_conf</span><span class="p">)</span> <span class="k">def</span> <span class="nf">zopewrapper</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span> <span class="n">environ</span><span class="p">[</span><span class="s">&#39;PATH_INFO&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">vhost</span> <span class="o">+</span> <span class="n">environ</span><span class="p">[</span><span class="s">&#39;PATH_INFO&#39;</span><span class="p">]</span> <span class="k">return</span> <span class="n">zopeapp</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span> <span class="k">return</span> <span class="n">zopewrapper</span> </pre></div> <p>Et hop, ça roule. L'avantage, en plus d'avoir une url propre en entrée, c'est que vu que je développe aussi derrière <a class="reference external" href="http://httpd.apache.org/">Apache</a>, j'ai juste eu à changer mon fichier <cite>debug.ini</cite> pour prendre en compte mon virtual host de développement.</p> <p>En fait j'ai fais un peu mieux que tout ça, car comme dit au début, le but était d'utiliser <a class="reference external" href="http://pythonpaste.org/modules/urlmap.html">Paste#urlmap</a>. La source de la bidouille en question est <a class="reference external" href="/projects/browser/www.gawel.org/trunk/src/wwwgawelorg/startup.py">ici</a>.</p> <p>Aller, pendant que j'y suis, j'en chiais aussi pas mal pour déterminer vers quel backend rediriger les requêtes dans <a class="reference external" href="http://varnish.projects.linpro.no/">varnish</a>. Tester des <cite>++</cite> dans l'url, ça lui plaisait pas du tout. Vu que j'utilise <a class="reference external" href="http://httpd.apache.org/">Apache</a> devant (surtout pour subversion), j'ai trouvé le truc. Il suffit d'activer le module <cite>headers</cite>:</p> <pre class="literal-block"> # a2enmod headers </pre> <p>Puis rajouter un truc du genre dans votre virtualhost <a class="reference external" href="http://httpd.apache.org/">Apache</a>:</p> <pre class="literal-block"> RequestHeader set VARNISH_BACKEND gawel_org </pre> <p>Vous l'aurez compris, ceci ajoute un header à la requête. Ensuite, dans <a class="reference external" href="http://varnish.projects.linpro.no/">varnish</a>, on test ce header:</p> <pre class="literal-block"> if (req.http.VARNISH_BACKEND ~ &quot;gawel_org&quot;) { set req.backend = gawel_org; } </pre> <p>Et le tour est joué. Il faut bien sur que toutes les requêtes entrantes aient ce header. Pour moi ce n'est pas un problème vu que tout passe par apache.</p> <!-- Links --> <!-- - - - - - --> Fri, 25 Jul 2008 18:39:00 -00000zope-virtualhosting-the-wsgi-waynose doctest plugin suckshttp://www.gawel.org/weblog/fr/2008/07/nose-doctest-plugin-sucks<p>En ce moment je bosse sur une application en <a class="reference external" href="http://pylonshq.com/">Pylons</a>. J'adore ce petit framework, mais y a un truc que je pouvais pas encadrer, c'est de faire des tests avec des <cite>TestCase</cite>. Je préfère de loin les <a class="reference external" href="http://docs.python.org/lib/module-doctest.html">doctests</a>.</p> <p>Me voilà donc partit à la recherche de docs pour pouvoir écrire mes tests comme j'aime les écrire. <a class="reference external" href="http://pylonshq.com/">Pylons</a> utilise <a class="reference external" href="http://pypi.python.org/pypi/nose">nose</a> comme framework de test. Je découvre alors avec joie que <a class="reference external" href="http://pypi.python.org/pypi/nose">nose</a> fournit un plugin pour parcourir les <a class="reference external" href="http://docs.python.org/lib/module-doctest.html">doctests</a>. Chouet !</p> <p>Le problème, c'est que ce plugin est carrément rudimentaire. En gros, il choppe vos <a class="reference external" href="http://docs.python.org/lib/module-doctest.html">doctest</a> et les initialise <strong>ultra</strong> basiquement. Comprendre: impossible de passer des options telles que <cite>optionflag</cite>, <cite>setUp</cite> ou <cite>tearDown</cite>. En bref, ça pu. Comment je fais pour initialiser mon framework <a class="reference external" href="http://pylonshq.com/">Pylons</a> pour mes tests moi ? Hein ?</p> <p>J'ai finalement trouvé une solution en surclassant la classe <cite>doctest.DocFileCase</cite> afin de faire ce que je veux. Voici le code en question. Il suffit de le placer dans le fichier <cite>tests/functional/test_docs.py</cite> de votre application <a class="reference external" href="http://pylonshq.com/">Pylons</a>:</p> <div class="highlight"><pre><span class="c"># -*- coding: utf-8 -*-</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">doctest</span> <span class="kn">import</span> <span class="nn">mypylonsapp</span> <span class="kn">from</span> <span class="nn">mypylonsapp.tests</span> <span class="kn">import</span> <span class="o">*</span> <span class="n">optionflags</span> <span class="o">=</span> <span class="p">(</span><span class="n">doctest</span><span class="o">.</span><span class="n">ELLIPSIS</span> <span class="o">|</span> <span class="n">doctest</span><span class="o">.</span><span class="n">NORMALIZE_WHITESPACE</span> <span class="o">|</span> <span class="n">doctest</span><span class="o">.</span><span class="n">REPORT_ONLY_FIRST_FAILURE</span><span class="p">)</span> <span class="n">dirname</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">mypylonsapp</span><span class="o">.</span><span class="n">__file__</span><span class="p">),</span> <span class="s">&#39;docs&#39;</span><span class="p">)</span> <span class="k">def</span> <span class="nf">build_testcase</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span> <span class="n">name</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">filename</span><span class="p">)[</span><span class="mf">0</span><span class="p">]</span> <span class="n">path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dirname</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span> <span class="k">class</span> <span class="nc">Dummy</span><span class="p">(</span><span class="n">doctest</span><span class="o">.</span><span class="n">DocFileCase</span><span class="p">,</span> <span class="n">TestController</span><span class="p">):</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> <span class="c"># init pylons stuff</span> <span class="n">TestController</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="c"># get tests from file</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">doctest</span><span class="o">.</span><span class="n">DocTestParser</span><span class="p">()</span> <span class="n">doc</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span> <span class="n">test</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">get_doctest</span><span class="p">(</span><span class="n">doc</span><span class="p">,</span> <span class="nb">globals</span><span class="p">(),</span> <span class="n">name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">,</span> <span class="mf">0</span><span class="p">)</span> <span class="c"># init doc test case</span> <span class="n">doctest</span><span class="o">.</span><span class="n">DocFileCase</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">test</span><span class="p">,</span> <span class="n">optionflags</span><span class="o">=</span><span class="n">optionflags</span><span class="p">)</span> <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;init pylons stuff and make app available in doctest</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">TestController</span><span class="o">.</span><span class="n">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="n">test</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dt_test</span> <span class="n">test</span><span class="o">.</span><span class="n">globs</span><span class="p">[</span><span class="s">&#39;app&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">app</span> <span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;cleaning</span> <span class="sd"> &quot;&quot;&quot;</span> <span class="n">TestController</span><span class="o">.</span><span class="n">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="n">test</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_dt_test</span> <span class="n">test</span><span class="o">.</span><span class="n">globs</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span> <span class="c"># generate a new class for the file</span> <span class="k">return</span> <span class="p">(</span><span class="s">&quot;Test</span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="n">name</span><span class="o">.</span><span class="n">title</span><span class="p">(),</span> <span class="nb">type</span><span class="p">(</span><span class="s">&#39;Test</span><span class="si">%s</span><span class="s">Class&#39;</span> <span class="o">%</span> <span class="n">name</span><span class="o">.</span><span class="n">title</span><span class="p">(),</span> <span class="p">(</span><span class="n">Dummy</span><span class="p">,),</span> <span class="nb">dict</span><span class="p">(</span><span class="n">path</span><span class="o">=</span><span class="n">path</span><span class="p">)))</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">dirname</span><span class="p">):</span> <span class="k">if</span> <span class="n">filename</span> <span class="o">==</span> <span class="s">&#39;.svn&#39;</span><span class="p">:</span> <span class="k">continue</span> <span class="n">name</span><span class="p">,</span> <span class="n">klass</span> <span class="o">=</span> <span class="n">build_testcase</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">exec</span> <span class="s">&quot;</span><span class="si">%s</span><span class="s"> = klass&quot;</span> <span class="o">%</span> <span class="n">name</span> <span class="c"># clean namespace to avoid test duplication</span> <span class="k">del</span> <span class="n">build_testcase</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">klass</span> </pre></div> <p>Vous admirerez la ruse qui est de générer une nouvelle classe pour chaque fichier trouvé dans le répertoire contenant les <cite>doctests</cite>.</p> <p>On peut ensuite créer un fichier texte dans <cite>docs/</cite> et y écrire des tests du genre:</p> <!-- Test code >>> def url_for(**kwargs): pass >>> class App(object): ... def get(self, *args): ... return """Response: 200 ... ...""" >>> app = App() --> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">response</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="n">controller</span><span class="o">=</span><span class="s">&#39;main&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s">&quot;index&quot;</span><span class="p">))</span> <span class="o">&gt;&gt;&gt;</span> <span class="k">print</span> <span class="n">response</span> <span class="n">Response</span><span class="p">:</span> <span class="mf">200</span> <span class="o">...</span> </pre></div> <p>Ce qui est tout de même vachement plus convi qu'un test classique.</p> <!-- Links --> <!-- - - - - - --> Thu, 17 Jul 2008 15:00:00 -00000nose-doctest-plugin-sucksPackager ses scripts Pythonhttp://www.gawel.org/weblog/fr/2008/07/packager-ses-scripts-python<p>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.</p> <p>Heureusement il y a <a class="reference external" href="http://docs.python.org/lib/module-distutils.html">distutils</a> !!</p> <p><a class="reference external" href="http://docs.python.org/lib/module-distutils.html">distutils</a> est un paquet inclus dans les distributions <a class="reference external" href="http://python.org">python</a> permettant de créer des paquet <a class="reference external" href="http://python.org">python</a>.</p> <p>Le principe est simple. On englobe un module <a class="reference external" href="http://python.org">python</a> dans un paquet contenant un fichier <cite>setup.py</cite></p> <p>Le plus simple est d'utiliser <a class="reference external" href="http://pythonpaste.org/">paste</a> 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:</p> <pre class="literal-block"> $ easy_install -U PasteScript $ paster create monscript $ cd monscript $ ls monscript/ monscript.egg-info/ setup.cfg setup.py </pre> <p>Ceci nous créer un répertoire <cite>monscript</cite> contenant un <cite>setup.py</cite> et un sous répertoire destiné à recevoir le code <a class="reference external" href="http://python.org">python</a>.</p> <p>Nous devons maintenant créer un point d'entrée pour notre script. Pour cela, nous allons modifier <cite>monscript/__init__.py</cite> pour qu'il ressemble à ça:</p> <div class="highlight"><pre><span class="k">def</span> <span class="nf">main</span><span class="p">():</span> <span class="k">print</span> <span class="s">&#39;Yeah !&#39;</span> </pre></div> <p>Ensuite, en modifiant le fichier <cite>setup.py</cite>, nous pouvons associer ce point d'entrée à un véritable script qui sera installé à l'installation du paquet. Modifiez la section <cite>entry_points</cite> du <cite>setup.py</cite> pour qu'il ressemble à quelque chose du du genre:</p> <div class="highlight"><pre><span class="n">entry_points</span><span class="o">=</span><span class="s">&quot;&quot;&quot;</span> <span class="s"># -*- Entry points: -*-</span> <span class="s">[console_scripts]</span> <span class="s">mon_super_script = monscript:main</span> <span class="s">&quot;&quot;&quot;</span><span class="p">,</span> </pre></div> <p>Voilà, le tour est joué. Alors, pourquoi tout cela pour un simple script ? C'est simple. Vous pouvez maintenant aisément le distribuer.</p> <p>Voici les principales commandes qui vous serons utiles:</p> <ul> <li><p class="first">créer un tarball:</p> <pre class="literal-block"> $ python setup.py sdist </pre> </li> <li><p class="first">créer un egg:</p> <pre class="literal-block"> $ python setup.py bdist_egg </pre> </li> <li><p class="first">rendre le paquet disponible sur pypi:</p> <pre class="literal-block"> $ python setup.py sdist bdist_egg register upload </pre> </li> </ul> <p>Un utilisateur lambda pourra ensuite l'installer simplement:</p> <ul> <li><p class="first">via le tarball:</p> <pre class="literal-block"> $ wget http://exemple.com/monscript-0.1.tar.gz $ tar monscript-0.1.tar.gz $ cd monscript $ python setup.py install </pre> </li> <li><p class="first">via pypi:</p> <pre class="literal-block"> $ easy_install -U monscript </pre> </li> </ul> <p>Moralité, <a class="reference external" href="http://docs.python.org/lib/module-distutils.html">distutils</a> rends la vie plus facile.</p> <!-- Links --> <!-- - - - - - --> Tue, 08 Jul 2008 19:08:00 -00000packager-ses-scripts-pythonDjango, le wsgi et paste: statushttp://www.gawel.org/weblog/fr/2008/06/django-le-wsgi-et-paste<p>Cette après-midi, j'ai retrouvé mon collègue de geekerie, <a class="reference external" href="http://oliviergrisel.name/">Olivier</a>. Pendant que lui tentait de faire apprendre à rêver à son cher <a class="reference external" href="http://www.dailymotion.com/video/x27vnx_dr-gumby-ia-avec-scientific-python">Dr Gumby</a>, je me suis atteler à faire fonctionner <a class="reference external" href="http://www.djangoproject.com/">django</a> avec <a class="reference external" href="http://pythonpaste.org/">paste</a>. Pas une mince affaire à priori. J'avais vu une tentative pour faire un <a class="reference external" href="http://pythonpaste.org/djangopaste/">paste.django</a> qui semblait évoluer péniblement. Mais bon, je suis plein de courage.</p> <p>J'ai finalement été assez surpris. Déjà, on trouve dans le trunk de <a class="reference external" href="http://www.djangoproject.com/">django</a> un <a class="reference external" href="http://code.djangoproject.com/browser/django/trunk/django/core/handlers/wsgi.py">WSGIHandler</a>. C'est la fête ! Il ne reste plus qu'as l'associer à <a class="reference external" href="http://pythonpaste.org/">paste</a>. Bon, je me lance.</p> <p>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 <a class="reference external" href="http://www.djangoproject.com/">django</a>... Soit, un simple <a class="reference external" href="https://svn.afpy.org/misc/jobs.afpy.org/trunk/setup.py">setup.py</a> et mon application peut-être intégrée dans mon <a class="reference external" href="https://svn.afpy.org/misc/wsgi.afpy.org/">buidlout</a>.</p> <p>Ensuite, première tentative pour utiliser ce précieux handler: créer une usine qui renvois le <a class="reference external" href="http://code.djangoproject.com/browser/django/trunk/django/core/handlers/wsgi.py">WSGIHandler</a> tel quel. Échec. Forcément, <a class="reference external" href="http://www.djangoproject.com/">django</a> attends son fameux module de <cite>settings</cite>. Soit, avec un peu de chance, en le plaçant dans l'environ utilisé par l'usine, on vas y arriver. Re-échec.</p> <p>Pourquoi ? Tout bêtement parce que <a class="reference external" href="http://www.djangoproject.com/">django</a> 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.</p> <p>Second problème, les url générées par l'application sont toutes erronées. En effet, un <a class="reference external" href="http://code.djangoproject.com/ticket/1516">bug</a> fait que <a class="reference external" href="http://www.djangoproject.com/">django</a> ne tiens pas compte de la variable <cite>SCRIPT_NAME</cite>. C'est bien dommage, mais un bon vieux hack permet de fixer le problème relativement facilement. Il suffit d'initialiser le <cite>PATH_INFO</cite> en le précédent du <cite>SCRIPT_NAME</cite> et le tour est joué.</p> <p>Voici le résultat:</p> <div class="highlight"><pre><span class="c"># -*- coding: utf-8 -*-</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">from</span> <span class="nn">paste.deploy.config</span> <span class="kn">import</span> <span class="n">ConfigMiddleware</span> <span class="kn">from</span> <span class="nn">django.core.handlers.wsgi</span> <span class="kn">import</span> <span class="n">WSGIHandler</span> <span class="k">def</span> <span class="nf">factory</span><span class="p">(</span><span class="n">global_config</span><span class="p">,</span> <span class="o">**</span><span class="n">local_config</span><span class="p">):</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">&#39;DJANGO_SETTINGS_MODULE&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s">&#39;jobsafpyorg.settings&#39;</span> <span class="n">conf</span> <span class="o">=</span> <span class="n">global_config</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span> <span class="n">conf</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="o">**</span><span class="n">local_config</span><span class="p">)</span> <span class="n">app</span> <span class="o">=</span> <span class="n">ConfigMiddleware</span><span class="p">(</span><span class="n">WSGIHandler</span><span class="p">(),</span> <span class="n">conf</span><span class="p">)</span> <span class="k">def</span> <span class="nf">django_app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">):</span> <span class="n">environ</span><span class="p">[</span><span class="s">&#39;PATH_INFO&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">environ</span><span class="p">[</span><span class="s">&#39;SCRIPT_NAME&#39;</span><span class="p">]</span> <span class="o">+</span> <span class="n">environ</span><span class="p">[</span><span class="s">&#39;PATH_INFO&#39;</span><span class="p">]</span> <span class="k">return</span> <span class="n">app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span> <span class="k">return</span> <span class="n">django_app</span> </pre></div> <p>On peut ensuite se servir de cette usine comme point d'entrée et l'utiliser dans un fichier de configuration <a class="reference external" href="http://pythonpaste.org/">paste</a>. Notez que le <cite>ConfigMiddleware</cite> est indipensable. Le handler de <a class="reference external" href="http://www.djangoproject.com/">django</a> semble renvoyer des choses peu ordinaire de type <cite>class</cite>, parfois. Le <cite>middleware</cite> permet de corriger tout cela.</p> <p>Problème, si on veut plusieurs instance <a class="reference external" href="http://www.djangoproject.com/">django</a> dans un seul environnement wsgi, ça devient problématique. Comment initialiser plusieurs configuration avec une seul clé du dictionnaire environ... Je me le demande.</p> <p>Cela dit, c'est un problème auquel je vais être confronter car j'aimerais fair cohabiter les deux applications <a class="reference external" href="http://www.djangoproject.com/">django</a> qui ont été faites pout l'<a class="reference external" href="http://www.afpy.org">AFPy</a>. Donc, la suite au prochaine épisode.</p> <!-- Links --> <!-- - - - - - --> Sun, 15 Jun 2008 21:44:00 -00000django-le-wsgi-et-pasteIl y a de la vie après PyCON !!!http://www.gawel.org/weblog/fr/2008/06/la-vie-apres-pycon<p>Il y a peu, c'était <a class="reference external" href="http://fr.pycon.org">Pycon FR</a>, la récompense annuelle des efforts fournit dans cette belle association qu'est l'<a class="reference external" href="http://www.afpy.org">AFPy</a>. Grande réussite de mon point de vue. La richesse et la diversité des conférences s'améliore, le public est plus nombreux. <a class="reference external" href="http://www.python.org">Python</a> a de beaux jours devant lui.</p> <p>J'y ai fait une <a class="reference external" href="http://fr.pycon.org/programme/wsgi-par-lexemple-avec-paste">conférence</a> sur le <a class="reference external" href="http://www.wsgi.org/FrontPage">WSGI</a>. Ç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.</p> <p>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'<a class="reference external" href="http://www.afpy.org">AFPy</a>. Une petite application en <a class="reference external" href="http://pylonshq.com/">Pylons</a> qui permet d'administrer les utilisateurs de notre annuaire ldap et les inscription dans le <a class="reference external" href="http://www.zope.org">Zope</a> et les <a class="reference external" href="http://lists.afpy.org/">listes de diffusions</a>. Elle est <a class="reference external" href="https://svn.afpy.org/misc/members.afpy.org/">ici</a>, pour ceux que ça intéresse. J'avais aussi fait une petite application compatible <a class="reference external" href="http://www.wsgi.org/FrontPage">WSGI</a> pour pouvoir afficher les photos avec un tag <a class="reference external" href="http://www.afpy.org">AFPy</a> 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.</p> <p>Le paquet <a class="reference external" href="https://svn.afpy.org/misc/wsgi.afpy.org/">wsgi.afpy.org</a> était né. Ça donne un petit fichier de <a class="reference external" href="https://svn.afpy.org/misc/wsgi.afpy.org/deploy.ini">configuration</a> sympa qui dessers ces trois applications via un urlmap. Bien sur, le tout est géré avec <a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a>, 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:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">afpy.core</span> <span class="kn">import</span> <span class="n">ldap</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">user</span> <span class="o">=</span> <span class="n">ldap</span><span class="o">.</span><span class="n">getUser</span><span class="p">(</span><span class="s">&#39;gawel&#39;</span><span class="p">)</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">user</span> <span class="o">&lt;</span><span class="n">User</span> <span class="n">dn</span><span class="p">:</span><span class="n">uid</span><span class="o">=</span><span class="n">gawel</span><span class="p">,</span><span class="n">ou</span><span class="o">=</span><span class="n">members</span><span class="p">,</span><span class="n">dc</span><span class="o">=</span><span class="n">afpy</span><span class="p">,</span><span class="n">dc</span><span class="o">=</span><span class="n">org</span><span class="o">&gt;</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">user</span><span class="o">.</span><span class="n">email</span> <span class="s">&#39;gawel@afpy.org&#39;</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">ldap</span><span class="o">.</span><span class="n">getMembersOf</span><span class="p">(</span><span class="s">&#39;bureau&#39;</span><span class="p">)</span> <span class="p">[</span><span class="s">u&#39;tarek&#39;</span><span class="p">,</span> <span class="s">u&#39;ogrisel&#39;</span><span class="p">,</span> <span class="s">u&#39;gwen&#39;</span><span class="p">,</span> <span class="s">u&#39;gawel&#39;</span><span class="p">,</span> <span class="s">u&#39;jpcw2002&#39;</span><span class="p">,</span> <span class="s">u&#39;ccomb&#39;</span><span class="p">]</span> </pre></div> <p>C'est quand même super fun !!!</p> <!-- Links --> <!-- - - - - - --> Sat, 07 Jun 2008 15:42:00 -00000la-vie-apres-pyconsvn: le futur du CMS !!!http://www.gawel.org/weblog/fr/2008/04/svn-le-futur-du-cms<p>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 <a class="reference external" href="http://vim.sf.net">éditeur</a> et du <a class="reference external" href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>.</p> <p>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 <a class="reference external" href="http://wiki.zope.org/zope3/Zope3Wiki">Zope3</a> 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 <a class="reference external" href="https://svn.gawel.org/gp.svnfolder/trunk">gp.svnfolder</a>, un package <a class="reference external" href="http://wiki.zope.org/zope3/Zope3Wiki">Zope3</a> permettant de publier un répertoire svn contenant des documents en <a class="reference external" href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>.</p> <p>Voyons plutôt. Il nous faut une repository valide:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">os</span> <span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">os.path</span> <span class="kn">import</span> <span class="n">split</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">curdir</span> <span class="o">=</span> <span class="n">split</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())[</span><span class="mf">0</span><span class="p">]</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">repos</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">curdir</span><span class="p">,</span> <span class="s">&#39;tests&#39;</span><span class="p">,</span> <span class="s">&#39;rstfolder&#39;</span><span class="p">)</span> </pre></div> <p>Avec ça, on peut instancier un dossier svn:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">gp.svnfolder.folder</span> <span class="kn">import</span> <span class="n">SVNFolder</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">folder</span> <span class="o">=</span> <span class="n">SVNFolder</span><span class="p">()</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">folder</span><span class="o">.</span><span class="n">__name__</span> <span class="o">=</span> <span class="s">&#39;blog&#39;</span> <span class="o">&gt;&gt;&gt;</span> <span class="n">folder</span><span class="o">.</span><span class="n">path</span> <span class="o">=</span> <span class="s">u&#39;file://</span><span class="si">%s</span><span class="s">&#39;</span> <span class="o">%</span> <span class="n">repos</span> </pre></div> <p>Et en voir le contenu:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">sorted</span><span class="p">(</span><span class="n">folder</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span> <span class="p">[</span><span class="s">u&#39;doc.rst&#39;</span><span class="p">,</span> <span class="s">u&#39;doc2.rst&#39;</span><span class="p">]</span> </pre></div> <p>Et accéder aux fichiers:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="nb">file</span> <span class="o">=</span> <span class="n">folder</span><span class="p">[</span><span class="s">&#39;doc.rst&#39;</span><span class="p">]</span> <span class="o">&gt;&gt;&gt;</span> <span class="k">print</span> <span class="nb">file</span><span class="o">.</span><span class="n">data</span> <span class="n">Document</span> <span class="mf">1</span> <span class="o">==========</span> <span class="o">&lt;</span><span class="n">BLANKLINE</span><span class="o">&gt;</span> </pre></div> <p>Ensuite il suffit d'utiliser les vues fournies pour rendre ces fichiers en html.</p> <p>Voila. Je penses qu'avec un peu de motivation je pourrais facilement le rendre compatible avec <a class="reference external" href="http://fr.wikipedia.org/wiki/Mercurial">Mercurial</a> et <a class="reference external" href="http://fr.wikipedia.org/wiki/Bazaar_(logiciel)">Bazaar</a> mais ça suffit à faire mon bonheur :)</p> <!-- Links --> <!-- - - - - - --> Mon, 31 Mar 2008 22:37:00 -00000svn-le-futur-du-cmsMonter un environnement python avec zc.buildouthttp://www.gawel.org/weblog/fr/2008/03/environnement-python-avec-zc.buildout<p><a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a> est un utilitaire créer par Jim Fulton, le papa de Zope. L'étendue des capacités de <a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a> est énorme et mériterais un livre entier.</p> <p>Cependant, voici un petit exemple pour se monter un environnement de paquets/binaire python avec un simple petit fichier de configuration.</p> <p>Tout d'abord, nous allons nous créer un <a class="reference external" href="http://pypi.python.org/pypi/virtualenv">virtualenv</a> afin de ne <em>vraiment</em> pas polluer notre distribution:</p> <pre class="literal-block"> gawel&#64;Stacy:~$ virtualenv --no-site-packages py New python executable in py/bin/python Installing setuptools.............done. </pre> <p>Si vous n'avez pas <a class="reference external" href="http://pypi.python.org/pypi/virtualenv">virtualenv</a>, installez <a class="reference external" href="http://pypi.python.org/pypi/setuptools">setuptools</a> et <a class="reference external" href="http://pypi.python.org/pypi/virtualenv">virtualenv</a> 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.</p> <p>On va ensuite installer <a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a> dans notre environnement:</p> <pre class="literal-block"> gawel&#64;Stacy:~$ cd py gawel&#64;Stacy:~/py$ ./bin/easy_install zc.buildout Searching for zc.buildout ... Finished processing dependencies for zc.buildout </pre> <p>Parfait, maintenant, initialisons l'environnement de <a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a>:</p> <pre class="literal-block"> gawel&#64;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'. </pre> <p>Puis on édite le fichier <cite>buildout.cfg</cite> pour qu'il ressemble à ça:</p> <pre class="literal-block"> gawel&#64;Stacy:~/py$ cat buildout.cfg [buildout] parts = eggs [eggs] recipe = zc.recipe.egg eggs= ipython i18ndude ZopeSkel IngeniSkel iw.releaser </pre> <p>Vous pouvez mettre absolument n'importe quel <cite>egg</cite> dans l'option <cite>eggs</cite>. La seule contrainte est que ceux-ci se trouvent sur <a class="reference external" href="http://pypi.python.org/">pypi</a>.</p> <p>On lance la construction du <cite>buildout</cite>:</p> <pre class="literal-block"> gawel&#64;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'. </pre> <p>J'ai maintenant tout mes binaires dans le dossier <cite>bin/</cite> de l'environnement:</p> <pre class="literal-block"> gawel&#64;Stacy:~/py$ ls bin/ activate easy_install-2.4* project_deploy* pycolor* buildout* i18ndude* project_diff* python* easy_install* ipython* project_release* python2.4&#64; </pre> <p>Le must, c'est que je peux importer chacun de ses paquets dans mon <a class="reference external" href="http://pypi.python.org/pypi/ipython">ipython</a>:</p> <pre class="literal-block"> gawel&#64;Stacy:~/py$ ./bin/ipython In [1]: import i18ndude In [2]: dir i18ndude ------&gt; dir(i18ndude) Out[2]: ['__builtins__', '__doc__', '__file__', '__name__', '__path__'] </pre> <p>Vous pouvez bien sur ajouter des <cite>eggs</cite> dans votre <cite>buildout.cfg</cite> puis relancer <cite>./bin/buildout</cite> pour qu'il soit prit en compte.</p> <p>Ceci est une simple introduction à <a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a>. On peut faire bien mieux, je sais. Mais ça deviendrais compliquer de tout expliquer dans un simple post :)</p> <p>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 <a class="reference external" href="http://pypi.python.org/pypi/zc.buildout">zc.buildout</a>.</p> Thu, 20 Mar 2008 11:09:00 -00000environnement-python-avec-zc.buildoutTraduire un package Plone 3http://www.gawel.org/weblog/fr/2008/02/i18n-et-egg-plone3<p>Je n'ai pas creusé plus que ça, mais apparemment il y a de beaux conflits entre <tt class="docutils literal"><span class="pre">zope.i18n</span></tt> et <tt class="docutils literal"><span class="pre">PlacelessTranslationService</span></tt>. Du coup, tant que l'on veut ajouter des traductions à un domaine non existant, se servir de <tt class="docutils literal"><span class="pre">zope.i18n</span></tt> ne pose pas de problème. Par contre, si l'on désire ajouter des traductions au domaine <tt class="docutils literal"><span class="pre">plone</span></tt> par exemple, ça ce complique.</p> <p>Le seul moyen que j'ai pu trouver après quelques heures d'introspection est d'enregistrer un <tt class="docutils literal"><span class="pre">.po</span></tt> qui se trouve hors du répertoire <tt class="docutils literal"><span class="pre">locales</span></tt> (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 <tt class="docutils literal"><span class="pre">__init__.py</span></tt>:</p> <div class="highlight"><pre><span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">context</span><span class="p">):</span> <span class="sd">&quot;&quot;&quot;Initializer called when used as a Zope 2 product.&quot;&quot;&quot;</span> <span class="n">context</span><span class="o">.</span><span class="n">_ProductContext__app</span><span class="o">.</span><span class="n">Control_Panel</span><span class="o">.</span><span class="n">TranslationService</span><span class="o">.</span><span class="n">_load_i18n_dir</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">)</span><span class="o">+</span><span class="s">&#39;/i18n&#39;</span><span class="p">)</span> </pre></div> <p>Alors, oui, c'est dégueulasse, mais ça marche.</p> Fri, 15 Feb 2008 10:50:00 -00000i18n-et-egg-plone3Utiliser zope.sendmail dans Zope 2http://www.gawel.org/weblog/fr/2008/02/zope-sendmail-in-zope2<p>Depuis que les sources de <tt class="docutils literal"><span class="pre">Zope3</span></tt> sont incluse dans <tt class="docutils literal"><span class="pre">Zope2</span></tt>, il est vraiment dommage de ne pas utiliser au maximum ces possibilités.</p> <p>Entre autre, il existe un package assez peu connu <tt class="docutils literal"><span class="pre">zope.sendmail</span></tt> qui permet d'envoyer des mails de façon asynchrone. Les mails sont stockés dans une <tt class="docutils literal"><span class="pre">mailbox</span></tt> puis envoyés par un thread séparé de <tt class="docutils literal"><span class="pre">Zope</span></tt>.</p> <p>L'inconvénient de ce package est que tout est configurable, mais uniquement via du <tt class="docutils literal"><span class="pre">zcml</span></tt>. Le <tt class="docutils literal"><span class="pre">TODO</span></tt> indique que, oui, ça serait bien qu'un jour cela soit dans le <tt class="docutils literal"><span class="pre">zope.conf</span></tt>, mais en attendant...</p> <p>Finalement, depuis qu'on a tendance à utiliser <tt class="docutils literal"><span class="pre">buildout</span></tt> (parce que quand même, ça arrache) il était super simple de créer une petite recette qui installe un bout de <tt class="docutils literal"><span class="pre">zcml</span></tt> dans le répertoire qui vas bien.</p> <p>Le résultat est <a class="reference external" href="http://pypi.python.org/pypi/iw.recipe.sendmail">iw.recipe.sendmail</a>. Simple, et efficace.</p> <p>Un autre petit paquet, <a class="reference external" href="http://pypi.python.org/pypi/iw.mailhost">iw.mailhost</a>, permet de patcher les produits <tt class="docutils literal"><span class="pre">MailHost</span></tt> et <tt class="docutils literal"><span class="pre">SecureMailHost</span></tt> afin d'utiliser <tt class="docutils literal"><span class="pre">zope.sendmail</span></tt> en lieu et place de <tt class="docutils literal"><span class="pre">smtplib</span></tt>. Il suffit de le placer dans un répertoire inclus dans le <tt class="docutils literal"><span class="pre">sys.path</span></tt> pour qu'il soit importé par le <tt class="docutils literal"><span class="pre">zcml</span></tt> généré par <a class="reference external" href="http://pypi.python.org/pypi/iw.recipe.sendmail">iw.recipe.sendmail</a></p> <p>A noter que ceci a été éprouvé sous OSX, Linux et Windows. Enjoy !</p> Mon, 11 Feb 2008 16:50:00 -00000zope-sendmail-in-zope2Utiliser zope avec virtualenvhttp://www.gawel.org/weblog/fr/2007/10/utiliser-zope-avec-virtualenv<p><a class="reference external" href="http://pypi.python.org/pypi/virtualenv">Virtualenv</a> 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.</p> <p>Il est possible de l'utiliser avec <a class="reference external" href="http://www.zope.org">Zope</a>. C'est même très simple...</p> <p>On install <a class="reference external" href="http://pypi.python.org/pypi/virtualenv">virtualenv</a> avec <a class="reference external" href="http://peak.telecommunity.com/DevCenter/EasyInstall">easy_install</a>:</p> <pre class="literal-block"> ~$ easy_install virtualenv </pre> <p>On s'extrait un <a class="reference external" href="http://www.zope.org">Zope</a>:</p> <pre class="literal-block"> ~$ tar xvzf Zope-2.X.X-final.tgz ~$ mv Zope-X.X.X-final myenv </pre> <p>On créer un <a class="reference external" href="http://pypi.python.org/pypi/virtualenv">virtualenv</a> dans l'arborescence du <a class="reference external" href="http://www.zope.org">Zope</a>:</p> <pre class="literal-block"> ~$ virtualenv myenv New python executable in myenv/bin/python Installing setuptools......................done. ~$ source bin/activate (myenv)$ which python /home/gawel/myenv/bin/python </pre> <p>On utilise le python de l'environnement pour installer le <a class="reference external" href="http://www.zope.org">Zope</a>:</p> <pre class="literal-block"> (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. </pre> <p>On le compile:</p> <pre class="literal-block"> (myenv)~/myenv% make &amp;&amp; make inplace </pre> <p>On peut ensuite créer des instances comme d'habitude. Ces instances utiliserons notre <a class="reference external" href="http://pypi.python.org/pypi/virtualenv">virtualenv</a>.</p> <!-- Links --> <!-- - - - - - --> Wed, 17 Oct 2007 08:50:00 -00000utiliser-zope-avec-virtualenvLes exceptions pythonhttp://www.gawel.org/weblog/fr/2007/07/python-exceptions<p>Les <a class="reference external" href="http://docs.python.org/lib/module-exceptions.html">exceptions</a> python sont toutes simple. Vous pouvez facilement créer les votre:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="k">class</span> <span class="nc">MonException</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span> <span class="o">...</span> <span class="s">&quot;&quot;&quot; Une exception &quot;&quot;&quot;</span> </pre></div> <p>Puis les propager:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="k">raise</span> <span class="n">MonException</span><span class="p">(</span><span class="s">&#39;Bouh !&#39;</span><span class="p">)</span> <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span> <span class="o">...</span> <span class="n">MonException</span><span class="p">:</span> <span class="n">Bouh</span> <span class="err">!</span> </pre></div> <p>Ca, c'est la base. Mais il y a des trucs plus rigolo à faire. Par exemple garder la trace d'une suite d'exceptions:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="k">class</span> <span class="nc">MultiException</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span> <span class="o">...</span> <span class="s">&quot;&quot;&quot; une exception contstitué de plusieurs autre &quot;&quot;&quot;</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="o">...</span> <span class="ne">Exception</span><span class="o">.</span><span class="n">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">...</span> <span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">[]</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">__nonzero__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="o">...</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="ow">and</span> <span class="bp">True</span> <span class="ow">or</span> <span class="bp">False</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="o">...</span> <span class="k">return</span> <span class="s">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">&#39;</span><span class="si">%s</span><span class="s">: </span><span class="si">%s</span><span class="s">&#39;</span> <span class="o">%</span> <span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">__class__</span><span class="o">.</span><span class="n">__name__</span><span class="p">,</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">])</span> </pre></div> <p>Et voila ce que cela donne à l'utilisation. On fait une boucle dans laquelle une ou plusieurs exceptions peuvent se produire:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">exceptions</span> <span class="o">=</span> <span class="n">MultiException</span><span class="p">()</span> <span class="o">&gt;&gt;&gt;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">[</span><span class="mf">1</span><span class="p">,</span> <span class="mf">3</span><span class="p">,</span> <span class="mf">0</span><span class="p">,</span> <span class="mf">7</span><span class="p">,</span> <span class="mf">1</span><span class="p">,</span> <span class="mf">0</span><span class="p">]:</span> <span class="o">...</span> <span class="k">try</span><span class="p">:</span> <span class="o">...</span> <span class="n">a</span> <span class="o">=</span> <span class="mf">2</span><span class="o">/</span><span class="n">i</span> <span class="o">...</span> <span class="k">except</span> <span class="ne">Exception</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span> <span class="o">...</span> <span class="n">exceptions</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> </pre></div> <p>Puis on propage notre exception si elle n'est pas vide:</p> <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="k">if</span> <span class="n">exceptions</span><span class="p">:</span> <span class="o">...</span> <span class="k">raise</span> <span class="n">exceptions</span> <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span> <span class="o">...</span> <span class="n">MultiException</span><span class="p">:</span> <span class="ne">ZeroDivisionError</span><span class="p">:</span> <span class="n">integer</span> <span class="n">division</span> <span class="ow">or</span> <span class="n">modulo</span> <span class="n">by</span> <span class="n">zero</span><span class="p">,</span> <span class="ne">ZeroDivisionError</span><span class="p">:</span> <span class="n">integer</span> <span class="n">division</span> <span class="ow">or</span> <span class="n">modulo</span> <span class="n">by</span> <span class="n">zero</span> </pre></div> <p>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.</p> <p>En fait, je n'ai rien inventé. C'est le principe utilisé par exemple dans <a class="reference external" href="http://zope.org">Zope</a> pour gérer la validation de formulaire (<a class="reference external" href="http://svn.zope.org/*checkout*/zope.app.form/trunk/src/zope/app/form/interfaces.py?content-type=text%2Fplain">zope.app.form</a>). 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 :)</p> <!-- Links --> <!-- - - - - - --> Mon, 02 Jul 2007 22:30:00 -00000python-exceptionsgawel.org testé et approuvéhttp://www.gawel.org/weblog/fr/2007/06/gawel.org-teste-et-approuve<p>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.</p> <p>Bon, ça, c'était bien, mais j'y ai trouvé un autre avantage. En suivant les diverses conférences sur les tests unitaire aux <a class="reference external" href="http://journees.afpy.org">JFP</a> je me suis dis: &quot;Hey, t'es con, tu pourrais tester tout ton site !&quot;</p> <p>En effet, il m'as suffit d'écrire ce petit bout de code:</p> <div class="highlight"><pre><span class="c">#!/usr/bin/python</span> <span class="c"># -*- coding: UTF-8 -*-</span> <span class="kn">import</span> <span class="nn">doctest</span> <span class="kn">import</span> <span class="nn">unittest</span> <span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">glob</span> <span class="kn">import</span> <span class="nn">zope.component</span> <span class="kn">import</span> <span class="nn">zope.interface</span> <span class="n">flags</span> <span class="o">=</span> <span class="p">(</span><span class="n">doctest</span><span class="o">.</span><span class="n">ELLIPSIS</span> <span class="o">|</span> <span class="n">doctest</span><span class="o">.</span><span class="n">NORMALIZE_WHITESPACE</span> <span class="o">|</span> <span class="n">doctest</span><span class="o">.</span><span class="n">REPORT_ONLY_FIRST_FAILURE</span><span class="p">)</span> <span class="n">exts</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;.rst&#39;</span><span class="p">,</span><span class="s">&#39;.txt&#39;</span><span class="p">]</span> <span class="k">def</span> <span class="nf">test_suite</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[]):</span> <span class="n">suite</span> <span class="o">=</span> <span class="p">[]</span> <span class="n">dirname</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">__file__</span><span class="p">)</span> <span class="ow">or</span> <span class="s">&#39;.&#39;</span> <span class="k">for</span> <span class="n">root</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">dirname</span><span class="p">):</span> <span class="k">if</span> <span class="s">&#39;/.&#39;</span> <span class="ow">in</span> <span class="n">root</span><span class="p">:</span> <span class="k">continue</span> <span class="k">for</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span> <span class="n">f</span><span class="p">,</span><span class="n">ext</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="n">ext</span> <span class="ow">in</span> <span class="n">exts</span><span class="p">:</span> <span class="n">path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">path</span><span class="p">):</span> <span class="k">continue</span> <span class="n">locales</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">template_dir</span><span class="o">=</span><span class="n">root</span><span class="p">)</span> <span class="n">suite</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">doctest</span><span class="o">.</span><span class="n">DocFileTest</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">optionflags</span><span class="o">=</span><span class="n">flags</span><span class="p">,</span> <span class="n">globs</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span><span class="nb">globals</span><span class="p">(),</span><span class="o">**</span><span class="n">locales</span><span class="p">)))</span> <span class="k">return</span> <span class="n">unittest</span><span class="o">.</span><span class="n">TestSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span> <span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&#39;__main__&#39;</span><span class="p">:</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">__file__</span> <span class="o">=</span> <span class="s">&#39;tests.py&#39;</span> <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">(</span><span class="n">defaultTest</span><span class="o">=</span><span class="s">&#39;test_suite&#39;</span><span class="p">)</span> </pre></div> <p>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:</p> <pre class="literal-block"> gawel&#64;cecilia:~/svn/rst% python tests.py ......................................... ---------------------------------------------------------------------- Ran 41 tests in 0.717s OK </pre> <p>Elle est pas belle la vie ? Même les annonces des AFPyro vont être testées !</p> <!-- Links --> <!-- - - - - - --> Wed, 06 Jun 2007 22:13:00 -00000gawel.org-teste-et-approuveAh ! Les JPF ! Bonheur !http://www.gawel.org/weblog/fr/2007/06/jpf-debrief-PUTAIN-C-ETAIT-SUPER-CONVI<p>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.</p> <p>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 <a class="reference external" href="http://journees.afpy.org">http://journees.afpy.org</a>. Je suis sauvé !</p> <p>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 :)</p> <p>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 ;)</p> <p>Une chose est sur, à l'année prochaine ! On ne s'arrêtera pas devant un tel succès.</p> Mon, 04 Jun 2007 21:00:00 -00000jpf-debrief-PUTAIN-C-ETAIT-SUPER-CONVI