I’ve encountered a situation where I’m working over a piece of code where I command changes on a remote object (that is one I can’t duplicate to work over a clone), then ask the remote object for some operation in the new state and revert all the changes I made to it by a sequence of opposite commands.
The problem is that if in the middle of all these changes I encounter an error, I want to be able to roll-back all the changes I made so far.
The best fitting solution that came to my mind is the python try-finally workflow, but it’s rather problematic when the sequence of commands is long:
try:
# perform action
try:
# perform action
try:
# ...
finally:
# unroll
finally:
# unroll
finally:
# unroll
This way, the more commands I need the deeper my indentation and nesting goes and the less my code is readable.
I’ve considered some other solutions such as maintaining a stack where for every command I push a rollback action, but this could get rather complicated, and I dislike pushing bound methods into stacks.
I’ve also considered incrementing a counter for every action I perform then in a single finally decide on the kind of rollback I want depending on the counter, but again, the maintainability of such code becomes a pain.
Most hits I got on searches for “transactions” and “rollback” were DB related and didn’t fit very well to a more generic kind of code…
Anyone has a good idea as to how to systematically flatten this atrocity?
Wouldn’t Context Manager objects and the with statement improve the situation? Especially if you can use a version of Python where the with statement supports multiple context expressions, as 2.7 or 3.x. Here’s an example:
You’d have to move your code to a set of “transactional” classes, such as
Actionabove, where the action to be performed is executed in the__enter__()method and, if this terminates normally, you would be guaranteed that the corresponding__exit()__method would be called.Note that my example doesn’t correspond exactly to yours; you’d have to tune what to execute in the
__enter__()methods and what to execute in thewithstatement’s body. In that case you might want to use the following syntax:To be able to access the
Actionobjects from within the body of thewithstatement.