Decorator chain
I am creating several decorators based on Django @permission_required
decorator. These decorators will simply call decor_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 examples like this.
The problem is that my decorator takes no arguments at all:
def special_permission():
return permission_required_ajax('special_permission')
This does not work. When I decorate the function with a decorator @special_permission
, I get the following error:
TypeError: special_permission () takes 0 positional arguments, but 1 is given
What am I missing?
source to share
You still need to call your factory decorator:
@special_permission()
def function():
# ...
Whatever the expression after the sign @
is used as a 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 the call ()
, the function is passed in instead of your shell.
Alternatively, let your decorator take this function and pass it directly to the decorator created by the call permission_required_ajax()
:
def special_permission(func):
return permission_required_ajax('special_permission')(func)
and then use it without calling (it's a decorator now, not a factory decorator):
@special_permission # no () call now
def function():
# ...
source to share
How decorator parameters work
When the decorator has no parameters, it gets the passed function / class. When it has parameters, they are first called with parameters and then whatever is returned is called with function / class decoration.
Repeat:
# Case 1, no arguments
@foo
def bar(): pass
The function foo
will be called by passing 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 it foo
returns is then called by passing the function bar
as an argument, and the return value is bound to the name bar
.
For your case
The solution might be to just add parentheses to the decoder call special_permission
...
@special_permission() # <-- note the parenthesis
def bar(): ...
Alternatively, if you don't like adding parentheses to using a decorator, this can be implemented as:
def special_permission(f):
return permission_required_ajax('special_permission')(f)
source to share