Script Benchmarking with Decorators Example

I made something similar to what you are aiming for but its also made for managing database transactions and errors in a standard way by creating a Response object that then I inspect after.

import functools
import timeit
import traceback
from forms2.structs import Response
import java.lang.Throwable

class handleTransaction(object):
	def __init__(self, logger):
		self.logger = logger

	def __call__(self, inner_func):
		"""
		We do 'tx' not in kwargs a few times here.  If a fucntion has tx=None and is decorated with this, we generate the transatction.
		However, sometimes we want to string together multiple transactions into one larger transcation.  So by passing it in directly and checking for
		'tx' not in kwargs, we only close it for a self contained decorated function.  If tx is passed, then the transaction lives on.  However,
		then you must manually close it.
		Look to forsm2.tests.runAllTest for an example of this.		
		"""
		@functools.wraps(inner_func)
		def wrapped_func(*args, **kwargs): 
			start_time = timeit.default_timer()
			# A transaction was not provided, we create one
			if 'tx' not in kwargs:
				transaction_config = forms2.common.TRANSACTION_CONFIG
				tx = system.db.beginNamedQueryTransaction(**transaction_config)
				kwargs['tx'] = tx
				self.logger.info("Starting transaction %s"%(tx))
			self.logger.info("Proceeding using transaction %s"%(kwargs['tx']))
			try:
				result = inner_func(*args, **kwargs)
				response = Response(success=True, result=result, error=None,errorType=None)
			except Exception, e:
				self.logger.error("Python error occured")
				self.logger.error(str(e))
				self.logger.error(traceback.format_exc())
				response = Response(success=False, result=None, error=str(e), errorType='Python')
			except java.lang.Throwable, e:
				self.logger.error("Java Error Occured")
				self.logger.error(str(e.cause))
				self.logger.error(traceback.format_exc())
				response = Response(success=False, result=None, error=str(e.cause), errorType='Java')
			finally:
				if response.success:
					self.logger.info("Committing transaction %s"%(str(kwargs['tx'])))
					system.db.commitTransaction(kwargs['tx'])
				else:
					self.logger.info("Rolling back transaction %s"%(str(kwargs['tx'])))
					system.db.rollbackTransaction(kwargs['tx'])
				self.logger.info("Closing Transaciton %s"%(str(kwargs['tx'])))
				system.db.closeTransaction(kwargs['tx'])
				# At this point we would parse the result object and return an appropraite?  Or return entire result
				elapsed = timeit.default_timer() - start_time
				self.logger.info("Finished transaction in %f seconds"%(elapsed))
				return response
		return wrapped_func

and response

class Response(object):
	def __init__(self, success=False, result=None, error=None, errorType=None):
		self.success = success
		self.result = result
		self.error = error
		self.errorType = errorType
	
	def __str__(self):
		if self.success:
			return "Success: True, Result: %s"%(str(self.result))
		elif self.errorType and self.error:
			return "Error Type %s Error Text: %s"%(self.errorType, self.error)
		else:
			return "Success: False, Result: None, Error Type: None, Error Text: None"

Let's me write my functions that only deal with the actual database actions and let this decorator handle all committing/reverting/closing the transaction. Works pretty well in my experience so far.

3 Likes