views:

622

answers:

2

In the context of a Google App Engine Webapp framework application:

I want to changed the request verb of a request in the case a parameter _method is provided, for example if a POST request comes in with a parameter _method=PUT, I need to change the request to call the put method of the handler. This is to cope with the way prototype.js works with verbs like PUT and DELETE(workaround for IE). Here is my first attempt:

class MyRequestHandler(webapp.RequestHandler):
   def initialize(self, request, response):
       m = request.get('_method')
       if m:
           request.method = m.upper()
       webapp.RequestHandler.initialize(self, request, response)

The problem is, for some reason whenever the redirect is done, the self.request.params are emptied by the time the handling method(put or delete) is called, even though they were populated when initialize was called. Anyone have a clue why this is? As a workaround I thought I could clone the params at initialize() time, but .copy() did not work, and I haven't found a way to do that either.

Update: I received a very helpful response from Arachnid. The solution I ended up with uses a metaclass. It is found below.

+3  A: 

Calling the handler from initialize isn't the right way anyway - if you do that, the webapp will then call the original handler as well.

Instead, you have a couple of options:

  • You can subclass webapp.WSGIApplication and override call to select the method based on _method when it exists.
  • You can check for the existence of _method in initialize, and if it exists, modify the request object's 'REQUEST_METHOD' environment variable accordingly. That will cause the WSGIApplication class to execute the method you choose.

Either way, take a look at google/appengine/ext/webapp/init.py in the SDK so you can see how it works.

Nick Johnson
Actually, I am attempting the second method that you described. My code reassigns request.method to the override method, but it didn't work because of some weirdness I described. But you first suggested method seems like something good to try.
toby
+2  A: 

Thats Arachnid for your response. Pointing me to the source of the framework was really helpful. Last I looked the source wasn't there(there was only .pyc), maybe it changed with the new version of the SDK. For my situation I think overriding WSGIApplication would have been the right thing to do. However, I chose to use a metaclass instead, because it didn't require me to cargo-cult(copy) a bunch of the framework code into my code and then modifying it. This is my solution:

class RequestHandlerMetaclass(type):
    def __init__(cls, name, bases, dct):
        super(RequestHandlerMetaclass, cls).__init__(name, bases, dct)
        org_post = getattr(cls, 'post')
        def post(self, *params, **kws):
            verb = self.request.get('_method')
            if verb:
                verb = verb.upper()
                if verb ==  'DELETE':
                    self.delete(*params, **kws)
                elif verb == 'PUT':
                    self.put(*params, **kws)
            else:
                org_post(self, *params, **kws)
        setattr(cls, 'post', post)

class MyRequestHandler(webapp.RequestHandler):
    __metaclass__ = RequestHandlerMetaclass
toby