Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule chain for if A: return B #113

Closed
BoPeng opened this issue Feb 12, 2020 · 10 comments
Closed

Rule chain for if A: return B #113

BoPeng opened this issue Feb 12, 2020 · 10 comments

Comments

@BoPeng
Copy link

BoPeng commented Feb 12, 2020

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.

@dfunckt
Copy link
Owner

dfunckt commented Mar 24, 2020

Make sure “test_A”, “test_B”, etc. are predicates themselves and it should work.

@BoPeng
Copy link
Author

BoPeng commented Mar 24, 2020

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.

@dfunckt
Copy link
Owner

dfunckt commented Mar 24, 2020

Is test_A a predicate? As in, did you define it like so?

@rules.predicate
def test_A(...):
  return some_condition

@BoPeng
Copy link
Author

BoPeng commented Mar 24, 2020

Yes, it is.

@BoPeng
Copy link
Author

BoPeng commented Mar 24, 2020

Sorry, the demo for the my function version was not written correctly. I am updating it.

@dfunckt
Copy link
Owner

dfunckt commented Mar 24, 2020

Can you reduce this down to a minimal example (that I can just copy/paste and run) that reproduces this please?

@BoPeng
Copy link
Author

BoPeng commented Mar 24, 2020

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.

@dfunckt
Copy link
Owner

dfunckt commented Mar 24, 2020

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.

@BoPeng
Copy link
Author

BoPeng commented Mar 24, 2020

Thanks for your clarification, at least I did not miss any trick from the documentation.

@BoPeng BoPeng closed this as completed Mar 24, 2020
@dfunckt
Copy link
Owner

dfunckt commented Mar 24, 2020

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants