dease
dease

Reputation: 3076

How should I write decorators?

In my legacy code, I've found two method of writing view decorators. 1st:

def require_session(f):
    def wrap(request, *args, **kwargs):
        if not 'username' in request.session:
            messages.warning(request,_('You have to login first before you can access this page.'))
            return redirect('/login')
        return f(request, *args, **kwargs)
    wrap.__doc__=f.__doc__
    wrap.__name__=f.__name__
    return wrap

and 2nd:

def require_role(role):
    def decorator(func):
        def inner_decorator(request,*args, **kwargs):
            user_role = request.session.get('role','user')
            if user_role != role:
                if user_role == 'user':
                    return redirect('/dashboard')
                else:
                    return redirect('/dashboard/list_users')
            return func(request, *args, **kwargs)
        return wraps(func)(inner_decorator)
    return decorator

Which one is better and more "pythonic"? Or maybe should I write it in a totally different way? What is your way to write decorators?

Upvotes: 0

Views: 76

Answers (2)

Daniel Roseman
Daniel Roseman

Reputation: 599610

These do different things. The first is a standard decorator, ie it wraps a function and returns the wrapped version, that when called does some stuff before calling the original. It is used in the standard way:

@require_session
def function_that_requires_session(request):
    ...

The second is a parameterized decorator. That is, the decorator itself needs a parameter - in this case, which exact role is required - and so the definition needs to accept a parameter and then return an actual decorator, which does the same as the standard version above. That's why you have an extra level of nested functions. So, this would be used like this:

@require_role(role_that_is_required)
def function_that_requires_role(request):
    ...

As you can see, the decorator takes a parameter.

The other difference between the two is that the first is using a manual way of transferring the name/docstring to the decorated function, whereas the second is using functools.wraps(). You should do the latter (although as lvc points out, you can use it as a decorator, which is even clearer).

Upvotes: 3

lvc
lvc

Reputation: 35069

The usual way to handle copying the docstring and other metadata is to use functools.wraps, like in your second example, except that you can use its return value as a decorator:

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # do stuff

    return wrapper

Upvotes: 1

Related Questions