Using a decorator to *remove* parameters from a call

Michel Albert exhuma at gmail.com
Mon Apr 13 12:11:01 CEST 2009


A small foreword: This might look like a cherrypy-oriented post, and
should therefore go to the cherrypy group, but if you read to the end,
you'll see it's a more basic python problem, with cherrypy only as an
example. ;)

>From the decorator PEP (318) I get it that you can /add/ parameters to
a call. Say you do something like this:

@mydeco( foo="bar" )
def myfunc( hello ):
   print foo, foo

However, this is not what I would like to do. I would like to take
away one or more attributes using a decorator. My goal is to
centralize the logic associated with that parameter. In my particular
example, I am writing a cherrypy application (more specifically,
turbogears1) and I would like all controller method to accept a "lang"
and a "skin" attribute. As a simple, but hands-on example, imagine
code like this (http://python.pastebin.com/f25f2429b):

class MyController(Controller):

    @expose()
    def index(self, skin="default", lang="en"):
        set_skin( skin )
        set_language( lang )
        return "Hello skinned world"

    @expose()
    def foo(self, skin="default", lang="en"):
        set_skin( skin )
        set_language( lang )
        return "bar"

This becomes cumbersome however for a large application with many
controllers and methods. Always adding the parameters to the methods
and function calls into the method bodies looks way to repetitive for
my taste.

Currently I solve this by using a cherrypy filter which removes those
parameters from the HTTP-Request and puts them into the session
object. This looked wrong to me though right from the start. Also, the
cherrypy docs advise against the use of "filters" for application
logic. But this looks pretty much like that to me.... somewhat. Worse
yet, filters are not supported in Turbogears2 any longer. Which makes
porting my app not straight-forward, as I have to port my filters and
write WSGI middleware instead.

I would prefer a syntax like this (http://python.pastebin.com/
f462bc29c)

class MyController(Controller):

    @expose()
    @skinnable
    @translatable
    def index(self):
        return "Hello skinned world"

    @expose()
    @skinnable
    @translatable
    def foo(self):
        return "bar"

This, to me has several advantages: The logic is "encapsulated" in the
application code itself. I do not need to rely on framework specific
features (be it cherrypy or wsgi). This would make the application
more self-contained and hence more future proof.

But right here lies my problem. If you are not familiar with CherryPy,
let me digress for a tiny bit to give you the necessary backgroud:

Inside a controller, an "exposed" method is reachable via HTTP
requests. It's possible to force request parameters. If that is the
case (as in my case it is), then the call will only be successful, if
all parameters received a vaule and no unknown parameters have been
passed. Assume the following signature:

def index(self)

This will successfully return an HTTP Response when the client
requested the resource on the URL "/index". If the client adds a query
string, say "/index?lang=en" the call will *fail* as this parameter is
unkown. For this to work the signature must read:

def index(self, lang)

or

def index(self, lang="en")

Right.... end of digression, back to topic:

My ultimate question is: Is it possible to write a decorator that
removes a parameter from a call and return a function without that
parameter? Something along the lines (http://python.pastebin.com/
f257877cd):

def translatable(f):
    "Note that this code is only an example and will not work!"
    lang = f.__get_parameter_value__("lang")
    f.__remove_parameter__("lang")
    do_something_with_lang(lang)
    return f

I am aware, that this depends on *when* the decorator call is
executed. If its called whenever the decorated function is called,
then there should be some way to handle this. If it's called at
"compile-time", then I suppose it's not going to be possible.

A usage scenario would be this:

# --- definition ---------------
@translatable
def index(self):
   return _("Hello", lang)

# --- call ----------------------
obj.index( lang="de")
obj.index()

As you can see, the method definition does not contain the "lang"
parameter in the signature, but I want to be able to (optionally) set
it during a call.

I hope I made my ideas clear enough. I would be very positively
surprised if this worked. It would make my application code much
easier to read, understand and follow. The filters currently do a lot
of magic "behind-the-scenes" and if somebody else needs to jump in and
code on that application they will surely ask themselves "where the
****** is that ***** lang variable set?" :) And I'd like to make it as
easy as possible for everyone :)

Or, maybe you even have a better solution in mind? I'm open for
suggestions.



More information about the Python-list mailing list