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()



[...]



[[email protected] 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.)