Using `AND` (`&`) with a predicate that returns `None` incorrectly returns `True`
See original GitHub issueWhen using &
to combine predicates, if one of the predicates returns None
(a falsey value), the overall predicate will mistakenly/confusingly return True
. This is not consistent with running the predicate by itself or OR
ed with other predicates.
This behavior can be demonstrated with the following test added to test_predicates.py
—the final assertion fails:
def test_returns_none(self):
@predicate
def returns_none(arg1, arg2):
return None
# Just the predicate (works)
assert not returns_none(2, 3)
# OR the predicate with itself (works)
p_OR = returns_none | returns_none
assert not p_OR()
# AND the predicate with a truthy-predicate (FAILS)
p_AND = always_true & returns_none
assert not p_AND()
I noticed this in production in my app, where my logic in one predicate was doing return obj and obj.boolean_field
, expecting that if obj
were None
, it’d still be treated as False
in all contexts. It took me a while to figure out what was going on. I didn’t feel sure about where/how to fix this in django-rules
, but hope the above example will help to resolve quickly if possible. Thanks in advance (and thanks for building this great library)!
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (2 by maintainers)
I agree it’s more “Pythonic” to raise an exception and I have unfortunately no recollection why I made the switch to None. I don’t think it’s sensible to revert back to the old behaviour as it is a breaking change however I’m willing to be convinced otherwise if others feel the change makes sense.
Yeah, that could be useful. I also noticed in the “Upgrading from 1.x” section of the docs, it mentions that skipping used to be done with raising a
SkipPredicate
exception. That more explicit approach seems significantly more preferable/safer to me, particularly for a security-oriented library, where a mistake in allowing access can be rather severe. (And then all truthy/falsey conditions can behave more in line with what’s typical in python.)