I am trying to re-use a member function decorator for other member function decorator but I am getting the following error:
'function' object has no attribute '_MyClass__check_for_valid_token'
Basically I have a working decorator that checks if a user is logged in (@LOGIN_REQUIRED
) and I would like to call this first in the @ADMIN_REQUIRED
decorator (so the idea is to check that the user is logged in with the existing @LOGIN_REQUIRED
decorator and then add some specific validation to check if the logged user is an Administrator in the @ADMIN_REQUIRED
decorator.
My current code is like this:
class MyClass:
def LOGIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
# username and token should be the first parameters
# throws if not logged in
self.__check_for_valid_token(args[0], args[1])
return func(self, *args, **kwargs)
return decorated_function
@LOGIN_REQUIRED
def ADMIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
is_admin = self.check_if_admin()
if not is_admin:
raise Exception()
return func(self, *args, **kwargs)
return decorated_function
@ADMIN_REQUIRED
def get_administration_data(self, username, token):
# return important_data
# currently throws 'function' object has no attribute '_MyClass__check_for_valid_token'
Do you have any idea how could I get this to work?
Some notes based on the comments and answers for clarification:
__check_for_valid_token
name can be changed to not run into name mangling issues. I was just using double underscore because it was a method supposedly only accessible by the class itself (private).@LOGIN_REQUIRED
code must run before the @ADMIN_REQUIRED
code (as that is what someone expects, at least in my case).I think this is possible, with two caveats; first, the decorator will have to move outside the class, and second, some adaptation will be required in regards to the name mangling. Let's tackle the first - first.
Decorating a decorator directly may seem intuitive, but it probably won't result with what you want. You can, however, decorate an inner function - just like how @wraps
is used. Due to how python parses code, the outer decorator will have to be defined outside (and before) the class, otherwise you'll get a NameError. The code should look something like this:
def _OUTER_LOGIN_REQUIRED(func):
@wraps(func)
def decorated_function(self, *args, **kwargs):
self.__check_for_valid_token(args[0], args[1])
return func(self, *args, **kwargs)
return decorated_function
[Notice no code-changes to this function (yet)]
class MyClass:
# The following line will make the transition seamless for most methods
LOGIN_REQUIRED = _OUTER_LOGIN_REQUIRED
def ADMIN_REQUIRED(func):
@wraps(func)
@_OUTER_LOGIN_REQUIRED # <-- Note that this is where it should be decorated
def decorated_function(self, *args, **kwargs):
... [the rest of ADMIN_REQUIRED remains unchanged]
@ADMIN_REQUIRED
def get_administration_data(self, username, token):
... [this should now invoke LOGIN_REQUIRED -> ADMIN_REQUIRED -> the function]
@LOGIN_REQUIRED
def get_some_user_data(self, ...):
... [Such definitions should still work, as we added LOGIN_REQUIRED attribute to the class]
If such a change is acceptable so-far, let's move on to
As the name of __check_for_calid_token
function is mangled (as its name starts with a dunder), you'll have to decide how to tackle it. There are two options:
_OUTER_LOGIN_REQUIRED
like so:def decorated_function(self, *args, **kwargs):
self._MyClass__check_for_valid_token(args[0], args[1])
This might affect inheritance, and should be tested thoroughly.
I've tested around this a bit on python 3.9, and it seems to work quite well. I noticed that login errors are raised before admin errors, as I assume is desired. Still, I only poked around in a shallow manner, and while I can't think of a reason for this to misbehave, I strongly recommend testing this thoroughly before committing to this method (especially if the code includes inheritance, which I didn't even touch).
I hope this works for you, and if it doesn't - let us know where it breaks, and how.