Fantomas’side

Weblog open-source

Persistance

Comment rendre les variables du code persistantes à travers le temps et les pannes de courants en passant par les crashs d'application ?

Cette problématique fut mise en avant lors du développement d'ECS, un framework e-commerce écrit en Python, au sein d'Emencia, société de services en logiciels libres.

Les modules Python pickle et cPickle servent à cela, mais leurs mise en place de manière générique peuvent être coûteuse et de plus comment centraliser des centaines d'objets à un moment T donné ?

ECS grâce au projet SQLAchemy, supporte tous types de bases données et fournis en plus un tas de fonctions pour manipuler des tables en base de donnée à partir d'un modèle de code. C'est donc à partir de la que fût développé le modèle de persistance.

Le modèle de persistance écrit pour ECS, fournit donc les outils pour effectuer la sauvegarde de tout objet instancié au sein de Python. Il contient en plus une implémentation rapide par méta-classe pour accélérer l'intégration au sein du code.

Voici un exemple d'implémentation de classe persistante, prenons l'exemple d'un chat :

from persistency import PersistentClass

class Cat(object):
  __metaclass__ = PersistentClass
  age = None
  name = None

def __init__(self, name='', age=-1):
  self.age = age
  self.name = name
  if self.age == -1:
    self.load()
  else:
    self.save()

def database(cls):
  return 'sqlite:///:memory:'

Cat._sqluri_callback = database

Nous allons donc créer un chat du nom de Felix âgé de 8 ans.

felix = Cat('Felix', 8)  # Roooh il est beau ce chat !

Malheureusement Felix meurt lors d'une messe noire :(

del felix  # Oh non c'est terrible !

Heureusement comme tout le monde le sait, les chats ont neuf vies et Felix n'est pas mort ! Hourra pour Felix !!!

felix_is_not_dead = Cat('Felix')
felix_is_not_dead.age

Ce qui affichera au final 8.

La méta-classe permet de sauvegarder tout les attributs d'objet, excluant ceux commençant par un '_' comme pour les attributs privés. La sauvegarde des attributs de l'objet se fais au format JSON, ce qui permet la visualisation de chaques objets sauvegardés à partir d'un autre langage.

Dans les méta-classes, on peut aussi ajouter des attributs et des méthodes qui vont s'ajouter à l'état et à l'interface des classes instanciées. Ce seront donc des attributs et des méthodes de classe. Dans notre cas, nous allons ajouter les méthodes save, load, delete.

class PersistentClass(type):

    def __new__(clsbase, name, bases, dic):
            """Turns the given class into a persistent one.
            By adding a wrapped SQL mapper
            """
            if '_sqluri_callback' not in dic:
                dic['_sqluri_callback'] = getBaseForPersistence

            def _get_persistence(cls):
                """Return the persistence"""
                return Persistence(sqluri_callback=cls._sqluri_callback,
                                   wrapper=GenericWrapper,
                                   table=cls.__class__.__name__.lower())
            dic['_get_persistence'] = _get_persistence

            def save(cls):
                """Saves into the DB"""
                persistence = cls._get_persistence()
                cls_wrapped = GenericWrapper(cls)

                persistence.delete(cls_wrapped.id)
                persistence.save(cls_wrapped)
            dic['save'] = save

            def delete(cls):
                """Deletes into the DB"""
                cls._get_persistence().delete(GenericWrapper(cls).id)
            dic['delete'] = delete

            def load(cls):
                """Loads from the DB."""
                wrapper = cls._get_persistence().load(cls.name)

                if wrapper is not None:
                    wrapper._load(cls)

            dic['load'] = load

            return type(name, bases, dic)

SVN du projet : http://svn.emencia.net/public