Tutorial

To fully appreciate the power and flexibility of composable predicates, we provide a number of examples in this tutorial.

Tutorial 1

Task: define a predicate expression that given a list of values, returns True if:

  1. all values are either strings or integers

  2. if the value is an integer, than it should be less than 5

  3. if the value is a string, it should start with foo

This will show a couple of concepts and useful standard predicates.

Let’s start with the first requirement. We start first with the code and then explain it:

from predicate import all_p, is_int_p, is_str_p

predicate = all_p(is_int_p | is_str_p)

In the example above we import 3 predicates. The all_p accepts one parameter, which is another predicate. The is_int_p checks if a value is an integer. And finally the is_str_p checks if a value is a string.

We combine the is_int_p and is_str_p with the | operator, resulting in a new predicate.

This is sufficient for the first requirement. Now lets restrict the allowed value of the integer to less than 5:

from predicate import all_p, is_int_p, is_str_p, lt_p

is_int_lt_5 = is_int_p & lt_p(5)

predicate = all_p(is_int_lt_5 | is_str_p)

As you can see, we imported the lt_p predicate. This checks if a given value, is less than the parameter (5 in this case). We could have made this a one-liner, but we assigned it to a new predicate variable is_int_lt_5. With this addition, we have implemented the second requirement.

Finally we turn to the third requirement, which says that if the value is a string, it should start with “foo”. This sounds like an ideal candidate for a regular expression, and indeed such a predicate is also available:

from predicate import all_p, is_int_p, is_str_p, lt_p, regex_p

is_int_lt_5 = is_int_p & lt_p(5)
is_str_foo = is_str_p & regex_p("^foo.*")

predicate = all_p(is_int_lt_5 | is_str_foo)

Now you are ready to check your new predicate against the requirements, for example:

predicate([1, "foo"])  # True
predicate([1, "foo", None])  # False, None is not valid
predicate([5, "foo"])  # False, 5 is to big
predicate([1, "bar"])  # False, "bar" doesn't begin with "foo"

Let’s now reuse this predicate to generate some sample values:

from predicate import generate_true
from more_itertools import take

result = take(10, generate_true(predicate))

Notice that this may take a few seconds. On my system the output was:

[
    [],
    (1, -1, "foo", "foo!", "foo!", 'foo"'),
    {0, -5, "foo"},
    ["foo5xT", "foo5xT", "foo", "foo ", "foo!", -1],
    (0, -1, -1, -1, "foo"),
    {"fooU7", "fooRV8TMtF", "foor959aO5"},
    [-9, "foo", "foojBA", "foo ", "foo "],
    ("foohqhdJr", "foou", "foo", 1, 1, 1, 1, 1, 2),
    {1, "foo", "foo!", -1, 'foo"'},
    ["foo", -1, -1, "foo", "foo", "foo ", 0, 'foo"', 'foo"'],
]

If you don’t want to check manually if these values are correct, you can of course use a predicate:

validate = all_p(predicate)
validate(result)

This should result in True. If not, please submit a bug report.

Tutorial 2

Task: define a predicate expression that given a list of values, returns True if:

  1. any value is a tuple

  2. the first element of that tuple is a uuid

  3. the second element of that tuple is in the set {“foo”, “bar”}

  4. the third element of that tuple is a truthy value (True, 1, etc.)

Let’s start with the first requirement. This is similar to what we saw in tutorial 1:

from predicate import any_p, is_tuple_p

predicate = any_p(is_tuple_p)

The any_p predicate is similar to the all_p predicate: it accepts one parameter (which is a predicate) and returns true iff at least one value satisfies this predicate.

Now lets turn to the other requirements. If it’s a tuple, we need to check the elements in this tuple against three different predicates. This is exactly what is_tuple_of_p does for us. Lets show the code first:

from predicate import any_p, in_p, is_truthy_p, is_tuple_of_p, is_uuid_p

foo_or_bar = in_p("foo", "bar")
valid_tuple = is_tuple_of_p(is_uuid_p, foo_or_bar, is_truthy_p)

predicate = any_p(valid_tuple)

Now you are ready to check your new predicate against the requirements, for example:

from uuid import uuid4

predicate([(uuid4(), "foo", 1)])  # True: 1 is a truthy value
predicate([(uuid4(), "meh", 1)])  # False: missing "foo" or "bar"
predicate([("not_a_uuid", "foo", 1)])  # False: missing uuid