Reputation: 85
In an attempt to debug an online permissions system I was having problems with, I've created a couple of classes and functions designed to replicate the issue offline. Unfortunately, I'm having a problem, in which turning a function into a decorator completely changes the results of a decorated function.
My code is as follows, I've kept it as basic as possible, to illustrate my point.
Setting up User class:
class User(object):
def __init__(self, forename=None, surname=None, logged_in=True, exists=True, poop=False, admin=False):
self.forename = forename
self.surname = surname
self.logged_in = logged_in
self.exists = exists
self.poop = poop
self.admin = admin
def __repr__(self):
return f'User: {self.forename} {self.surname}.'
user1 = User('Paddy', 'McHugh', True, True, False, True)
user2 = User('Rodney', 'Donger', False, False, True, False)
user3 = User('Bob', 'Dangler', True, True, True, True)
Creating functions to test against a user:
def user_just_is(user):
return user.exists
def user_is_poop(user):
return user.poop
def user_is_admin(user):
return user.admin
Testing those functions against the chosen user with a regular function:
class Permissions2(object):
def __init__(self):
pass
def requires(self, *args):
user = user2
if not user.logged_in:
print('You\'re not logged in, please log in.')
return
if not all(i(user) for i in args):
print('Not all of the conditions were met.')
else:
print('All of the conditions were met.')
Permissions2().requires(user_just_is, user_is_poop, user_is_admin)
Testing those functions against the chosen user with a decorator function:
class Permissions(object):
def __init__(self):
pass
def requires(self, *args):
user = user2
def decorator(func):
@wraps(func)
def allower(*args, **kwargs):
if not user.logged_in:
print('You\'re not logged in, please log in.')
return
if not all(i(user) for i in args):
print('Not all of the conditions were met.')
return
return func(*args, **kwargs)
return allower
return decorator
@Permissions.requires(user_just_is, user_is_poop, user_is_admin)
def print_stuff():
print('All of the conditions were met.')
print_stuff()
I'd expect the outcome of both the regular and decorator function to be the same. That if User.logged_in == False, then it would print: 'You're not logged in, please log in.'
. That if all the Boolean variables were True, it would print: 'All of the conditions were met.'
. That if any of the conditions were False, it would print: 'Not all of the conditions were met.'
.
The decorator function still returns the 'You're not logged in, please log in'
, but if User.logged_if == True, then the other Booleans don't matter, it always returns True to the all()
function and print 'All of the conditions were met.'
.
What is it about putting it in a decorator that means all()
seems to return True to all of the tested functions?
Upvotes: 1
Views: 73
Reputation: 106618
The args
parameter for your allower
function shadows the args
parameter of requires
, so when you iterate over args
here:
if not all(i(user) for i in args):
you are not iterating through the list of functions passed in to requires
as args
anymore, but rather the args
passed to the decorated function. You should rename the parameter to avoid the naming conflict.
Moreover, you're defining Permissions.requires
as an instance method so its first parameter is self
, the object that the method is bound to, so when you call:
@Permissions.requires(user_just_is, user_is_poop, user_is_admin)
user_just_is
is passed as self
, rather than becoming part of args
. Since requires
does not actually make use of self
, it should be defined as a static method instead.
So with the above issues fixed, your Permissions
class should look like:
class Permissions(object):
@staticmethod
def requires(*conditions):
user = user2
def decorator(func):
@wraps(func)
def allower(*args, **kwargs):
if not user.logged_in:
return print('You\'re not logged in, please log in.')
if not all(i(user) for i in conditions):
return print('Not all of the conditions were met.')
return func(*args, **kwargs)
return allower
return decorator
Upvotes: 2