Sur un coup de tête (un peu déçu par le PHPCamp, où l’on a beaucoup parlé de technologies connexes et très peu de PHP), j’ai décidé lundi matin de tester l’implémentation en Django/Python d’une application web PHP4 que je dois réécrire pour l’internationaliser (anglais / français / chinois). Je précise que si je développe en PHP depuis 2001, je parcours les manuels python depuis plusieurs années et je me suis déjà bien plongé dans la documentation de Django puisque j’ai implémenté en PHP son moteur de templates. Bien qu’ayant très peu pratiqué, je ne suis pas tout à fait novice sur ces deux technologies.

Voici le bilan de trois jours d’évaluation.

Premier jour : (ré)installation de Django, la version 1.0.2, et création du projet. L’application se compose de trois interfaces : une interface web classique (qui doit donc etre multilingue), une interface XML-RPC et une interface client-serveur proprietaire. J’ai choisi de créer une app par interface. Je commence par l’interface client-serveur, qui est le point le plus critique et qui va subir prochainement une forte montée en charge. C’est elle qui va contenir la couche modèle.

La structure de la base de données étant déjà définie, je crée les classes modèles afin qu’elles s’adaptent aux tables existantes. Il s’agit pour l’instant de code essentiellement déclaratif. Parmi les spécificités, je créé un modèle superflu pour prendre en charge une table de liaison qui ne suit pas les conventions de nommage de Django; et un champ CSVField pour des listes de valeurs stockées en CSV dans la base. Le code ci-dessous, incluant docstrings et doctests (Qui sont pour moi une forme de documentation) :

from django.db import models

class CSVField(models.Field):
    """Python list stored as a single CSV-encoded varchar field"""
    separator = ';'
    max_length = 255

    def db_type(self):
        """Define SQL column type"""
        return 'VARCHAR(%s)' % self.max_length

    def to_python(self, value):
        """Converts CSV to Python list

        >>> CSVField.to_python('1;2;3;4;5')
        [1,2,3,4,5]"""
        return value.split(self.separator)

    def get_db_prep_value(self, value):
        """Converts Python list to CSV

        >>> CSVField.get_db_prep_value([1,2,3,4,5])
        '1;2;3;4;5'"""
        return self.separator.join(value)

Je crée également les premières vues, qui se contentent de réafficher les paramètres passées dans l’url.

Deuxième jour : Je mets en place les premiers tests unitaires. Je découvre le client de test de Django, qui me permet de tester les premières vues, avec des données générées dans une base de tests en partie par un fichier yaml (fixtures) et en partie via l’API de la couche modèle dans le setUp() de la classe de tests.

Ensuite, je m’attaque à l’interface web classique qui doit être internationalisée. J’installe pour cela gettext sur mon poste de travail (windows). Je crée la première vue (la page d’accueil) et les templates correspondant. Je marque les parties à traduire dans le code et les templates, et crée les fichiers de traduction anglais et chinois. Je les édite avec poedit, un logiciel spécialisé (de l’avantage d’utiliser un standard existant). Je copie-colle ce que me donne Google Translate pour le chinois parce que, euh, je ne parle pas cette langue. Mais je peux voir avec satisfaction les premiers idéogrammes apparaitre sur le site.

Ici les contenus textuels n’auront pas à être édités fréquemment. Dans le cas contraire je vois mal comment interfacer simplement gettext avec un CMS. Ici, le choix de gettext et poedit permet de faire saisir les traductions par quelqu’un qui n’est pas webmestre. De plus, gettext est également disponible en PHP via une extension spécifique.

Troisième jour : Il est temps de passer à l’interface xml-rpc. Après une brève recherche, je tombe sur django_xmlrpc qui permet d’associer les vues aux methodes fournies par le serveur (Django sert les vues selon des expressions régulières sur l’url de la requete, or avec xml-rpc toutes les méthodes sont servies sur la même url).

Après avoir listé les méthodes et les vues, je crée quelques tests unitaires pour la première. pour cela, je surcharge la classe TestCase de Django avec quelques méthodes utilisant les utilitaires xml-rpc de python (Voir le code ci-dessous). Après quelques tatonnements, mes premiers tests passent !

from django.test import TestCase

class XmlRpcTestCase(TestCase):
    """Specialisation de TestCase pour xml-rpc"""

    # @param string method
    # @param tuple params
    # @param string uri
    # @return Response
    def methodCall(self, method, params=(), uri='/rpc/'):
        """Issue a methodCall and sends a django's test client Response"""
        from xmlrpclib import dumps
        call = dumps(params, method)
        return self.client.post(uri, call, content_type='text/xml')

    # @param Response response
    # @param bool use_datetime if True return datetimes as object
    # @return tuple
    def parseResponse(self, response, use_datetime=True):
        """Parse django's client http Response to python tuple"""
        from xmlrpclib import loads, Fault
        try:
            result = loads(response.content, use_datetime)
            # result is a tuple params, method
            return result[0]
        except Fault, f:
            self.fail("Response was a Fault")
            return ()

    # @param string method
    # @param tuple params
    # @param string uri
    # @return tuple
    def parseCall(self, method, params=(), uri='/rpc/'):
        """method's result <params> as a python tuple

        assert False on method Fault"""
        response = self.methodCall(method, params, uri)
        return self.parseResponse(response)

    def assertNotFault(self, response):
        return self.assertNotContains(response, '<fault>')

Comme il me reste un peu de temps, je reviens sur l’interface client-serveur qui sert un XML maison. Je découvre donc l’implémentation DOM de python pour le générer. Je rajoute quelques tests, et quitte la boite (tôt) sur un premier test positif :)

Bilan : Compte tenu de la vitesse à laquelle je me retrouve avec un prototype, et du fait que je n’ai rencontré aucun obstacle, je vais très probablement continuer l’implémentation en python et django. Il s’agit d’un choix tout à fait subjectif. J’apprécie beaucoup la syntaxe Python, son modèle tout-objet, et la philosophie batteries included (il y a en standard une bonne librairie pour la plupart des besoins). Et surtout, la qualité et l’exhaustivité de la documentation du langage et du framework, qui permettent de trouver très rapidement ce que l’on cherche, et même plus !

Tagged with:
 

3 Responses to Tests de portage PHP > Python

  1. Salut,

    Je suis curieux. Quelques mois plus tard… Est-ce que tu utilises toujours Django pour tes développements? Qu’est-ce qui t’a motivé à utiliser ce framework plutôt qu’un autre? Je songe par exemple à Ruby on Rails dont on entend que du bien, ou à CakePHP qui est censé reprendre les mêmes principes MVC avec toute la familiarité de PHP.

    J’avoue ne jamais avoir essayé Python (malgré tout le bien que j’en entends) et manquer de temps pour vraiment expérimenter Ruby on Rails. Mais je serais curieux de voir ce que tu penses de tout cela.

    Fab.

  2. Olivier Pons says:

    Bonsoir,

    Pour répondre à Fab, je pratique php depuis plusieurs années, j’ai traduit un long article ici :

    http://olivierpons.com/2010/02/06/debutants-pourquoi-le-langage-python/

    Et je m’y suis mis moi même ici :
    http://olivierpons.com/2010/02/14/python-mes-premiers-essais/

    La seule et unique chose qui me gêne c’est le manque de suivi / documentation sur le module python/Apache, alors qu’en théorie et en pratique, c’est mieux (au sens c’est plus agréable), c’est plus fiable, on développe plus vite, disons, sans éxagérer, deux fois plus vite. Il ne manque qu’une chose qui est malheureusement cruciale : des frameworks. Rien en python/Apache n’a l’équivalent d’outils de template comme Smarty Php/Apache, ni de sites aussi puissants, et gratuits tels que WordPress, Joomla ou Drupal pour n’en citer que quelques uns.

    Un grand “malheureusement” qui fait qu’actuellement, je ne me sers de python que pour du scripting.

  3. Jérémie Ducastel says:

    Fab > oui j’utilise toujours Django pour mes développements, sauf pour quelques projets en maintenance et que je passerai en python dès que l’occasion se présentera !

    Olivier > C’est vrai que je ne connais pas d’équivalent de WordPress. Pour ce qui est des moteurs de template, je préfère et de loin celui intégré à Django, que j’avais d’ailleurs implémenté en PHP, et dont la syntaxe a été reprise pour TWIG.

Set your Twitter account name in your settings to use the TwitterBar Section.