Rule chain for if A: return B
BoPeng opened this issue · comments
I have some rules like
@rules.predicate
def can_do_something(user, target):
if user.is_anonymous:
return test_A(user, target)
elif user.is_admin:
return test_B(user, target)
else:
return test_C(user, target)
which I would like to translate to
can_do_something = (rules.is_anonymous & test_A) | (rules.is_admin & test_B) | (rules.is_authenticated & test_C)
The problem is that in the original implementation if user.is_anonymous
is True
, the checker will return immediately with test_A
, but in the new implementation the checker will go through all cases if is_anonymous & test_A
is False
. How do I avoid evaluating all three cases? It looks like I am missing something obvious here.
Make sure “test_A”, “test_B”, etc. are predicates themselves and it should work.
Both version works but as I said I prefer the second cleaner version but it is less "efficient" (which perhaps does not matter) because it evaluate all three conditions.
I was asking if there is a trick for
(rules.is_anonymous & test_A) | ...
returning test_A
if rules.is_anonumous
is True
, without going into the next condition, as the function version does.
Is test_A
a predicate? As in, did you define it like so?
@rules.predicate
def test_A(...):
return some_condition
Yes, it is.
Sorry, the demo for the my function version was not written correctly. I am updating it.
Can you reduce this down to a minimal example (that I can just copy/paste and run) that reproduces this please?
import rules
user = {'anonymous': True, 'admin': False }
target = {'test_A': False, 'test_B': False }
@rules.predicate
def is_anonymous(user):
print('tested for anonymous')
return user['anonymous']
@rules.predicate
def is_admin(user):
print('tested for admin')
return user['admin']
@rules.predicate
def test_A(user, target):
print('tested for A')
return target['test_A']
@rules.predicate
def test_B(user, target):
print('tested for B')
return target['test_B']
@rules.predicate
def func_form(user, target):
if is_anonymous(user):
return test_A(user, target)
elif is_admin(user):
return test_B(user, target)
chain_form = (is_anonymous & test_A ) | (is_admin & test_B )
print('Function form')
func_form(user, target)
print('\nChain form')
chain_form(user, target)
with output
Function form
tested for anonymous
tested for A
Chain form
tested for anonymous
tested for A
tested for admin
Note that I know beforehand that is_anonymous
, is_admin
etc are mutually exclusive so there is no need to test other branches.
Thanks, I see now. Seems I misread this bit in your message:
The problem is that in the original implementation if user.is_anonymous is True, the checker will return immediately with test_A, but in the new implementation the checker will go through all cases if is_anonymous & test_A is False
Well, that is expected behaviour, you're not missing anything. Predicates combine and behave similar to regular boolean algebra:
def test_A():
return False
def test_B():
return True
test_A() or test_B()
# yields `True`
The two forms (ie. "function" and "chain" form) are not equivalent. To achieve the behaviour you describe, you should use the first one.
Thanks for your clarification, at least I did not miss any trick from the documentation.
To clarify somewhat, the "chain" form is as if you wrote the "function" form like so:
@rules.predicate
def can_do_something(user, target):
if user.is_anonymous and test_A(user, target):
return True
elif user.is_admin and test_B(user, target):
return True
else:
return test_C(user, target)