zmbq
zmbq

Reputation: 39013

Decorator chain

I'm building a few decorators based on Django's @permission_required decorator. These decorators simply call the permission_required decorator with different parameters.

For example:

def permission_required_ajax(perm):
    return permission_required(perm, raise_exception=True)

This works well, and I have several such examples.

The problem is when my decorator accepts no arguments at all:

def special_permission():
    return permission_required_ajax('special_permission')

This doesn't work. When I decorate a function with the @special_permission decorator, I get the following error:

TypeError: special_permission() takes 0 positional arguments but 1 was given

What am I missing?

Upvotes: 3

Views: 304

Answers (2)

6502
6502

Reputation: 114481

How decorator parameters work

When a decorator has no parameters it gets passed the function/class being decorated. When it has parameters it's first called with the parameters and then whatever is returned is then called with the function/class being decorated.

To recap:

# Case 1, no arguments

@foo
def bar(): pass

The function foo will be called being passed the function bar and whatever is returned is then bound to the name bar.

# Case 2

@foo(1, 2, 3)
def bar(): pass

The function foo is called passing 1, 2 and 3 as parameters. What foo returns is then called passing the bar function as argument and what it returns is the bound to the name bar.

For your case

A solution could be just adding parenthesis in the special_permission decorator call...

@special_permission()  # <-- note the parenthesis
def bar(): ...

Alternatively if you don't like adding the parenthesis in the use of the decorator it could be implemented as:

def special_permission(f):
    return permission_required_ajax('special_permission')(f)

Upvotes: 1

Martijn Pieters
Martijn Pieters

Reputation: 1121744

You need to still call your decorator factory:

@special_permission()
def function():
    # ...

Whatever the expression after the @ sign produces is used as the decorator; the decorator is called, passing in the function to decorate.

In other words, a decorator is syntactic sugar for:

def function():
    # ...
special_permission()(function)

If you leave off the () call, the function is passed to your wrapper instead.

Alternatively, have your decorator accept the function, and pass it on directly to the decorator produced by the permission_required_ajax() call:

def special_permission(func):
    return permission_required_ajax('special_permission')(func)

and then use it without calling (it is now a decorator, not a decorator factory):

@special_permission  # no () call now
def function():
    # ...

Upvotes: 6

Related Questions