26 June 2008

A foray into decorators and closures

Recently, I had to do some debugging on a project's python module that handled SOAP web services. I had written that module a while back and am still kind of proud of it.

The problem was that I needed different error handling via exceptions for different methods. Some of the methods needed one type of error handling and some needed another. What I wanted to do was to centralize the error handling. The alternative (at least the one that came to mind) was to duplicate the error handling in each method. Yuck.

Python has a nice feature called decorators which, to my understanding, allows one to wrap a method with some other code. I wrapped each applicable method that needed this error handling with an error handling decorator.

Enough narrative, let's look at the code!

from socket import error as SocketError
import datetime
import re
from pylib.util import config
from ZSI.client import Binding
from ZSI import ZSIException


class WebServiceError(Exception): pass
class InvalidURLError(WebServiceError): pass
class DatabaseNotReadyError(WebServiceError): pass


def error_handle(fn):
'''A decorator to wrap web service calls with consistent error handling.'''
def _execute(*args, **kwargs):
try:
result = fn(*args, **kwargs)
except (SocketError, TypeError), e:
raise WebServiceError(e)
except ZSIException, e:
# Handle SOAP Faults.
# Check specifically if the SOAP message starts with an ODBC error string.
# If it does then the FileMaker server is unavailable.
if re.search(_WebService.odbc_error_pattern, str(e)):
raise DatabaseNotReadyError(e)
else:
raise WebServiceError(e)
except Exception, e:
raise WebServiceError("Unhandled exception: %s" % e)
else:
return result
return _execute


Later in the program I invoke the decorator be preceding the method definition. The method that follows is effectively wrapped. For example,

@error_handle
def getSettings(self):
result = self.binding.getSettings()
return result