banniere.png

09/30/2007

AfpySprint à Dijon

L'A.G. de l'Afpy organisée ce week-end à Dijon, s'est transformée en AfpySprint par manque de monde pour atteindre le quorum. Le nouveau bureau de l'association sera donc voté plus tard :)

Entre deux restos et une dégustation de vins, nous avont bossé sur trois ateliers:
  • préparation des JFP'08, qui a été l'occasion d'une démo par Seb de MinMap pour la prise de note, avec Freemind;
  • création d'une application Django (afpy_jobs) pour les offres d'emplois du site, qui deviendra jobs.afpy.org à terme;
  • reprise du taf sur mailwoman, qui doit nous permettre à terme de fusionner mailman et le forum web du site.

Pour info, l'association a maintenant son propre serveur dedibox, et vous pouvez suivre les devs sur le repository : http://trac.afpy.org/misc
Categories: afpy, coding, evenements 0 comments - commenter | Trackbacks (604) |

09/13/2007

Dijon capitale du V.. Libre !

Hasard du calendrier, le mois de septembre sera riche en évènements open source à Dijon:


Si vous aimez le vin, il faut aller au #2 ;)
Categories: evenements 0 comments - commenter | Trackbacks (466) |

08/20/2007

Recherche des similitudes par inférence Bayesienne

Le site fr.luvdit.com permet de proposer aux visiteurs une sélection de livres grâce au k-NN, mais il part du principe que ces derniers utilisent avec pertinence les étiquettes, ce qui est loin d'être systèmatique: seuls les geeks et les personnes habituées aux sites comme flickr ou delicious le font correctement.

Pour pallier à ce problème, une autre méthode de sélection peut venir renforcer celle du k-NN: l'inférence bayesienne. Cette technique permet d'associer à des catégories une liste de mots (==un texte). Lorsqu'une nouvelle liste de mots est rencontrée, le système calcul la probabilité d'appartenance à chacune des catégories existantes, en fonction des listes de mots déjà croisées. Pour augmenter la pertinence, les mots communs ou courts sont supprimés, pour tenter de conserver l'essence du texte.

Elle est appliquée ainsi au site:
  • Le résumé (==la liste des mots) de chaque livre commenté par un utilisateur est associé à une étiquette correspondant à la note que ce dernier a donné;
  • de la même manière, le résumé est associé à chaque étiquette que l'utilisateur a donné.

Lorsque des nouveaux livres sont ajoutés, le système va calculer pour chaque:
  • la note que l'utilisateur pourrait donner;
  • les étiquettes qu'il pourrait mettre.

Les propositions obtenues par le k-NN peuvent dès lors être renforcées par ce résultat:
  • les livres à note possible élevée sont mis en avant;
  • lorsque l'utilisateur choisi des étiquettes, celles calculées par le systèmes sont proposées.

Pour affiner les calculs, l'utilisateur pourra réfuter un livre en indiquant que ce dernier ne l'intéresse pas. Cette action aura pour effet de recalculer les probabilités des catégories. Le code-maquette est ici: http://hg.programmation-python.org/browser/classifier. Mais est encore affreusement lent à cause d'une mauvaise conception au niveau du stockage SQL. Il est en cours de refactorisation, mais fonctionne déjà. A terme le paquet classifier pourra servir à n'importe quel besoin d'inférence bayésienne (et il y en a partout). Le doctest montre le fonctionnement.




Categories: coding 0 comments - commenter | Trackbacks (428) |

08/13/2007

Ressources Python

Cette page regroupe une liste de ressources web pour le langage Python, organisées en catégories. Elle démarre à peine et sera mise à jour régulièrement.

Catégories:

  • Référence du langage
  • Aide-mémoire
  • Bibliothèques tierces
  • Frameworks web
  • Blogs
  • Sites web
  • Présentations, tutoriels

Référence du langage


http://docs.python.org/ est le point de départ pour rechercher de la documentation en ligne sur la syntaxe de Python. Les sections les plus importantes sont:

Aide-mémoire


Laurent Pointal maintient une Quick Reference Card qui regroupe sur une feuille A4 une cheatsheet.
 

Bibliothèques tierces




Frameworks web




Comparatifs en français : Django vs Turbogears

Blogs



-> si vous avez un blog, n'hésitez pas à le signaler en commentant

Sites web




Présentations, tutoriels



N'hésitez pas à commenter ce billet pour le compléter

08/10/2007

Un widget flash pour luvdit!

Julien m'a fait un petit widget flash pour ludvit!. Le code récupère un flux xml sur le site, qui permet de lister les 5 derniers items commentés ou ajoutés par un utilisateur donné. Il apparaît sur mon blog à gauche.

Voici le code à inclure:
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" 
codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0"
width="80" height="450" id="widget" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="FlashVars" value="user=tarek">
<param name="wmode" value="transparent" />
<param name="movie" value="http://fr.luvdit.com/js/widget.swf" />
<param name="loop" value="false" />
<param name="menu" value="false" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="http://fr.luvdit.com/js/widget.swf"
FlashVars="user=tarek" loop="false" menu="false"
quality="high" bgcolor="#ffffff" width="80"
height="450" name="widget" align="middle"
allowScriptAccess="sameDomain"
wmode="transparent"
type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer" />
<br/>
<a href="http://fr.luvdit.com">luvdit!</a>
Si vous avez un compte sur le site, il suffit de remplacer 'tarek' par votre login dans les paramètres du widget.
Categories: coding 0 comments - commenter | Trackbacks (439) |

07/30/2007

Au coeur de Python Volume 1

J'ai reçu la traduction de l'ouvrage de Wesley Chun, "Core Python Programming". La version française se présente en deux tomes, dont voici le premier:

Au coeur de Python, Tome 1, Notions fondamentales


Si vous l'avez lu, n'hésitez pas à commenter la fiche
Categories: documentation 0 comments - commenter | Trackbacks (439) |

07/22/2007

Un site de critiques de livre

J'ai commencé à regrouper sur un site toutes mes lectures. L'application qui fait fonctionner ce site reprend tous les principes énoncés dans les billets suivants:


Un widget en flash permet aussi d'afficher sur un blog (regardez à gauche) ses 5 dernières critiques (merci Julien).

Et c'est Django Powered. Si vous aimez bien lire et partager, rejoignez moi sur fr.luvdit.com, même s'il n'est pas tout à fait terminé, il est déjà utilisable.

07/06/2007

Un joli trac pour mes projets

J'ai insallé un trac pour mes projets, avec le plugin Mercurial.

http://hg.programmation-python.org/

L'interface est beaucoup pus agréable que le browser web par défaut de Mercurial.

Joie.
Categories: misc 0 comments - commenter | Trackbacks (380) |

07/05/2007

Service de mailing asynchrone pour Django, Plone, etc..

Django ne déroge pas à la règle, et fourni dans comme pour la plupart des frameworks web Python, un module pour l'envoi de mail totalement... inutilisable dans des conditions de production. En effet, send_mail et send_mass_mail, les deux API de django.core.mail, envoient tous les deux les mails de manière synchrone.

L'effet est relativement désastreux sur les performances du site puisque chaque envoi de mail provoque un blocage du thread en cours, le temps de l'échange avec le serveur SMTP via telnet. Sur un site chargé, ou qui utilise de façon massive les envois de mails, l'emploi de django.core.mail est donc fortement déconseillé.

Plus globalement, tout code qui n'entre pas en ligne de compte pour calculer la page à afficher, ne doit pas s'exécuter de manière synchrone (autres exemples de calculs asynchrones: calculs des voisins, indexation, etc).

Zope 3, un peu plus mature et sophistiqué que les autres frameworks sur ce point précis, propose un module d'envoi de mails asynchrone, qui recopie les mails dans un répertoire au format Maildir, et lance un thread en charge de dépiler les mails du répertoire. L'interêt, outre l'aspect asynchrone qui permet d'accélerer les envois et de libérer le thread qui sert la page immédiatement, est la robustesse: si le serveur tombe, le thread peut reprendre son travail d'envoi lorsqu'il est relancé.

Mais cette solution reste liée au serveur d'application car le thread est lié au processus. L'autre défaut est qu'il est nécessaire, si l'on veut ajouter des informations supplémentaires aux mails à traiter, d'ajouter des en-têtes pour respecter le format RFC-2822 des mails qui sont recopiés dans la maildir (et de les retirer avant l'envoi réel des mails). Enfin, le code devient dépendant du système de fichiers, ce qui peut poser des problèmes d'infrastructure si l'on déploie ce service d'envoi de mails sur une machine tierce au serveur d'application.

Une autre solution, beaucoup plus robuste, consiste à déposer ces mails dans une table de base de donnée relationnelle (celle employée par le site dans Django, ou une dédiée pour Zope) qui est lue régulièrement par un service d'envois de mails, totalement indépendant du serveur web.

Deux tables pour le prix d'une


Pour mettre en place ce service, deux tables sont créées dans la base de données, grâce à SQLAlchemy:
mail_data = Table('mailer_mail_data', metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('subject', String(300)),
Column('sender', String(300)),
Column('recipients', String(300)),
Column('date', DateTime()),
Column('data', TEXT()))

mailed_data = Table('mailer_mailed_data', metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('subject', String(300)),
Column('sender', String(300)),
Column('original_id', Integer),
Column('recipients', String(300)),
Column('error', String(300)),
Column('data', TEXT()),
Column('date', DateTime()),
Column('status', String(10)),)
La table mail_data sert à stocker les informations sur les mails à envoyer, et la table mailed_data permet de stocker les mails envoyés, avec pour chaque un statut, si jamais l'envoi a échoué. Cette deuxième table permet aux applications de mettre en place du feedback en cas de problème d'envoi.

Travailleuse, travailleur


Le programme en charge d'envoyer les mails est un thread qui ouvre régulièrement la base pour:
  • Lire la table mail_data et envoyer les mails qu'elle contient
  • Archiver les mails envoyés, avec ou sans erreurs, dans mailed_data
  • Supprimer les mails envoyés de la table mail_data

Il est lancé comme programme indépendant vi un script run.py, qui peut être dameonisé sur le serveur grâce aux dameontools par exemple (il y a surement plus simple de nos jours avis aux experts Linux...)
class MailWorker(Thread):
"""reads the SQLDB to do the jobs"""

def __init__(self):
Thread.__init__(self)
self.is_working = False
self.running = False

def _get_mails(self):
"""returns lines of mail_data """
return mail_data.select().execute().fetchall()

def _get_message(self, mail):
"""returns a Mime"""
msg = MIMEText(b64decode(mail.data))
msg['From'] = mail.sender
msg['To'] = mail.recipients
msg['Subject'] = mail.subject

msg['Date'] = mail.date.isoformat()
return msg

def _send_mail(self, mail):
"""sends the mail"""
server = smtplib.SMTP(settings.SMTP_SERVER)
msg = self._get_message(mail)
try:
server.sendmail(msg['From'], msg['To'], msg.as_string())
finally:
server.quit()
logging.debug('mailer:message sent to %s' % msg['To'])

def _store_mail(self, mail, error=None):
"""stores the mail"""
if error is not None:
error = str(error)
ins = mailed_data.insert()
ins.execute(subject=mail.subject, sender=mail.sender,
original_id=mail.id, recipients=mail.recipients,
error=error, data=mail.data, date=datetime.now(),
status='processed')

# removes from original table
mail_data.delete().execute(id=mail.id)

def run(self):
"""called threaded"""
self.running = True
logging.debug('mailer:launched')

while self.running:
# index
self.is_working = True
try:
# get mails to send
mails = self._get_mails()
for mail in mails:
try:
# send then
self._send_mail(mail)
except Exception, e:
logging.debug('mailer:failed to send mail')
self._store_mail(mail, e)
else:
self._store_mail(mail)
finally:
self.is_working = False
time.sleep(.1)

logging.debug('mailer:stopped')

worker = None

def start_server():
"""starts the worker"""
global worker
worker = MailWorker()
worker.start()

def stop_server():
"""stops the worker"""
global worker
if worker is not None:
worker.running = False
worker.join()
worker = None

def is_working():
"""tells if the worker works"""
return worker.is_working

# will make sure the thread stops when the process quits
from atexit import register
register(stop_server)

API d'envoi de mail


Enfin, les applications peuvent se servir du module sender pour envoyer des mails. Ce dernier injecte dans la table mail_data le mail et rend la main immédiatement
def send_mail(sender, recipients, subject, msg):
"""sends the mail by storing it into the DB"""
inserter = mail_data.insert()
res = inserter.execute(subject=subject, sender=sender,
recipients=','.join(recipients),
data=b64encode(msg), date=datetime.now())

return res.last_inserted_ids()[0]

J'utilise cette API dans mes applications pour tous les envois de mail. Dans Django, elle remplace avantageusement django.core.mail.

Exemple complet


Voici la docstring du paquet que j'ai conçu
mailer
=====

Mailer provides:

- a simple method to send mails (eg: store them)
- a worker that actually sends them

Let's work on a sql data file for the tests:

>>> import settings
>>> settings.DATABASE = test_db

send mails
----------

To send a mail, the package provides the `sender` module::

>>> from sender import send_mail
>>> mail_id = send_mail(sender='tarek@ziade.org',
... recipients=['ziade.tarek@gmail.com'],
... subject='hello',
... msg='héllo')


The mail is then stored in the database, and the id returned is the
mail id in the DB.

Another API will give the status of the mail in process::

>>> from sender import mail_status
>>> mail_status(mail_id)
u'processing'

When the id is not given, the whole mailed table is returned::

>>> mail_status()
[]

We can ask for the queue size as well::

>>> from sender import mail_queue_size
>>> mail_queue_size()
1

sending mails, for real
-----------------------

A worker is in charge of sending mails::

>>> from mailer import start_server, stop_server
>>> start_server()

The mail is then processed::

>>> import time
>>> while mail_status(mail_id) == u'processing':
... time.sleep(0.2)
>>> mail_status(mail_id)
u'processed'

Let's stop the server::

>>> stop_server()

And see the status::

>>> status = mail_status()
>>> status[0]['subject']
u'hello'
>>> status[0]['status']
u'processed'


Et, Ô joie, le code est disponible, comme d'habitude, sur http://hg.programmation-python.org, dans le paquet mailer.

Categories: coding, django, zope 0 comments - commenter | Trackbacks (393) |

06/22/2007

Qui est mon voisin ?

Dans un site de contenu, pour rechercher les similitudes entre deux documents basée sur les tags qui leur ont été associés, on recherche l'intersection commune. En d'autres termes, on recherche dans le corpus des documents les documents avec le plus grand nombre de tags communs.

Les algorithmes de voisinage, qui sont les plus simples à mettre en oeuvre, permettent de trouver rapidement ces similitudes en disposant dans un espace multidimensionnel les documents. Chaque dimension est un tag et le document se trouve dans cette dimension
à la coordonée 0 ou 1 (le document possède ce tag ou non).

Une fois cette carte dressée, il est possible de retrouver les "voisins" d'un document, c'est-à-dire les documents les plus proche dans l'espace.

Cet algorithme s'appel le k-Nearest Neighbor, ou k-NN.

Le livre de référence en matière de IA ("Artificial Intelligence: A Modern Approach"), propose une implémentation de cet algorthime en Python, disponible sur son site.

Qui sont mes copains ?


Les cas d'utilisations ne manquent pas pour le k-NN. Il peut par exemple être employé pour rechercher des similitudes entres utilisateurs de la même application. C'est le cas par exemple sur Last.Fm, qui propose une fonctionnalité de "Voisinage" où la liste des utilisateurs les plus proches de vos choix en matière de Tags, est affichée. Cette liste de personne est susceptible d'avoir des goûts très similaires aux vôtres.

Le paquet neighbors


Pour une de mes applications, j'ai développé un paquet au dessus du code du livre, qui propose une api simplifié qui permet de retrouver les voisins d'un utilisateur, en fonction des tags utilisés.

Il corrige aussi des erreurs (signalées) dans l'implémentation proposée par le livre et fourni un système de persistence en base, qui permet de découpler le travail de calcul des requêtes: une application peut requêter la base pour connaître les voisins d'un utilisateur, pendant qu'un processus se charge de les calculer et les mettre à jour régulièrement.

Voici le docstring du paquet:
=======
neartag
=======

This module implements the k-nearest neighbor algorithme (k-NN) that allows
to compute the distance between elements, given a set of value. Each value
is a dimension and the set are the coordinates of the element in the multi
dimensional space.

For tags, the idea is to find neighbours of a given user, depending on the
tags she uses. The `NearestByTag` class is instanciated with those tags::

>>> from neartag import NearestByTag
>>> tags = ["django", "python", "zen", "fun", "scary"]
>>> solver = NearestByTag(tags)

Then each user is added with her name and tag values (boolean value)::

>>> user_1 = 'user 1', ["django", "python"]
>>> user_2 = 'user 2', ["zen", "fun", "scary"]
>>> user_3 = 'user 3', ["django"]
>>> user_4 = 'user 4', ["django", "python"]
>>> for user, tags in (user_1, user_2, user_3, user_4):
... solver.add_user(user, tags)

The class then will give a sorted list of neighbours of a given user::

>>> solver.neighbours('user 1')
[(0.16..., 'user 4'), (0.3..., 'user 3'), (1.0, 'user 2')]
>>> solver.neighbours('user 2')
[(0.83..., 'user 3'), (1.0, 'user 1'), (1.0, 'user 4')]
>>> solver.neighbours('user 3')
[(0.33..., 'user 1'), (0.33..., 'user 4'), (0.83..., 'user 2')]
>>> solver.neighbours('user 4')
[(0.16..., 'user 1'), (0.33..., 'user 3'), (1.0, 'user 2')]

The smallest the returned value is, the closest the user is.

`neighbours` will return at most 10 neighbours, but this size can be changed::

>>> solver.neighbours('user 1', 1)
[(0.16..., 'user 4')]

This class works in-memory, since the loaded values are small enough to fit.

How to use it with an application
=================================

Tags changes all the time in an application. The best use is to instanciate
the class over data retrieved from a database and to compute the distances,
then to save them within a dedicated table. Since the computation can take
time, a thread worker can update those distances from time to time in the
background.
Le paquet neighbors est disponible sur mon repository Mercurial: http://hg.programmation-python.org/repositories/public/ (il faut vraiment que je fasse une autre interface web que celle par défaut de Mercurial...)

Merci à Olivier pour ses précieux conseils en IA !


Categories: coding 0 comments - commenter | Trackbacks (442) |


Tarek Ziadé. Copyright 2006. Tous droits réservés. Licence contenu site
BuzTrucs
Add to Technorati Favorites