memento.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """http://code.activestate.com/recipes/413838-memento-closure/"""
  4. import copy
  5. def Memento(obj, deep=False):
  6. state = (copy.copy, copy.deepcopy)[bool(deep)](obj.__dict__)
  7. def Restore():
  8. obj.__dict__.clear()
  9. obj.__dict__.update(state)
  10. return Restore
  11. class Transaction:
  12. """A transaction guard. This is really just
  13. syntactic suggar arount a memento closure.
  14. """
  15. deep = False
  16. def __init__(self, *targets):
  17. self.targets = targets
  18. self.Commit()
  19. def Commit(self):
  20. self.states = [Memento(target, self.deep) for target in self.targets]
  21. def Rollback(self):
  22. for st in self.states:
  23. st()
  24. class transactional(object):
  25. """Adds transactional semantics to methods. Methods decorated with
  26. @transactional will rollback to entry state upon exceptions.
  27. """
  28. def __init__(self, method):
  29. self.method = method
  30. def __get__(self, obj, T):
  31. def transaction(*args, **kwargs):
  32. state = Memento(obj)
  33. try:
  34. return self.method(obj, *args, **kwargs)
  35. except:
  36. state()
  37. raise
  38. return transaction
  39. class NumObj(object):
  40. def __init__(self, value):
  41. self.value = value
  42. def __repr__(self):
  43. return '<%s: %r>' % (self.__class__.__name__, self.value)
  44. def Increment(self):
  45. self.value += 1
  46. @transactional
  47. def DoStuff(self):
  48. self.value = '1111' # <- invalid value
  49. self.Increment() # <- will fail and rollback
  50. if __name__ == '__main__':
  51. n = NumObj(-1)
  52. print(n)
  53. t = Transaction(n)
  54. try:
  55. for i in range(3):
  56. n.Increment()
  57. print(n)
  58. t.Commit()
  59. print('-- commited')
  60. for i in range(3):
  61. n.Increment()
  62. print(n)
  63. n.value += 'x' # will fail
  64. print(n)
  65. except:
  66. t.Rollback()
  67. print('-- rolled back')
  68. print(n)
  69. print('-- now doing stuff ...')
  70. try:
  71. n.DoStuff()
  72. except:
  73. print('-> doing stuff failed!')
  74. import sys
  75. import traceback
  76. traceback.print_exc(file=sys.stdout)
  77. pass
  78. print(n)
  79. ### OUTPUT ###
  80. # <NumObj: -1>
  81. # <NumObj: 0>
  82. # <NumObj: 1>
  83. # <NumObj: 2>
  84. # -- commited
  85. # <NumObj: 3>
  86. # <NumObj: 4>
  87. # <NumObj: 5>
  88. # -- rolled back
  89. # <NumObj: 2>
  90. # -- now doing stuff ...
  91. # -> doing stuff failed!
  92. # Traceback (most recent call last):
  93. # File "memento.py", line 91, in <module>
  94. # n.DoStuff()
  95. # File "memento.py", line 47, in transaction
  96. # return self.method(obj, *args, **kwargs)
  97. # File "memento.py", line 67, in DoStuff
  98. # self.Increment() # <- will fail and rollback
  99. # File "memento.py", line 62, in Increment
  100. # self.value += 1
  101. # TypeError: Can't convert 'int' object to str implicitly
  102. # <NumObj: 2>