decorator to set attributes of function

11,954

Solution 1

You are checking the attribute on the inner (wrapper) function, but set it on the original (wrapped) function. But you need a wrapper function at all:

def permission(permission_required):
    def decorator(func):
        func.permission_required = permission_required
        return func
    return decorator

Your decorator needs to return something that'll replace the original function. The original function itself (with the attribute added) will do fine for that, because all you wanted to do is add an attribute to it.

If you still need a wrapper, then set the attribute on the wrapper function instead:

from functools import wraps

def permission(permission_required):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # only use a wrapper if you need extra code to be run here
            return func(*args, **kwargs)
        wrapper.permission_required = permission_required
        return wrapper
    return decorator

After all, you are replacing the wrapped function with the wrapper returned by the decorator, so that's the object you'll be looking for the attribute on.

I also added the @functools.wraps() decorator to the wrapper, which copied across important identifying information and other helpful things from func to the wrapper, making it much easier to work with.

Solution 2

Your decorator should return a function that can replace do_x or do_y , not return the execution result of do_x or do_y You can modity you decorate as below:

def permission(permission_required):
    def wrapper(func):
        def inner():
            setattr(func, 'permission_required', permission_required)
            return func
        return inner()
    return wrapper

Of course, you have another brief solution:

def permission(permission_required):
    def wrapper(func):
        setattr(func, 'permission_required', permission_required)
        return func
    return wrapper
Share:
11,954

Related videos on Youtube

Code Review Doctor
Author by

Code Review Doctor

I'm a review Pull requests on GitHub to improve your Django code

Updated on June 04, 2022

Comments

  • Code Review Doctor
    Code Review Doctor about 2 years

    I want different functions to be executable only if the logged in user has the required permission level.

    To make my life more simple I want to use decorators. Below I attempt to set attribute permission on 'decorated' functions - as shown below.

    def permission(permission_required):
        def wrapper(func):
            def inner(*args, **kwargs):
                setattr(func, 'permission_required', permission_required)
                return func(*args, **kwargs)
            return inner
        return wrapper
    
    @permission('user')
    def do_x(arg1, arg2):
    
        ...
    
    @permission('admin')
    def do_y(arg1, arg2):
        ...
    

    But when I do:

    fn = do_x
    if logged_in_user.access_level == fn.permission_required:
        ...
    

    I get an error 'function' object has no attribute 'permission_required'

    What am I missing?

    • abarnert
      abarnert about 11 years
      As a side note: I'm pretty sure you want to use functools.wraps here. Not to directly solve your problem, but because it's next to impossible to debug this kind of code when every functions ends up named inner, taking (*args, **kwargs), inspecting to the wrong source, etc.
  • Code Review Doctor
    Code Review Doctor about 11 years
    I need the wrapper to allow for arguments in the decorated function. I used your code and it worked great - but when I added wrapper for arguments then the error came back.
  • Martijn Pieters
    Martijn Pieters about 11 years
    Your inner function does nothing, so you are replacing the original with a function that does nothing more than set an attribute on the wrapped function, making it useless.
  • abarnert
    abarnert about 11 years
    @rikAtee: You don't need a wrapper to allow for arguments in the decorated function. The first example just modifies and returns the function; it still takes the exact same arguments it did before being decorated.
  • Martijn Pieters
    Martijn Pieters about 11 years
    @rikAtee: Indeed, the wrapper is not required if all you do is set the attribute. Only add a wrapper if there is a need for a wrapper (e.g. adds extra code to operate on the arguments or the return values, or do extra things when the function is called).
  • minmaxavg
    minmaxavg over 8 years
    Don't forget to decorate wrapper with @wraps (in functools), so that the decorated function will have its attributes kept (including __doc__)!