Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python decorator access argument by name

I want to make a Python decorator that can take an argument (the name of an argument in the function it is decorating) and use that argument to find the value of the argument in the function. I know that sounds super confusing, ha! But what I want to do would look like this (it's for a Flask web application):

This is what I have right now:

@app.route('/admin/courses/<int:course_id>')
def view_course(course_id):
    ... view code here

And I want to do something like this:

@app.route('/admin/courses/<int:course_id>')
@access_course_permission(course_id)
def view_course(course_id):
    ... view code here

Right now I know I can do something like:

def access_course_permission(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # check args[0] right here to find the course ID
        return f(*args, **kwargs)
    return decorated_function

which works great as long as course_id is always the first parameter. However, it isn't. That's why I want to be able to specify the name.

If this is not possible, I guess I could just pass the index of the parameter to the decorator, but that isn't very nice...

like image 882
ian93 Avatar asked Jun 09 '16 17:06

ian93


1 Answers

You can use the inspect.getfullargspec() function to access the names used in a function:

try:
    # Python 3
    from inspect import getfullargspec
except ImportError:
    # Python 2, use inspect.getargspec instead
    # this is the same function really, without support for annotations
    # and keyword-only arguments
    from inspect import getargspec as getfullargspec

from functools import wraps

def access_course_permission(argument_name):
    def decorator(f):
        argspec = getfullargspec(f)
        argument_index = argspec.args.index(argument_name)
        @wraps(f)
        def wrapper(*args, **kwargs):
            try:
                value = args[argument_index]
            except IndexError:
                value = kwargs[argument_name]
            # do something with value
            return f(*args, **kwargs)
        return wrapper
    return decorator

The above finds out at what index your specific argument is positioned; this covers both positional and keyword arguments (because in Python, you can pass in a value for a keyword argument by position too).

Note however that for your specific example, Flask will call view_course with course_id as a keyword argument, so using kwargs[argument_name] would suffice.

You'll have to pass in a string to name that argument:

@app.route('/admin/courses/<int:course_id>')
@access_course_permission('course_id')
def view_course(course_id):
    # ... view code here

Note however, that in Flask you could just access request.view_args, without the need to parse this information out of the function arguments:

course_id = requests.view_args[argument_name]
like image 68
Martijn Pieters Avatar answered Oct 30 '22 18:10

Martijn Pieters