0

I've got some methods which have attributes (to use .Net terminology). Something like.

#Example Usage
@RequireLoggedIn
def SomeAuthorisedFunction():
    # ...

The attribute is defined as

def RequireLoggedIn(func):
    func.AuthenticationRequired = True
    return func

I can then check if this has been set using hasattr(view_func, 'AuthenticationRequired'). What I'd like to do is something like this...

@RequireRole('Administrator')
def SomeAuthorisedFunction():
    # ...

I've tried defining an attribute like this:

def RequireRole(func, Role):
    func.RequiresRole = Role
    return func

But @RequireRole('Administrator') results in missing positional argument "Role" and @RequireRole(Role='Administrator') results in missing positional argument "func".

How can I specify properties for attributes?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Basic
  • 26,321
  • 24
  • 115
  • 201

2 Answers2

3

You need to create a nested function; the RequireRole function should return the actual decorator function:

def RequireRole(Role):
    def decorator(func):
        func.RequiresRole = Role
        return func
    return decorator

The @expression syntax is nothing more than syntactic sugar, the following setup:

@decorator
def foo(): pass

is translated to:

def foo(): pass
foo = decorator(foo)

instead. But the decorator part can itself be an expression too; that is how you are trying to use it. So when you use @RequireRole('Administrator') as a decorator, you really do this:

def SomeAuthorisedFunction():
    # ...
SomeAuthorisedFunction = RequireRole('Administrator')(SomeAuthorisedFunction)

Note the double function call there! This is why my version of RequireRole() returns a nested function. This is what happens when you apply it:

  1. def SomeAuthorizedFunction(): functionbody is executed.
  2. RequireRole('Administrator') is executed. It returns a scopeddecorator()` function.
  3. This returned function is called and passed in the SomeAuthorizedFunction function from step 1.
  4. The scoped decorator() function adds the .RequiresRole attribute to the function, with the scoped Role value passed into RequireRole in step 2. The function is returned.
  5. Python assigns the return value of step 4 to the name SomeAuthorizedFunction.
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
1

Your RequireRole function must return a decorator function. This can easily be accomplished via a nested function, like so:

>>> def RequireRole(role):
...   def RequireRole(func):
...     func.RequiresRole = role
...     return func
...   return RequireRole
... 
>>> @RequireRole('Administrator')
... def SomeAuthorisedFunction():
...   pass
... 
>>> print SomeAuthorisedFunction.RequiresRole
Administrator
>>> 
Robᵩ
  • 163,533
  • 20
  • 239
  • 308