views:

458

answers:

2

I am developing an application on the Google App Engine using Python.

I have a handler that can return a variety of outputs (html and json at the moment), I am testing for obvious errors in the system based on invalid parameters sent to the request handler.

However what I am doing feels dirty (see below):

class FeedHandler(webapp.RequestHandler):
def get(self):
 app = self.request.get("id")
 name = self.request.get("name") 
 output_type = self.request.get("output", default_value = "html")
 pretty = self.request.get("pretty", default_value = "")


 application = model.Application.GetByKey(app)

 if application is None:
  if output_type == "json":
   self.response.out.write(simplejson.dumps({ "errorCode" : "Application not found."}))
  self.set_status(404)
  return

 category = model.FeedCategory.GetByKey(application, name)

 if category is None:
  if output_type == "json":
   self.response.out.write(simplejson.dumps({ "errorCode" : "Category not found."}))
  self.set_status(404)
  return

I am specifically handling cases per output type and also and per "assert".

I am keen to here suggestions, patterns and examples on how to clear it up (I know it is going to be a nightmare to try and maintain what I am doing).

I am toying with the idea of having and raising custom exceptions and having a decorator that will automatically work out how to display the error messages - I think it is a good idea but I am would love to get some feedback and suggestions based on how people have done this in the past.

A: 

As least, you should refactor repetitive code such as:

if application is None:
    if output_type == "json":
        self.response.out.write(simplejson.dumps({ "errorCode" : "Application not found."}))
        self.set_status(404)
        return

into an auxiliary method:

def _Mayerr(self, result, msg):
    if result is None:
        if output_type == 'json':
            self.response.out.write(simplejson.dumps(
                {"errorCode": msg})
        self.set_status(404)
        return True

and call it e.g. as:

if self._Mayerr(application, "Application not found."):
    return

Beyond that, custom exceptions (and wrapping all your handlers with a decorator that catches the exceptions and gives proper error messages) is an excellent architecture, though it's more invasive (requires more rework of your code) than the simple refactoring I just mentioned, the extra investment right now may well be worthwhile in preventing repetitious and boilerplatey error handling spread all over your application level code!-)

Alex Martelli
Yeah it definately needs a refactor. I am quite keen on the custom exceptions and decorator route although I might have to also alter how I do the views - at the moment it is a self.response.out.write(). I might return a view object that is rendered and have the decorator (or some other method render the response).
Kinlan
+2  A: 

There's a couple of handy methods here. The first is self.error(code). By default this method simply sets the status code and clears the output buffer, but you can override it to output custom error pages depending on the error result.

The second method is self.handle__exception(exception, debug_mode). This method is called by the webapp infrastructure if any of your get/post/etc methods return an unhandled exception. By default it calls self.error(500) and logs the exception (as well as printing it to the output if debug mode is enabled). You can override this method to handle exceptions however you like. Here's an example to allow you to throw exceptions for various statuses:

class StatusCodeException(Exception):
  def __init__(self, code):
    self.status_code = code

class RedirectException(StatusCodeException):
  def __init__(self, location, status=302):
    super(RedirectException, self).__init__(status)
    self.location = location

class ForbiddenException(StatusCodeException):
  def __init__(self):
    super(ForbiddenException, self).__init__(403)

class ExtendedHandler(webapp.RequestHandler):
  def handle_exception(self, exception, debug_mode):
    if isinstance(exception, RedirectException):
      self.redirect(exception.location)
    else:
      self.error(exception.status_code)
Nick Johnson
Love it - had no idea that handle_exception even existed... (Even though I saw the page earlier :) I am pretty sure that this will help - I presume that I can do self.response.out.write in the handle_exception to output json?
Kinlan
Yup. handle_exception is part of the regular request-response cycle, so you can do anything you want in there.
Nick Johnson
Sweet - it will clear up my code no end :)
Kinlan