Faire du virtualhosting avec zope façon wsgi

Posté le 25/07/2008 sous zope3 wsgi

J'en avais ras le bol que Zope nécessite des url complètement tordues pour faire du virtual hosting. Ça m'empêchais entre autre d'utiliser Paste#urlmap pour dispatcher certaines url sur d'autres applis que Zope.

Du coup, j'ai tenté un truc tout con: plutôt que d'utiliser les RewriteRule d'Apache, récrire le PATH_INFO en englobant l'application Zope dans une autre. Et ça marche. Fiesta !

Voilà donc à quoi ça ressemble. J'utilise zopeproject. J'ai donc modifier le machin qui créer l'application Zope. A savoir le fichier startup.py comme ceci:

def application_factory(global_conf, conf='zope.conf', vhost='www.gawel.org'):
    vhost = '/++vh++http:%s:80/++' % vhost
    zopeapp = zope.app.wsgi.getWSGIApplication(zope_conf)
    def zopewrapper(environ, start_response):
        environ['PATH_INFO'] = vhost + environ['PATH_INFO']
        return zopeapp(environ, start_response)
    return zopewrapper

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 Apache, j'ai juste eu à changer mon fichier debug.ini pour prendre en compte mon virtual host de développement.

En fait j'ai fais un peu mieux que tout ça, car comme dit au début, le but était d'utiliser Paste#urlmap. La source de la bidouille en question est ici.

Aller, pendant que j'y suis, j'en chiais aussi pas mal pour déterminer vers quel backend rediriger les requêtes dans varnish. Tester des ++ dans l'url, ça lui plaisait pas du tout. Vu que j'utilise Apache devant (surtout pour subversion), j'ai trouvé le truc. Il suffit d'activer le module headers:

# a2enmod headers

Puis rajouter un truc du genre dans votre virtualhost Apache:

RequestHeader set VARNISH_BACKEND gawel_org

Vous l'aurez compris, ceci ajoute un header à la requête. Ensuite, dans varnish, on test ce header:

if (req.http.VARNISH_BACKEND ~ "gawel_org") {
    set req.backend = gawel_org;
}

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.

nose doctest plugin sucks

Posté le 17/07/2008 sous python pylons

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.

Packager ses scripts Python

Posté le 08/07/2008 sous python unix

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 Paste
$ 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.