"""
The boolean module allows delayed evaluation of boolean expressions. You wrap
predicates in objects that have overloaded operators so they can be connected
symbolically to express ``and``, ``or``, and ``not``. This is useful if you
want to build up a complicated predicate and pass it to something else for
evaluation, in particular :py:class:`insights.parsr.query.Entry` instances.
.. code-block:: python
def is_even(n):
return (n % 2) == 0
def is_positive(n):
return n > 0
even_and_positive = pred(is_even) & pred(is_positive)
even_and_positive(6) == True
even_and_positive(-2) == False
even_and_positive(3) == False
You can also convert two parameter functions to which you want to partially
apply an argument. The arguments partially applied will be those *after* the
first argument. The first argument is the value the function should evaluate
when it's fully applied.
.. code-block:: python
import operator
lt = pred2(operator.lt) # operator.lt is lt(a, b) == (a < b)
gt = pred2(operator.gt) # operator.gt is gt(a, b) == (a > b)
gt_five = gt(5) # creates a function of one argument that when called
# returns operator.gt(x, 5)
lt_ten = lt(10) # creates a function of one argument that when called
# returns operator.lt(x, 5)
gt_five_and_lt_10 = gt(5) & lt(10)
"""
from itertools import count
import six
import sys
[docs]class Boolean(object):
def __and__(self, other):
return All(self, other)
def __or__(self, other):
return Any(self, other)
def __invert__(self):
return Not(self)
[docs] def test(self, value):
return True
def __call__(self, value):
return self.test(value)
[docs] def to_pyfunc(self):
ver = sys.version_info
if ver[0] == 2 and ver[1] == 6:
return self.test
env = {}
ids = count()
def expr(b):
if b is TRUE:
return " True "
elif b is FALSE:
return " False "
elif isinstance(b, All):
return "(" + " and ".join(expr(p) for p in b.exprs) + ")"
elif isinstance(b, Any):
return "(" + " or ".join(expr(p) for p in b.exprs) + ")"
elif isinstance(b, Not):
return "(" + "not " + expr(b.query) + ")"
elif isinstance(b, Predicate):
num = next(ids)
func = "func_{num}".format(num=num)
args = "args_{num}".format(num=num)
env[func] = b.func
env[args] = b.args
if isinstance(b, CaselessPredicate):
return func + "(value.lower(), " + "*" + args + ")"
return func + "(value, " + "*" + args + ")"
else:
raise Exception(b)
func = """
def predicate(value):
try:
return {body}
except Exception as ex:
return False
""".format(body=expr(self))
six.exec_(func, env, env)
return env["predicate"]
class TRUE(Boolean):
pass
class FALSE(Boolean):
def test(self, value):
return False
[docs]class Any(Boolean):
def __init__(self, *exprs):
self.exprs = list(exprs)
[docs] def test(self, value):
return any(q.test(value) for q in self.exprs)
[docs]class All(Boolean):
def __init__(self, *exprs):
self.exprs = list(exprs)
[docs] def test(self, value):
return all(q.test(value) for q in self.exprs)
[docs]class Not(Boolean):
def __init__(self, query):
self.query = query
[docs] def test(self, value):
return not self.query.test(value)
[docs]class Predicate(Boolean):
def __init__(self, func, *args):
self.func = func
self.args = args
[docs] def test(self, value):
try:
return self.func(value, *self.args)
except Exception:
return False
[docs]class CaselessPredicate(Predicate):
[docs] def test(self, lhs):
if isinstance(lhs, str):
return super(CaselessPredicate, self).test(lhs.lower())
return super(CaselessPredicate, self).test(lhs)
[docs]def pred(func, ignore_case=False):
if ignore_case:
return CaselessPredicate(func)
return Predicate(func)
[docs]def pred2(func, ignore_case=False):
def inner(val):
if ignore_case:
return CaselessPredicate(func, val.lower())
return Predicate(func, val)
return inner
Or = Any
And = All
TRUE = TRUE()
FALSE = FALSE()