Using a decorator to *remove* parameters from a call

M

Michel Albert

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.
 
A

Aaron Brady

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 snip
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"
snip
more future proof.
snip

Sure. Just pop the keywords you want from the dictionary, if they
exist.

def take_ab( fun ):
def newfun( *ar, **kw ):
aval= kw.pop( 'a', None )
bval= kw.pop( 'b', None )
print( 'aval %r and bval %r popped'% ( aval, bval ) )
res= fun( *ar, **kw )
return res
return newfun

@take_ab
def f( c= 42, d= 'some' ):
print( 'in f, c= %r, d= %r'% ( c, d ) )

f( a= 'paramA', b= 'paramB', c= 'other number' )

/Output:

aval 'paramA' and bval 'paramB' popped
in f, c= 'other number', d= 'some'

As you can see, 'a', 'b', and 'c' were passed to 'f', but 'f' only
received 'c', as well as its default value for 'd'. So long as you
only need to pass by keyword, not position, it's pretty easy. It's
harder if the parameter you want might have been passed by either, and
you need to remove it from whichever one it was passed by.
 
J

Jon Clements

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:

Haven't finished coffee yet, plus it's a bank holiday! [/excuse]

Can't that be changed to def index(self, **kwdargs) -- then your
decorators can operate something like set_language(kwdargs.get('lang',
'en')) and then delete the key....

Anyway, back to more coffee!
 
M

Michel Albert

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)

def index(self, lang="en")
Right.... end of digression, back to topic:

Haven't finished coffee yet, plus it's a bank holiday! [/excuse]

Can't that be changed to def index(self, **kwdargs) -- then your
decorators can operate something like set_language(kwdargs.get('lang',
'en')) and then delete the key....

Sure. It could work like that. But that, in my case, would mean that
anyone could pass any parameters into that method. That should not
make any problems, but I prefer enforcing only the parameters I expect
(and /only/ those) to be passed into the method. If only for my own
sanity ;)
 
M

Michel Albert

snip

Sure.  Just pop the keywords you want from the dictionary, if they
exist.

def take_ab( fun ):
    def newfun( *ar, **kw ):
        aval= kw.pop( 'a', None )
        bval= kw.pop( 'b', None )
        print( 'aval %r and bval %r popped'% ( aval, bval ) )
        res= fun( *ar, **kw )
        return res
    return newfun

@take_ab
def f( c= 42, d= 'some' ):
    print( 'in f, c= %r, d= %r'% ( c, d ) )

f( a= 'paramA', b= 'paramB', c= 'other number' )

/Output:

aval 'paramA' and bval 'paramB' popped
in f, c= 'other number', d= 'some'

As you can see, 'a', 'b', and 'c' were passed to 'f', but 'f' only
received 'c', as well as its default value for 'd'.  So long as you
only need to pass by keyword, not position, it's pretty easy.  It's
harder if the parameter you want might have been passed by either, and
you need to remove it from whichever one it was passed by.

Thanks. That works like a charm. I tried that last night and it gave
me an error. So I must have had a typo in there somewhere. Not enough
sleep I guess ;)
 
A

azeem

snip

Sure.  Just pop the keywords you want from the dictionary, if they
exist.

def take_ab( fun ):
    def newfun( *ar, **kw ):
        aval= kw.pop( 'a', None )
        bval= kw.pop( 'b', None )
        print( 'aval %r and bval %r popped'% ( aval, bval ) )
        res= fun( *ar, **kw )
        return res
    return newfun

@take_ab
def f( c= 42, d= 'some' ):
    print( 'in f, c= %r, d= %r'% ( c, d ) )

f( a= 'paramA', b= 'paramB', c= 'other number' )

/Output:

aval 'paramA' and bval 'paramB' popped
in f, c= 'other number', d= 'some'

As you can see, 'a', 'b', and 'c' were passed to 'f', but 'f' only
received 'c', as well as its default value for 'd'.  So long as you
only need to pass by keyword, not position, it's pretty easy.  It's
harder if the parameter you want might have been passed by either, and
you need to remove it from whichever one it was passed by.


What does the @take_ab do ?
 
A

Aaron Brady

What does the @take_ab do ?

Hi. It's a function decorator. It replaces 'f' with another
function, which is impersonating it.

In particular, the meaning of this decorator is to remove any and all
occurrences of 'a' or 'b' in the keywords that are passed to 'f'.

So: If you call the impersonated 'f' like this:

f( 1, 2 )

Then the call proceeds as usual. If you call it like this:

f( 1, 2, a= 'jamqar' ),

Then the real 'f' only receives this:

f( 1, 2 )

Unfortunately, to the world's loss, the original 'f' isn't practically
accessible anymore to anyone but the hijacker. In fact, if you look
closely, you will find its internal name stamp is not 'f', but
'newfun'!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top