1

Within Python, I have created a User class that may have one of two UserType's, Regular or Admin. The User class has multiple methods, and I want some of them to only be accessible by an admin.

Currently, I have this code:

from enum import Enum


class AuthorizationError(Exception):
    """Raised when a user attempts an admin-restricted task"""


class UserType(Enum):
    Regular = 0
    Admin = 1


class User:

    def __init__(self, username, user_type):
        self.username = username
        self.user_type = user_type

    def admin_required(self, func):
        def wrapper(*args, **kwargs):
            if self.user_type is UserType.Admin:
                return func(*args, **kwargs)
            else:
                raise AuthorizationError(f"User must be an admin to use {func.__name__}.")

        return wrapper

    def do_something_regular(self):
        print(f"{self.username} is doing something any regular user can do.")

    @admin_required
    def do_something_admin(self):
        print(f"{self.username} is doing something only an admin can do.")


me = User("MyUsername", UserType.Admin)
me.do_something_regular()
me.do_something_admin()

Which yields the following error:

Traceback (most recent call last):
  File "example.py", line 13, in <module>
    class User:
  File "example.py", line 31, in User
    @admin_required
TypeError: admin_required() missing 1 required positional argument: 'func'

I understand I can probably create a subclass for an admin, but the goal is to use a decorator within the User class to check for admin privileges.

I think the problem is that when I wrap the do_something_admin function, do_something_admin is passed to the self argument instead of self being passed as the instance of the class.

I have not been able to solve this problem. Keep in mind, I want to use a decorator in the solution. Thank you!

YulkyTulky
  • 886
  • 1
  • 6
  • 20
  • This question already has answers here:[decorating-python-class-methods-how-do-i-pass-the-instance-to-the-decorator](https://stackoverflow.com/questions/2365701) – stovfl Jun 28 '20 at 09:41

2 Answers2

0

The self argument is only populated when a method is bound to an instance of a class.

In this case, the referenced function (the decorator) is unbound and therefore the first argument (self) needs to be provided.

If you remove the self argument this will work eg:

class User:

    ...

    def admin_required(func):
        def wrapper(self, *args, **kwargs):
            if self.user_type is UserType.Admin:
                return func(self, *args, **kwargs)
            else:
                raise AuthorizationError(f"User must be an admin to use {func.__name__}.")

        return wrapper

   ...
Tim
  • 2,510
  • 1
  • 22
  • 26
  • No, the advice about the method being bound is correct, but using self will be problematic. – Tim Jun 28 '20 at 02:14
  • On second thoughts this will work now, the internal wrapper function will be bound to the instance. – Tim Jun 28 '20 at 02:21
  • 1
    Unless I'm misunderstanding how you intend to use this, I think it should raise: `TypeError: 'staticmethod' object is not callable` when you try to access the function inside the class. – Mark Jun 28 '20 at 02:24
  • @MarkMeyer is right, this gets me `TypeError: 'staticmethod' object is not callable` – YulkyTulky Jun 28 '20 at 02:29
  • Hmm something funky with the interaction of a decorator and staticmethod not come across that before, removing the static method decorator will fix it, however your IDE might complain. – Tim Jun 28 '20 at 03:00
0

This is a dirty trick but it will allow you to use your decorator on methods:

from enum import Enum


class AuthorizationError(Exception):
    """Raised when a user attempts an admin-restricted task"""


class UserType(Enum):
    Regular = 0
    Admin = 1

def admin_required(func):
    def wrapper(self, *args, **kwargs):
        if self.user_type is UserType.Admin:
            return func(self,*args, **kwargs)
        else:
            raise AuthorizationError(f"User must be an admin to use {func.__name__}.")

    return wrapper

class User:

    def __init__(self, username, user_type):
        self.username = username
        self.user_type = user_type

    def do_something_regular(self):
        print(f"{self.username} is doing something any regular user can do.")

    @admin_required
    def do_something_admin(self):
        print(f"{self.username} is doing something only an admin can do.")


me = User("MyUsername", UserType.Admin)
me.do_something_regular()
me.do_something_admin()

yorodm
  • 4,359
  • 24
  • 32