# # Copyright (c) 2018 Red Hat, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # """ Transaction - provides transactional support $Id: //eng/vdo-releases/magnesium/src/python/vdo/utils/Transaction.py#1 $ """ from functools import wraps import gettext import sys import threading gettext.install('Transaction') class Transaction(object): """Client-visible transaction object. """ # A dictionary mapping thread identifier to a list of the thread's # transactions. __transactionLists = {} ###################################################################### # Public methods ###################################################################### @classmethod def transaction(cls): """Returns the current transaction for the current thread. """ return cls._threadTransactionList()[-1] ###################################################################### def addUndoStage(self, undoStage): """Adds callable undoStage to transaction stages. Arguments: undoStage (callable) - callable to execute as part of transaction roll-back """ self.__undoStages.append(undoStage) ###################################################################### def setMessage(self, handleMessage, message = None): """Sets the message to handle if there is an exception. If message is specified and an exception occurs the exception will be appended to the message using "; {0}" where "{0}" represents the exception. If message is not specified, the exception will be coerced to a string and passed to handleMessage. Specifying handleMessage as None will clear both handleMessage and message. Arguments: handleMessage (callable) - method to handle message message (string) - message """ raise NotImplementedError ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(Transaction, self).__init__() self.__undoStages = [] ###################################################################### # Protected methods ###################################################################### @classmethod def _threadTransactionList(cls): """Returns the transaction list for the current thread, creating it if need be. Returns: list of Transactions """ transactionList = None threadId = threading.currentThread().ident try: transactionList = cls.__transactionLists[threadId] except KeyError: transactionList = [] cls.__transactionLists[threadId] = transactionList return transactionList ###################################################################### def _undoStages(self): """Returns the list of undo stages for the transaction. Returns: list of undo stages """ return self.__undoStages ###################################################################### # Private methods ###################################################################### ######################################################################## def transactional(func): """Method decorator providing transactional capabilities to the method. """ ###################################################################### class _Transaction(Transaction): """Decorator-local transaction object providing actual transaction capabilities. """ #################################################################### # Public methods #################################################################### @classmethod def addTransaction(cls): """Adds, and returns, a transaction to the transaction list for the current thread. """ cls._threadTransactionList().append(cls()) return cls.transaction() #################################################################### @classmethod def removeTransaction(cls): """Removes the current transaction for for the current thread. """ cls._threadTransactionList().pop() #################################################################### def undo(self, exception): """Performs the undo processing of the transaction. Exceptions from the undo stages are ignored. Arguments: exception - the exception which resulted in undo being called """ # Handle any message that was set for the transaction. if self.__handleMessage is not None: if self.__message is not None: self.__handleMessage(_("{0}; {1}").format(self.__message, str(exception))) else: self.__handleMessage(str(exception)) # Perform the undo processing. undoStages = self._undoStages()[:] undoStages.reverse() for undoStage in undoStages: try: undoStage() except Exception: pass #################################################################### # Overridden methods #################################################################### def __init__(self): super(_Transaction, self).__init__() self.__handleMessage = None self.__message = None #################################################################### def setMessage(self, handleMessage, message = None): self.__handleMessage = handleMessage if self.__handleMessage is None: self.__message = None else: self.__message = message #################################################################### # Protected methods #################################################################### #################################################################### # Private methods #################################################################### ###################################################################### @wraps(func) def wrap(*args, **kwargs): """Wrapper method providing transactional processing capabilities. """ transaction = _Transaction.addTransaction() result = None try: result = func(*args, **kwargs) except Exception as ex: transaction.undo(ex) # Re-raise the Exception, preserving the stack trace from its origin. #pylint: disable=E0710 raise type(ex), ex, sys.exc_info()[2] finally: _Transaction.removeTransaction() return result return wrap