How do I modify parameters stored by functools.wraps?

985 views Asked by At

I have a decorator that validates some parameters and passes an validated key to various functions:

from functools import wraps

ref validate(f):
    @wraps(f) # This is to ensure docstrings are passed through the decorated function
    def redirect_if_invalid(request, *args, **kwargs):
        if request.valid == False:
            return HttpResponseRedirect('/login')
        else:
            newkwargs = { 'key': request.key }
        return f(request, *args, **newkwargs)
return redirect_if_invalid

This is used by some other functions:

@validate
def genericHandler(request, key)
   pass

I'd call the function like this:

genericHandler(request)

And the decorator generates the 'key' kwarg. However, I'd like to optionally pass in the key at some other point, ie call:

genericHandler(request, 'keydata')

Currently this gives me an error:

TypeError: genericHandler() got multiple values for keyword argument 'key'

How can I get around this? To reiterate, the main goal is to be able to call genericHandler() with or without the optional parameter, and have the decorator generate the parameter only if it's missing.

So far inside the decorator, I can't figure out how to determine whether the 'key' parameter was passed in or not because functools.wraps() seems to hide it.

2

There are 2 answers

2
Nick Olson-Harris On

There's not any reasonable way to do this if you want your wrapper's signature to still be (request, *args, **kwargs). On the other hand, it looks like your decorator already assumes that the wrapped function takes a key parameter, so why not rewrite the wrapper to take one as well? In that case it becomes trivial to check if it's been passed or not.

def validate(f):
    @wraps(f)
    def redirect_if_invalid(request, key=None):
        # do the validation
        if key is None:
            key = request.key
        return f(request, key)
    return redirect_if_invalid

You can add the *args and **kwargs parameters back if you like, of course.

0
dragonx On

So the best way for me to do this was to explicitly pass kwargs as kwargs. So decorated functions should actually be:

@validate
def genericHandler(request, **kwargs)
    key = kwargs.get('key')
    pass

This way, I can call the function either with or without args:

genericHandler(request)

or

genericHandler(request, **{ 'key' : key })

And the actual decorated looks like:

def validate(f):
    @wraps(f) # This is to ensure docstrings are passed through the decorated function
    def redirect_if_invalid(request, *args, **kwargs):
        key = kwargs.get('key')
        if not key:
            kwargs.set('key', request.key)
        return f(request, *args, **kwargs)
    return redirect_if_invalid