Combining context managers and decorators for nice DRY Python

I love Python's clarity and expressive power but I recently had a problem where my code was becoming excessively repetitive. In short, I was using an API which may raise a number of exceptions (HttpError, ServerNotResponding…) which I needed to report. Other errors are raised as normal. Thus I had lots of blocks of code looking something like this:

try:
    BLOCK
    return HttpResponseRedirect('somewhere good')
except (HttpError, ServerNotResponding...) as e:
    return HttpResponse(str(e), content_type="text/plain")

I used a context manager to first collate all of the errors I know how to handle into a single 'aggregation' error type (I used the API base error class but it could have been anything):

class APIError(object):
    """
    Context manager that collects all of the request and other api
    errors into a single convenient APIError exception (which has
    an original_exception attribute). Thus:
    try:
	with proxy.APIError():
	    BLOCK
    except APIError:
	BLOCK

    will handle all APIErrors.
    """
    def __init__(self):
	import inspect
	self.exc_types = [exc_type for (exc_name, exc_type) 
                          in inspect.getmembers(exceptions, callable)
			  if not isinstance(exc_type, exceptions.APIError)]

    def __enter__(self):
	return self

    def __exit__(self, exc_type, exc_value, traceback):
	if (exc_type in self.exc_types):
	    raise exceptions.APIError(exc_value)

Then I created a decorator that used this context manager thus:

def api_errors_as_http_response(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
	try:
	    with APIError():
		return func(*args, **kwargs)
	except APIError as e:
	    return HttpResponse(str(e), content_type="text/plain")
    return wrapper

Now, in my Django views I can just decorate any view function with the decorator in order to have all the remote API errors reported as plain text:

@api_errors_as_http_response
def add_app(request):
    ...

All the error handling is nicely described in the decorator and it's just one line to add to any function definition so that it will report the relevant set of errors appropriately. In the event of more complex cases arising, the context manager can be used directly.


Comments

There are currently no comments

New Comment

required

required (not published)

optional

Australia: 07 3103 2894

International: +61 410 545 357