Integrations
Predicates are plain callables, so they work with any library that accepts a function
returning a bool. For some libraries py-predicate provides ready-made wrappers.
annotated-types
annotated-types provides Predicate, which takes any
callable returning a bool. Predicates can be used directly, no wrapper needed. annotated-types itself doesn’t
validate anything; a library like pydantic enforces the constraint:
from typing import Annotated
from annotated_types import Predicate
from pydantic import BaseModel
from predicate import ge_lt_p, is_str_p
class Person(BaseModel):
name: Annotated[str, Predicate(is_str_p)]
age: Annotated[int, Predicate(ge_lt_p(0, 150))]
Person(name="alice", age=30) # ok
Person(name="alice", age=200) # raises ValidationError
beartype
beartype accepts any callable as a validator through
beartype.vale.Is. Since predicates are callables, they can be used directly in type hints, no wrapper needed:
from typing import Annotated
from beartype import beartype
from beartype.vale import Is
from predicate import ge_le_p, is_list_of_p, is_str_p
Percentage = Annotated[int, Is[ge_le_p(0, 100)]]
@beartype
def set_volume(level: Percentage) -> Percentage:
return level
@beartype
def join(words: Annotated[list, Is[is_list_of_p(is_str_p)]]) -> str:
return " ".join(words)
set_volume(50) # ok
set_volume(101) # raises BeartypeCallHintParamViolation
join(["a", 1]) # raises BeartypeCallHintParamViolation
pandera
pandera accepts any callable as a check through pandera.Check.
With element_wise=True the check is called per value, so predicates can be used directly, no wrapper needed:
import pandas as pd
import pandera.pandas as pa
from predicate import ge_lt_p, is_str_p
schema = pa.DataFrameSchema(
{
"name": pa.Column(checks=pa.Check(is_str_p, element_wise=True)),
"age": pa.Column(int, pa.Check(ge_lt_p(0, 150), element_wise=True)),
}
)
schema.validate(pd.DataFrame({"name": ["alice"], "age": [30]})) # ok
schema.validate(pd.DataFrame({"name": ["alice"], "age": [200]})) # raises SchemaError
ReactiveX
The predicate.rx module wraps the ReactiveX operators that take a
predicate, so they can be used directly in a pipe. Install the optional dependency first:
pip install py_predicate[rx]
Then filter a stream with a predicate:
import reactivex as rx
from predicate import gt_p
from predicate.rx import filter_p
rx.of(1, 2, 3, 4).pipe(filter_p(gt_p(2))).subscribe(print) # prints 3 and 4
The following operators are available:
Operator |
Description |
|---|---|
|
only lets through items satisfying the predicate |
|
lets through items as long as the predicate holds ( |
|
skips items as long as the predicate holds |
|
splits a stream into |
|
emits whether all items satisfy the predicate |
|
emits whether any item satisfies the predicate |
|
emits the number of items satisfying the predicate |
|
emits the first item satisfying the predicate, errors if there is none |
|
emits the last item satisfying the predicate, errors if there is none |
|
emits the only item satisfying the predicate, errors if there is not exactly one |
|
like |
|
like |
|
like |
|
emits the first item satisfying the predicate, or |
|
emits the index of the first item satisfying the predicate, or |
all_p and count_p share their names with core predicates, so alias them when you need both:
from predicate import all_p
from predicate.rx import all_p as rx_all_p
Note
partition_p subscribes to the source once per output stream. With a cold source such as rx.of, the
first subscription consumes all items before the second one starts. Use a hot source when both streams need
to see the items, e.g. a Subject or a published source:
import reactivex as rx
import reactivex.operators as ops
from predicate import gt_p
from predicate.rx import partition_p
source = rx.of(1, 2, 3, 4).pipe(ops.publish())
matching, non_matching = source.pipe(partition_p(gt_p(2)))
matching.subscribe(print) # prints 3 and 4
non_matching.subscribe(print) # prints 1 and 2
source.connect()