Les émissions que j'écoute régulièrement

Voici une liste d'émissions (toujours disponible sur Internet) que j'écoute presque toutes les semaines :

  • « Conversation d'avenir » diffusé par Public Sénat. À chaque émissions Jaques Attali donne sa vision de l'avenir à propos d'un sujet. Malheureusement l'émission du 22/12/2010 sera la dernière après plus de 200 épisodes. Vous pouvez toutefois consulter les archives.
  • Tous les vendredi Paul Jorion diffuse une vidéo nommée « Le temps qu'il fait, le … ». Attention, la vidéo ne parle pas de météo mais traite de sujets d'actualités, d'économies, de philosophies… toujours très intéressant.
  • Les enregistrements audios et vidéos du site Ars Industrialis. Surtout des sujets de philosophies, psychanalyses… On y trouve de nombreuses conférences, discours du philosophe français Bernard Stiegler.
  • Je suis abonné à Arrêt sur Image. Je trouve les émissions de ce sites souvent très intéressantes.
  • J'adore l'émission 2000 ans d'histoire diffusée par France Inter. Un vrai plaisir à écouter.

J'écoute plus rarement :

Il s'agit là uniquement des sources d'informations audios… Personnellement je préfère l'information écrite :

  • car elle demande plus de concentration,
  • elle est plus facile à produire pour l'auteur,
  • elle est plus facilement indexable,
  • il est plus facile d'en extraire du contenu

mais le format audio me permet de m'instruire tout en exerçant des tâches purement manuelles, comme la vaisselle, le ménage… Grâce à ces émissions je peux passer des heures sur ces tâches sans m'ennuyer :)

Maintenant vous aimeriez peut être savoir quelles émissions télévisés je regarde ? et bien aucune, depuis plus de 10 ans je passe vraiment très très peu de temps devant la télévision (je regarde « Conversation d'avenirs » en ligne, à la demande).

Read and Post Comments

Explication à propos de l'initialisation / configuration de SQLAlchemy et Elixir dans Pylons

Voici dans ce billet quelques notes à propos de la configuration de Elixir dans Pylons "0.9.7". J'y ai aussi ajouté une "explication" personnelle de l'initialisation/configuration de SQLAlchemy dans Pylons.

1.   SQLAlchemy

1.1.   Généralités

L'initialisation de SQLAlchemy consiste par exemple à :

  • créer un objet "metadata"
from sqlalchemy import MetaData

metadata = MetaData()
  • créer un objet "session"
from sqlalchemy.orm import sessionmaker
session = sessionmaker()
  • créer un objet "engine"
from sqlalchemy import create_engine
engine = create_engine('sqlite://') # in memory
  • connecter la session à un "engine"
session.configure(bind = engine)

En résumé, il nous faut 3 éléments :

  • un objet MetaData
  • un objet Session
  • un objet Engine

Nous retrouverons chacun de ces trois éléments dans les deux méthodes de configurations présentées ci-dessous.

1.2.   Par défaut, comment Pylons initialise SQLAlchemy ?

Pylons défini quelques conventions quant à l'initialisation de SQLAlchemy.

Les fichiers en questions :

config/environment.py
model/__init__.py
model/meta.py
1.2.1.   model/meta.py

Le module "model/meta.py" permet un accès standardisé aux variables "engine", "Session" et "metadata".

Extrait de quelques lignes du module "model/meta.py" :

from sqlalchemy import MetaData

engine = None
Session = None
metadata = MetaData()
1.2.2.   model/__init__.py

Le module "model/__init__.py" se charge de l'initialisation de la couche modèle. Dans notre contexte (utilisation de SQLAlchemy) ce module se charge d'initialiser SQLAlchemy.

Extrait de quelques lignes du module "model/__init__.py" :

from sqlalchemy import orm

from pylonsapp.model import meta

def init_model(engine):
    sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)

    meta.engine = engine
    meta.Session = orm.scoped_session(sm)

Ce module défini une fonction nommée "init_model" avec un seul paramètre nommé "engine". Cette fonction se charge de créer un objet Session et de connecter ce dernier avec l'objet engine passé en paramètre. Cette fonction est appelé par le module "config/environment.py".

1.2.3.   config/environment.py

Pour finir, le module config/environment.py est chargé de la configuration globale du framework Pylons. Celle-ci s'effectue en fonction de paramètres définies dans un fichier ".ini". Ce fichier ".ini" est passé en paramètre lors du lancement de Pylons (par exemple : paster serve development.ini).

Extrait de quelques lignes du module "config/environment.py" :

from sqlalchemy import engine_from_config

...

from pylonsapp.model import init_model

def load_environment(global_conf, app_conf):

    ...

    engine = engine_from_config(config, 'sqlalchemy.')
    init_model(engine)

La fonction "engine_from_config" crée un objet Engine en fonction des paramètres présents dans le fichier ".ini".

1.2.4.   Résumé

Pour résumer :

  • model/meta.py : création de l'objet MetaData
  • config/environment.py : création de l'objet Engine en fonction des paramètres définis dans un fichier ".ini"
  • model/__init__.py : création de l'objet Session et connection de ce dernier avec l'objet engine créé dans environment.py

Note : par convention, model/__init__.py doit aussi se charger de l'initialisation des entités de la couche modèle.

2.   Elixir

2.1.   Configuration standard de Elixir

Voici la méthode standard d'initialisation de Elixir :

import elixir

elixir.metadata.bind = "sqlite:///movies.sqlite"
elixir.metadata.bind.echo = True

# ... déclaration des entités

elixir.setup_all()

Elixir met directement à disposition les objets suivants :

  • Metadata : elixir.metadata
  • Session : elixir.session

L'objet fournit par "elixir.metadata" est identique à un objet créé via "sqlalchemy.MetaData()".

Il est tout à fait possible de remplacer l'objet Session accessible via "elixir.session"... c'est ce qui va être fait dans la section suivante.

2.2.   Initialiser Elixir dans Pylons

Voici maintenant comment configurer Elixir dans Pylons.

2.2.1.   model/meta.py

Extrait de quelques lignes du module "model/meta.py" :

import elixir

engine = None
Session = None
metadata = elixir.metadata

Cette fois metadata contient l'objet Metadata défini par Elixir.

2.2.2.   model/__init__.py

Extrait de quelques lignes du module "model/__init__.py" :

from sqlalchemy import orm
import elixir

from pylonsapp.model import meta

meta.Session = orm.scoped_session(orm.sessionmaker(autoflush=False, expire_on_commit=False))
elixir.options_defaults.update(dict(shortnames=True, inheritance='multi', polymorphic=True))
elixir.session = meta.Session
from pylonsapp.model.entities import *

def init_model(engine):
    meta.metadata.bind = engine
    meta.engine = engine
    elixir.setup_all()

Explications du contenu/changement de ce module :

  • cette fois, l'objet Session n'est pas créé dans la fonction "init_model" mais avant le chargement des classes "Entities"
  • "elixir.options_defaults.update" permet de configurer certains "comportements" de Elixir
  • le module "pylonsapp.model.entities" contient la déclaration des classes entities
  • la fonction "init_model" permet de connecter l'objet Engine à l'objet Metadata créé par Elixir. Ensuite "elixir.setup_all()" permet l'initialisation des entities.

3.   Modification de "lib/base.py"

Le billet Using Elixir with Pylons précise qu'il faut modifier le module "lib/base.py" afin de s'assurer du "nettoyage" de la Session de SQLAlchemy.

from pylonsapp.meta import Session

class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            Session.remove()

4.   Notes

  • Cette configuration de Elixir permet toujours de créer des tables, classes... via les méthodes classiques de SQLAlchemy
  • En cas du problème suivant : rien n'est enregistré lors de l'exécution de session.commit() c'est sans doute parce que les entities ont été sauvegardé dans une autre session.
Read and Post Comments

Ce qui me fait préférer Python

Depuis que j'ai débuté l'informatique, j'ai appris et utilisé de nombreux langages.
Concernant les langages uniquement, sans parler des APIs ou Framework ni des shells, plus ou moins en ordre chronologique, j'ai commencé sur Amstrad CPC par le Basic, puis l'assembleur z80.
Quand je suis passé au PC, j'ai commencé par le Turbo Basic de Borland, le Quick Basic de Microsoft, le Turbo Pascal de Borland, de l'assembleur x86, un tout petit peu de C, le Visual Basic de Microsoft, le Delphi de Borland.
Je suis ensuite passé à GNU/Linux en 1998, là j'ai découvert et utilisé le Perl, PHP, Python, j'ai approfondi le C et C++. J'ai étudié un peu Java et C#. J'ai utilisé un peu TCL. J'ai jetté un oeil sur Ruby, Ada, Lisp. Je connais de nom Caml, Scheme, Smaltalk, Eiffel ...

Pourquoi cette restrospective ? Parce que parmi tous les langages que j'ai étudié et/ou utilisé, à mes yeux, l'un d'eux sort du lot : Python.

Bien entendu, celui-ci sort du lot dans son domaine d'application. Par exemple, Python n'est pas adapté pour développer le noyau d'un système d'exploitation, ni pour réaliser un moteur de base de donnée dont l'objectif premier est la performance (bien qu'il existe une implémentation de base de donnée 100% développé en Python). Par contre je choisirais Python pour tous autres domaines où les performances bruts ne sont pas au coeur du besoin, par exemple pour :

  • des scripts d'administrations systèmes ou d'analyses de fichiers textuels.

  • tous les développements liés à l'Internet et en particulier au Web: scripts CGI, moteurs de recherche, agents intelligents, objets distribués...

  • l'accès aux bases de données (relationnelles).

  • la réalisation d'interfaces graphiques utilisateurs (par exemple pour des applications "lourdes") basées sur Gtk, Qt, wxWidgets.

  • la réalisation d'applications web

  • le calcul scientifique et l'imagerie. Python ne sert alors pas à écrire les algorithmes, mais à combiner et mettre en oeuvre rapidement des librairies de calcul écrites en langage compilé (C, C++, Fortran, Ada,...).

  • L'apprentissage de la programmation objet.

  • Le prototypage rapide d'applications. L'idée générale est de commencer par écrire une application en Python, de la tester (ou de la faire tester par le client pour d'éventuelles modifications du cahier des charges). Trois cas peuvent alors se présenter:

    • Les performances sont satisfaisantes, après optimisation éventuelle du code Python. On livre alors le produit tel quel au client.

    • Les performances ne sont pas satisfaisantes, mais l'analyse de l'exécution du programme (à l'aide du profiler de Python) montre que l'essentiel du temps d'exécution se passe dans une petite partie du programme. Les fonctions, ou les types de données, correspondants sont alors réécrits en C ou en C++, sans modification du reste du programme.

    • Sinon, il est toujours possible de récrire tout le programme, en utilisant la version Python comme un brouillon.
      Même dans le pire des trois cas, il est très vraisemblable que le temps de développement aura été sensiblement plus court que si le programme avait été développé directement en C ou en C++.

Le but de cet article et des autres articles qui traiteront du même thème est de vous présenter pourquoi j'apprécie Python plutôt qu'un autre langage.

Pour ceux qui me suggéreront Java, je répondrais que Java est un très bon langage, dont les performances (du langage) [1] sont très bonnes et meilleurs que Python, dont l'étendu des librairies, framework... est très importante... mais j'aime le coté agile, léger et simple de Python. D'autre part, je connais le langage Java mais j'ai de très modestes connaissances au niveau des frameworks qui gravitent autour de ce langage. Par conséquence, je ne ferais pas de comparaison entre Java et Python.

Je ne ferais pas non plus (ou alors très très peu) de comparaison avec Ruby car je ne connais pas beaucoup ce langage. Je connais les bases et les concepts de RoR (RubyOnRails) car j'ai survolé le livre Ruby On Rails. D'autre part, j'ai l'impression que la philosophie Python/Ruby sont assez proche.

Qu'est ce qui me fait préférer Python à d'autres langages :

  • D'un point de vue langage (syntaxique, fonctionnalités...) :
    • La syntaxe de Python est très simple et, combinée à des types de données évolués (listes, dictionnaires,...), conduit à des programmes à la fois très compacts et très lisibles. A fonctionnalités égales, un programme Python (abondamment commenté et présenté selon les canons standards) est souvent de 3 à 5 fois plus court qu'un programme C ou C++ (ou même Java) équivalent, ce qui représente en général un temps de développement de 5 à 10 fois plus court et une facilité de maintenance largement accrue [2].
    • Les arguments des fonctions peuvent être optionnels et/ou nommés (voir l'article que j'ai écris à ce sujet : Dans la série ''Ce qui me fait préférer Python'' : ''arguments optionnels et nommés'')
    • Les compréhensions de listes.
    • Python est un langage réflexif (il supporte la métaprogrammation, par exemple la capacité pour un objet de se rajouter ou de s'enlever des attributs ou des méthodes, ou même de changer de classe en cours d'exécution).
    • Introspectif (un grand nombre d'outils de développement, comme le débogueur ou le profileur, sont implantés en Python lui-même).
    • Le système de namespace des modules, système d'importation, et les possibilités offertes par ce mécanisme.
    • Les metaclass.
    • Les décorateurs.
    • Le fait qu'une classe soit un dict.
    • Presque tout est objet (par exemple les chaînes...).
    • self.__dict__.update(locals())
    • Les exceptions et l'arbre d'exceptions.
    • La surcharge de __call__ permettant d'exécuter l'instance d'un objet.
    • Les surcharges d'opérateurs : __repr__ , __str__, __unicode__, ...
    • les docstrings
    • import __future__ qui permet d'activer des fonctionnalités qui seront livrées par défauts dans les futures versions de Python
  • Caractéristique "non technique" de Python (philosophie, communauté, organisation, méthode...)
    • Python convient aussi bien à des scripts d'une dizaine de lignes qu'à des projets complexes de plusieurs dizaines de milliers de lignes;
    • Le langage Python est entièrement libre et les outils, librairies qui gravitent autour de ce langage sont eux aussi la plupart du temps libre.
    • La philosophie agile du langage, des librairies et des personnes qui composent la communauté Python.
    • L'annuaire Python Index qui regroupe à un endroit unique les librairies Python.
    • C'est super plaisant d'écrire du code python : exemple de comparaison "XML-RPC Python vs PHP".
    • La communauté de qualitée : être en contact avec des gens compétant et qui sont capables de réaliser des designs de qualités.
    • Le fait que python soit souvent utilisé comme langage de bindings des outils libres (même Mozilla s'y met).
    • Les designs pattern sont facilement réalisables (par exemple un décorateur, une fabrique, proxy, observateur...)
    • l'avenir avec PyPy: un des buts du projet est de générer une implémentation Python optimisée à partir de PyPy qui tournera plus rapidement que l'implémentation en C actuelle (CPython), bien que ce but ne soit pas encore atteint.
    • le fait que ce soit un langage "multidomaine" : web, application lourde (qt, gtk, wxwindows)
    • la grande quantité de librairies et de framework. Dans la majorité des cas ils/elles sont de grand(e)s qualité(e)s.
    • les PEP : des minis cahiers des charges soumis à l'approbation dans un premier temps de l'ensemble des développeurs Python, pour être affinés ou rejetés, puis sont acceptés ou non par Guido van Rossum (créateur et mainteneur de Python)
  • Outils intégrés ou proches du langage :
    • Les possibilités offertes par le prompt python (et IPython), parfaitement adapté pour tester une api, un objet ou même apprendre le langage.
    • Très bonne portabilité du langage et la librairie standard Python : réalisation assez simplement d'application mutliplateforme.
    • pdb : débogueur fortement intégré au langage.
    • Les possibilités de dépréciations de fonctions / classes ainsi que leurs déplacements automatiques.
    • virtualenv : permet de créer facilement et rapidement un environnement Python de développement ou de test. Peut même servir en production.
    • Les bindings en C facilement réalisable.
    • reStructuredText (docutils)
    • Rapidité et simplicité d'installation de paquet (egg) via easy_install.
    • distutils (Building and installing Python modules)
    • Possibilité de réaliser des modules très rapides grâce à Pyrex. Cet outil permet de transformer du code Python typé en langage C. Le code C est ensuite compilé pour produire un module très performant directement utilisable via Python.
    • Psyco pour accélérer l'exécution de programmes Python sans aucune modification des sources.
  • Outils agiles, tests...
    • Les doctests.
    • pylint : permettant de tester la qualité du code.
    • Les outils de profiling et de recouvrement des tests.
  • Les outils, librairies Python :
  • Les frameworks et outils web :
    • Zope3 : serveur d'application web orienté objet
    • Django : framework de développement Web en Python
    • Pylons : framework de développement Web en Python
    • Routes : URL mapper
    • Cherrypy : framework de développement Web en Python
    • Paste
    • ...

J'arrête la liste ici pour le moment car le but de cet article n'est pas de réaliser une liste exhaustive des outils Python. Je compte plutôt utiliser cet article comme une sorte de sommaire vers d'autres articles que j'écrirai à l'avenir.

[1]The Computer Language Benchmarks Game : http://shootout.alioth.debian.org/
[2]http://www.linux-center.org/articles/9812/python.html
Read and Post Comments

Dans la série « Ce qui me fait préférer Python » : « arguments optionnels et nommés »

Une des fonctionnalités qui me fait préférer Python à d'autre langage est la possibilité d'utiliser des arguments nommés dans les fonctions ou les méthodes.

Exemple en PHP

Je commence par écrire une fonction avec des paramètres optionnels :

<?php

function affiche_contact($nom, $prenom, $telephone1 = "", $telephone2 = "", $email = "")
{
    print("Nom : $nom\n");
    print("Prénom : $prenom\n");
    print("Téléphone1 : $telephone1\n");
    print("Téléphone2 : $telephone2\n");
    print("Email : $email);
}

?>

J'affiche un contact sans spécifier aucun argument optionnel :

<?php
affiche_contact("Dupond", "Pierre");
?>

Jusque là tout va bien. A noter tout de même que sans lire l'entête de la fonction, il n'est pas possible de savoir à quoi correspond chaque argument. De plus il n'est pas possible d'envoyer en premier argument le prénom puis le nom. L'entête de la fonction déclare que c'est d'abord le nom, puis le prénom, il n'est pas possible de changer cet ordre lors de l'appel de la fonction. Vous comprendrez ensuite pourquoi j'ai donné cette précision.

J'affiche un contact mais cette fois je spécifie l'argument optionnel $telephone1 :

<?php
affiche_contact("Dupond", "Pierre", "03 87 20 25 20");
?>

Toujours aucun problème, l'argument $telephone1 est le premier argument optionnel.

Je souhaite maintenant afficher un contact avec un numéro de téléphone et une adresse mail :

<?php
affiche_contact("Dupond", "Pierre", "03 87 20 25 20", "", "dupond.pierre@example.com");
?>

Comme vous pouvez le voir, si je souhaite passer le paramètre $email je suis obligé d'envoyer l'argument $telephone2. Ceci n'est vraiment pas pratique ni élégant. De plus, je dois avoir connaissance de la valeur par défaut du paramètre que je ne souhaitais pas envoyer.

Ce cas de figure est une vrai limite au arguments optionnels des fonctions du langage PHP. J'ai souvent été confronté à ce problème qui est vraiment énervant lorsque l'on souhaite étendre les fonctionnalités d'une fonction sans casser une API existante.

Exemple en Python

Je commence par écrire une fonction avec des paramètres optionnels :

def affiche_contact(nom, prenom, telephone1 = "", telephone2 = "", email = ""):
    print ("""Nom : %s
        Prénom : %s
        Téléphone1 : %s
        Téléphone2 : %s
        Email : %s""" % (nom, prenom, telephone1, telephone2, email))

J'affiche un contact sans spécifier aucun arguement optionnel tout comme avec la syntax PHP :

affiche_contact("Dupond", "Pierre")

Je peux aussi utiliser la syntaxe suivante :

affiche_contact(nom = "Dupond", prenom = "Pierre")

Cette syntaxe nous indique la correspondance de chaque champ envoyé. Cela évite au lecteur de consulter l'entête de la fonction.

Je peux aussi envoyer les aguments dans un ordre quelconque :

affiche_contact(prenom = "Pierre", nom = "Dupond")

J'affiche un contact mais cette fois je spécifie l'arguement optionnel telephone1 :

affiche_contact("Dupond", "Pierre", "03 87 20 25 20")

L'argument telephone1 est le premier arguement optionnel, il n'est donc pas obligatoire de le nommer.

Je souhaite maintenant afficher un contact avec un numéro de téléphone et une adresse mail :

affiche_contact("Dupond", "Pierre", "03 87 20 25 20", email = "dupond.pierre@example.com")

Comme vous pouvez le constater, j'ai envoyé uniquement les paramètres optionnels que je souhaite transmettre, contrairement à PHP je n'ai pas besoin de transmettre le paramètre telephone2.

Les arguments optionnels nommés sont vraiment une fonctionnalité Python très pratique. Cela permet de vraiment choisir les paramètres que l'on souhaite envoyer sans se soucier de l'ordre de ces derniers.

Les arguments nommés ont aussi l'avantage d'indiquer au lecteur le nom de l'argument envoyé sans avoir à regarder l'entête de la fonction.

Solution de contournement en PHP

En PHP, il y a aussi une méthode pour produire plus ou moins le même effet.

Exemple :

<?php

function affiche_contact($data)
{
    if (!isset($data['telephone1'])) $data['telephone1'] = '';
    if (!isset($data['telephone2'])) $data['telephone2'] = '';
    if (!isset($data['email'])) $data['email'] = '';

    print("Nom : " . $data['nom'] . "\n");
    print("Prénom : " . $data['prenom'] . "\n");
    print("Téléphone1 :" . $data['telephone1'] . "\n");
    print("Téléphone2 :" . $data['telephone2'] . "\n");
    print("Email :" . $data['email']);
}

?>

Puis lors de l'appel de la fonction :

<?php

affiche_contact(array("nom": "Dupond", "prenom": "Pierre"));
affiche_contact(array("nom": "Dupond", "prenom": "Pierre", "telephone1": "03 87 20 25 20"));
affiche_contact(array(
    "nom" => "Dupond",
    "prenom" => "Pierre",
    "telephone1" => "03 87 20 25 20",
    "email" => "dupond.pierre@example.com"
));

?>

Ceci est plus long (surtout dans le corps de la fonction) et moins élégant qu'en Python où cette fonctionnalité est directement intégrée au langage.

Conclusion

Je trouve que cette fonctionnalité du langage Python est très pratique bien qu'elle ne soit pas une killer feature du langage.

Références

Pour finir, quelques références :

Read and Post Comments

XML-RPC Python vs PHP

Il y a quelques mois, j'ai eu à développer un serveur XML-RPC en PHP. Il m'est alors venu à l'esprit que l'utilisation de ce protocole était un bon exemple de comparaison entre PHP et Python (avec préalablement une petite idée derrière la tête).

Le but de ce document est donc de comparer l'utilisation d'une l'API XML-RPC en langage PHP et en langage python.

1   Avant propos

Par honnêteté, je tiens à préciser qu'il m'a été difficile d'écrire une comparaison objective alors que j'avais déjà une conclusion à l'esprit.
Pour luter contre ce manque d'objectivité, j'ai approfondi mes connaissances au niveau de la solution la moins efficace. Cela n'a pas été inutile car j'ai justement trouvé une solution qui simplifie le code dans le langage le moins mis en valeur.
Par conséquent, je pense que ce document est assez équilibré et pas trop partisan... enfin... je l'espère...

2   En PHP

2.1   Choix de la librairie

Avec PHP, le problème se pose déjà au niveau du choix de la librairie... et on peut dire qu'on a le choix, voici celles que j'ai trouvé (via XMLRPC.com [1] et Google) :

  • XMLRPC-EPI - binding PHP 4.1 [2]
  • XML-RPC Library for PHP [3]
  • phpRPC [4]
  • The Incutio XML-RPC Library for PHP [5]
  • SimpleXMLRPC for PHP4 [6]
  • XML-RPC Functions - intégré à PHP mais en expérimental basé sur XMLRPC-EPI [7]
  • XML-RPC for PHP [8]
  • Pear XML_RPC Package [9]

Liste sans doute incomplète car je n'ai pas beaucoup cherché...

Ca fait du monde tout ça ! En théorie pour faire la même chose !

Normalement, les deux solutions les plus "officiels" sont :

  • XML-RPC Functions - intégré à PHP mais en expérimental - basé sur XMLRPC-EPI [10]
  • Pear XML_RPC Package [11]

Je ne sais pas pourquoi la librairie XML-RPC interne à PHP est en état expérimental depuis décembre 2001 dans PHP 4.1 ! Cela ne donne pas vraiment envie de l'utiliser même si je pense qu'elle doit bien fonctionner. Si je me réfère aux discussions sur la mailing-list des développeurs PHP [12], la librairie XML-RPC interne de PHP est voué à disparaître au profit de la librairie Pear.

Dans mes précédents développements, j'ai utilisé la libraire Pear XML-RPC, c'est donc celle-ci que je vais utiliser dans l'exemple PHP de ce document.
Je ne sais pas si c'est le meilleur choix... mais en tout les cas, cela me parait la solution la plus officielle.
[1]http://www.xmlrpc.com/directory/1568/implementations
[2]http://xmlrpc-epi.sourceforge.net/
[3]http://keithdevens.com/software/xmlrpc
[4]http://sourceforge.net/projects/phprpc/
[5]http://scripts.incutio.com/xmlrpc/
[6]http://simplexmlrpc.sourceforge.net/
[7]http://fr.php.net/manual/en/ref.xmlrpc.php
[8]http://sourceforge.net/projects/phpxmlrpc/
[9]http://pear.php.net/package/XML_RPC/redirected
[10]http://fr.php.net/manual/en/ref.xmlrpc.php
[11]http://pear.php.net/package/XML_RPC/redirected
[12]http://marc.info/?l=php-internals&m=112519107120577&w=2

2.2   Installation de Pear XML-RPC

Je pars du principe que Pear [13] est installé sur votre système. Pour installer le paquet "XML_RPC" vous devez entrer les commandes suivantes :

$ sudo pear channel-update "pear.php.net"
$ sudo pear install XML_RPC

Normalement, si tout c'est bien passé, le paquet devrait être installé.

[13]http://fr.wikipedia.org/wiki/PEAR

2.3   La documentation de Pear XML-RPC

Alors ne faite pas comme moi, ne passez pas à coté de la documentation utilisateur qui se trouve derrière "End-user Documentation" à gauche de la page documentation.
Avant la rédaction de ce document, je m'étais focalisé sur la "documentation de l'API" à droite de la "page documentation". Malheureusement, cette documentation est très pauvre. Vous trouverez bien plus d'information dans la partie "End-user Documentation".

3   Exemple 1 en PHP

Cet exemple met en oeuvre la technique que j'ai utilisé lors de mes précédents développements. Vous verrez ensuite une solution plus simple dans le second exemple.

3.1   Exemple : partie serveur

<?php
require_once('XML/RPC/Server.php');

function somme($params)
{
    $x = $params->getParam(0)->getval();
    $y = $params->getParam(1)->getval();

    return new XML_RPC_Response(new XML_RPC_Value($x * $y, 'int'));
}

function renvoi_tableau_fois_2($params)
{
    $tableau = $params->getParam(0);
    $result = array();
    for($i = 0; $i < $tableau->arraysize(); $i++) {
        $result[] = new XML_RPC_Value($tableau->arraymem($i)->getval() * 2, 'int');
    }

    return new XML_RPC_Response(new XML_RPC_Value($result, 'array'));
}

function concat_dict($params)
{
    $result = array();
    $dict1 = $params->getParam(0);
    while($element = $dict1->structeach()) {
        $result[$element['key']] = new XML_RPC_Value($element['value']->getval(), 'string');
    }

    $dict2 = $params->getParam(1);
    while($element = $dict2->structeach()) {
        $result[$element['key']] = new XML_RPC_Value($element['value']->getval(), 'string');
    }

    return new XML_RPC_Response(new XML_RPC_Value($result, 'struct'));
}

$server = new XML_RPC_Server(
     array(
         'somme' => array('function' => 'somme'),
         'renvoi_tableau_fois_2' => array('function' => 'renvoi_tableau_fois_2'),
         'concat_dict' => array('function' => 'concat_dict')
    ),
    1 // serviceNow
);

?>

3.2   Exemple : partie client

<?php
require_once('XML/RPC.php');

$client = new XML_RPC_Client('/xmlrpc/server2.php', 'localhost');
$client->setDebug(1);
$response = $client->send(
    new XML_RPC_Message('somme', array(
        new XML_RPC_Value(4, 'int'),
        new XML_RPC_Value(8, 'int')
    ))
);
print $response->value()->getval();
// Affiche : 12

$response = $client->send(
    new XML_RPC_Message('renvoi_tableau_fois_2', array(
        new XML_RPC_Value(array(
            new XML_RPC_Value(1, 'int'),
            new XML_RPC_Value(2, 'int'),
            new XML_RPC_Value(3, 'int'),
            new XML_RPC_Value(4, 'int')
        ), 'array')
    ))
);
print("<pre>");
for($i = 0; $i < $response->value()->arraysize(); $i++) {
    print $response->value()->arraymem($i)->getval() . "\n";
}
print("</pre>");

/* Affiche :
2
4
6
8
*/

$response = $client->send(
    new XML_RPC_Message('concat_dict', array(
        new XML_RPC_Value(
            array("key1" => new XML_RPC_Value("value1", "string")),
            'struct'
        ),
        new XML_RPC_Value(
            array("key2" => new XML_RPC_Value("value2", "string")),
            'struct'
        )
    ))
);

print("<pre>");
while($element = $response->value()->structeach()) {
    print($element['key'] . '=>'. $element['value']->getval() . "\n");
}
print("</pre>");
/* Affiche :
key1=>value1
key2=>value2
*/
?>

4   Exemple 2 en PHP

En réalisant ce document, j'ai découvert les méthodes XML_RPC_Encode et XML_RPC_Decode. Ces fonctions diminuent fortement la quantité de code nécessaire pour "coder" et "décoder" les entrées/sorties des fonctions XML-RPC.

Voici le même exemple que le précédent mais en une version réduite grâce à l'utilisation de XML_RPC_Encode et XML_RPC_Decode.

4.1   Exemple : partie serveur

<?php
require_once('XML/RPC/Server.php');

function somme($params)
{
    $x = XML_RPC_Decode($params->getParam(0));
    $y = XML_RPC_Decode($params->getParam(1));

    return new XML_RPC_Response(XML_RPC_Encode($x * $y));
}

function renvoi_tableau_fois_2($params)
{
    $tableau = XML_RPC_Decode($params->getParam(0));
    $result = array();
    for($i = 0; $i < count($tableau); $i++) {
        $result[] = $tableau[$i] * 2;
    }

    return new XML_RPC_Response(XML_RPC_Encode($result, 'array'));
}

function concat_dict($params)
{
    $result = array();
    $dict1 = XML_RPC_Decode($params->getParam(0));
    foreach($dict1 as $key => $value) {
        $result[$key] = $value;
    }

    $dict2 = XML_RPC_Decode($params->getParam(1));
    foreach($dict2 as $key => $value) {
        $result[$key] = $value;
    }

    return new XML_RPC_Response(XML_RPC_Encode($result));
}

$server = new XML_RPC_Server(
     array(
         'somme' => array('function' => 'somme'),
         'renvoi_tableau_fois_2' => array('function' => 'renvoi_tableau_fois_2'),
         'concat_dict' => array('function' => 'concat_dict')
    ),
    1 // serviceNow
);

?>

4.2   Exemple : partie client

<?php
require_once('XML/RPC.php');

$client = new XML_RPC_Client('/xmlrpc/server3.php', 'localhost');
$client->setDebug(1);
$response = $client->send(
    new XML_RPC_Message('somme', array(
        XML_RPC_Encode(4),
        XML_RPC_Encode(8)
    ))
);
print XML_RPC_Decode($response->value());
// Affiche : 12

$response = XML_RPC_Decode($client->send(
    new XML_RPC_Message('renvoi_tableau_fois_2', array(XML_RPC_Encode(
        array(1, 2, 3, 4)
    )))
)->value());
print("<pre>");
for($i = 0; $i < count($response); $i++) {
    print $response[$i] . "\n";
}
print("</pre>");

/* Affiche :
2
4
6
8
*/

$response = XML_RPC_Decode($client->send(
    new XML_RPC_Message('concat_dict', array(
        XML_RPC_Encode(array("key1" => "value1")),
        XML_RPC_Encode(array("key2" => "value2")),
    ))
)->value());

print("<pre>");
foreach($response as $key => $value) {
    print($key . '=>'. $value . "\n");
}
print("</pre>");
/* Affiche :
key1=>value1
key2=>value2
*/
?>

Cette version est tout de même beaucoup plus simple que le précédent mais nous allons voir que la version Python est encore plus simple !

5   En Python

Avec Python, le choix de la librairie XML-RPC est très simple, il suffit d'utiliser les classes disponibles dans la librairie standard de Python [14]. Encore une fois, la communauté Python évite la multiplication des projets similaires. La communauté essaie si c'est possible de n'avoir qu'une librairie et de l'améliorer constamment.

[14]Internet Protocols and Support - http://docs.python.org/lib/internet.html

5.1   Documentation

Documentation concernant la partie XML-RPC de la librairie standard Python :

  • Section "XML-RPC client access" [15] qui contient entre autre la documentation de la classe xmlrpclib, un exemple de client [16]...
  • Section "Basic XML-RPC server" [17] qui contient entre autre la documentation de la classe SimpleXMLRPCServer, un exemple de serveur...
[15]http://docs.python.org/lib/module-xmlrpclib.html
[16]http://docs.python.org/lib/xmlrpc-client-example.html
[17]http://docs.python.org/lib/module-SimpleXMLRPCServer.html

5.2   Exemple : partie serveur

Cet exemple est identique à la partie serveur des précédents exemples écris en PHP.

from SimpleXMLRPCServer import SimpleXMLRPCServer

# Create server
server = SimpleXMLRPCServer(("localhost", 8080))
server.register_introspection_functions()

def somme(x, y):
    return x + y

server.register_function(somme)

def renvoi_tableau_fois_2(tableau):
    return [v * 2 for v in tableau]

server.register_function(renvoi_tableau_fois_2)

def concat_dict(dict1, dict2):
    dict1.update(dict2)
    return dict1

server.register_function(concat_dict)

server.serve_forever()
On constate que l'écriture de fonctions XML-RPC est très simple. La méthode est scrupuleusement identique à l'écriture de fonctions python tout à fait standard (ligne 7 et 8, 12 et 13, 17 à 19). Le passage de paramètres est réalisé d'une manière totalement transparente.
La seule chose à faire pour rendre une fonction disponible via XML-RPC est de "l'inscrire" au serveur via la fonction "register_function" (lignes 10, 15 et 21).

5.3   Exemple : partie client

Cet exemple est identique à la partie client des précédents exemples écris en PHP.

import xmlrpclib

s = xmlrpclib.Server("http://localhost:8080")
print s.somme(4, 8)
# Affiche : 12

print s.renvoi_tableau_fois_2([1, 2, 3, 4])
# Affiche [2, 4, 6, 8]

print s.concat_dict({"key1": "value1"}, {"key2": "value2"})
# Affiche {'key2': 'value2', 'key1': 'value1'}

Pour la partie client, c'est encore plus simple car il suffit de créer un objet "xmlrpclib.Server" et via cet objet, appeler les fonctions XML-RPC d'une manière totalement transparente... comme si ces fonctions étaient des méthodes natives de la classe "xmlrpclib.Server".

6   Bilan

Voici un petit bilan de cette comparaison sous la forme d'un tableau :

  Python PHP
Choix de la librairie simple : une solution officiel documenté et maintenu
difficile :
  • nombreuses librairies
  • difficulté à trouver la solution la plus officiel
Solution retenu Librairie standard Python Librairie Pear XML-RPC
Entrées / Sortie des fonctions XML-RPC Aucun traitement spécifique ! Les fonctions sont écrites comme des fonctions Python standards Il faut coder et décoder toutes les entrées sorties ! => lourdeur du code, difficulté de mise au point et de débogage
Déclaration des fonctions XML-RPC Simple passage de la fonction en paramètre de la méthode register_function Gros tableau imbriqué à passer en paramètre du constructeur XML_RPC_Server
Appel des fonctions Très simple Très lourd
Avantages
  • quantité de code minimal
  • transparence de la solution : très peu de différence entre l'utilisation d'une fonction distante et local.
  • choix de la librairie à utiliser
  • documentation complète
?
Inconvénients ?
  • difficulté de choix de la librairie
  • documentation pas très soignée
  • aucune transparence : l'utilisation, la création ou l'utilisation d'une fonction XML-RPC est totalement différente à la création et à l'utilisation de fonctions PHP standards
  • grande quantité de code nécessaire

Autre chose : j'ai plus d'expérience en PHP qu'en Python mais la solution Python est celle qui a été pour moi la plus évidente et qui m'a demandé le moins de recherche.

7   Conclusion

L'API XML-RPC de Python est un bon exemple pour illustrer une des forces de Python : rendre les choses simples et concises.

Vous pourrez faire le même constat dans d'autres librairies Python traitant du "même" domaine d'activité :

J'essaierais peut être dans un prochain article d'expliquer pourquoi Python permet de réaliser des API très simples et concises...

Read and Post Comments

Mes flux :