« CPSGeo : Simple GIS for CPS | Main | YADI, part 2: a simple Psyco decorator »

Sep 23, 2005

YADI, part 1: Memento Pattern

YADI ?


"YADI" stands for Yet Another Design pattern Implementation, because GoF design patterns have been reviewed and implemented a bunch of people already, all over the web. I'd like to present my own implementation for some of them, though, in this blog.

This first post is about Memento, inspired from Zoran Isailovski's one on ASPN, (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/413838) but with a slightly different approach to simplify its use.

Memento: What for ?


Memento DP says: a state of the program data can be saved and reloaded, thus allowing to make safe transactionnal operations. In other words, some program data changes can be rolled back at some point, or commited.

The code pattern that describes it the best is:

begin_transaction()
try:
  ...
except:
  rollback_transaction()
  raise
else:
  commit_transaction()

Depending on what the program does, the scope of data that are to be saved can vary a lot. Transactions in RDBMS covers the whole database for instance.

The easiest way to implement Memento in Python is to set the transactional level to objects.

And the easiest way to implement this is to create a decorator that allow some methods of an object to become transactionnal.

The transaction decorator


The decorator transaction could be:

import copy

def get_memento(object):
  """ get the object state """
  return copy.deepcopy(object.__dict__)

def set_memento(object, state):
  """ restore the objet """
  object.__dict__.clear()
  object.__dict__.update(state)

def transaction(method):
  """ decorator """
  def bind(object, *args, **kw):
    state = get_memento(object)
    try:
      return method(object, *args, **kw)
    except:
      set_memento(object, state)
      raise

  return bind

This implementation is based on the copy module, that works with the __dict__ atribute of the object, thus it won't work if the object is a new-style class that has dropped the use of __dict__ in some ways (slots, descriptors, etc..).

Anyway, it's pretty usefull on most classes, and they can implement the __deepcopy__ method to control the copy behavior.

Example of use


The example below implements a run() method, that becomes transactionnal

class M(object):
  def __init__(self):
    def o():
      print 'OK'
    self.a = 12
    self.b = ['a', 32]
    self.l = o

    @transaction
    def run(self):
      self.b.append('c')
      self.o = 12
      self.a = '14'
      self.a += 1

objet = M()

try:
  objet.run()
except TypeError:
  pass

print objet.a
print objet.b
objet.l()

[...]

[tziade@Tarek Desktop]$ python memento.py
12
['a', 32]
OK

When run() fails, because a + 1 (a is a string) leads to a TypeError, the state of M is automatically rolled back.

(Post originally written by Tarek Ziadé on the old Nuxeo blogs.)

Comments

About Us

We're the friendly employees of Nuxeo, a leading open source software vendor, which develops a complete Enterprise Content Management (ECM) software platform to help companies better produce, process, publish, archive, expose and find their information from digital assets to transactional documents.

» Follow us @nuxeo (Twitter)

» Connect on LinkedIn

» Visit Nuxeo.com

 

Customize & Configure
Nuxeo • Studio

Nuxeo • DM
Online Trial

Nuxeo • DM
Download

Nuxeo • DAM
Download

Nuxeo Connect support