Module to create composable predicates
Project description
py-predicate
A typed Python library for composable predicates.
Predicates are first-class objects that can be combined with boolean operators, optimized algebraically, compiled to native callables for performance, serialized to multiple formats, and used to instrument functions with runtime contracts.
Full documentation: mrijk.github.io/py-predicate
Installation
pip install py-predicate
Quick start
from predicate import ge_p, le_p
ge_2 = ge_p(2)
le_5 = le_p(5)
between = ge_2 & le_5
between(3) # True
between(7) # False
filtered = [x for x in range(10) if between(x)] # [2, 3, 4, 5]
Predicates compose with standard Python operators:
| Operator | Meaning |
|---|---|
p & q |
both must hold (AndPredicate) |
p | q |
either must hold (OrPredicate) |
p ^ q |
exactly one must hold (XorPredicate) |
~p |
negation (NotPredicate) |
Built-in predicates
Comparisons: eq_p, ne_p, gt_p, ge_p, lt_p, le_p, is_close_p
Ranges: ge_le_p, ge_lt_p, gt_le_p, gt_lt_p (compiled to chained comparisons)
Type checks: is_int_p, is_str_p, is_float_p, is_bool_p, is_list_p, is_dict_p, is_none_p, is_not_none_p, is_instance_p, and many more
Collections: all_p, any_p, in_p, not_in_p, has_length_p, is_empty_p, is_not_empty_p, has_key_p, has_path_p
Typed collections: is_list_of_p, is_set_of_p, is_dict_of_p, is_tuple_of_p
Strings: regex_p, starts_with_p, ends_with_p, is_alpha_p, is_digit_p, is_upper_p, is_lower_p, and more
Logic: always_p, never_p, implies_p, fn_p
Numeric: is_even_p, is_odd_p, is_nan_p, is_inf_p, is_finite_p, pos_p, neg_p, zero_p
Failure explanation
Use explain to get a structured description of why a predicate failed:
from predicate import explain, ge_p, le_p
between = ge_p(2) & le_p(5)
explain(between, 8)
# {'reason': 'Right predicate failed', 'right': {'reason': 'Expected 8 <= 5'}}
Optimizer
Predicate trees can be algebraically simplified:
from predicate import ge_p, le_p, optimize, can_optimize
p = ge_p(2) & le_p(5)
can_optimize(p) # True (may simplify to a range predicate, etc.)
optimized = optimize(p)
The optimizer handles identities like p & always_true == p, p | always_true == always_true, double-negation elimination, De Morgan's laws, and more.
Compile to native callables
compile_predicate translates a predicate tree into a native Python lambda via AST generation, removing interpreter overhead on hot paths:
from predicate import compile_predicate, ge_p, le_p
between = ge_p(2) & le_p(5)
fast = compile_predicate(between) # returns a CompiledPredicate
fast(3) # True — evaluated by a native lambda: lambda x: x >= 2 and x <= 5
fast(8) # False
# Introspection is preserved
repr(fast) # same as repr(between)
fast.explain_failure # delegates to the original predicate
Use try_compile_predicate to fall back to the original when compilation is not supported (e.g. fn_p, regex_p):
from predicate import try_compile_predicate
safe = try_compile_predicate(some_predicate) # always returns a callable
Value generation
Predicates can drive property-based testing by generating values that satisfy or violate them:
from predicate import generate_true, generate_false, ge_p, le_p
from more_itertools import take
between = ge_p(2) & le_p(5)
take(5, generate_true(between)) # e.g. [2, 3, 4, 5, 2]
take(5, generate_false(between)) # e.g. [0, 1, 6, 7, -1]
Recursive predicates
Use root_p (or this_p) to define self-referencing predicates over arbitrarily nested structures without writing recursive functions:
from predicate import all_p, is_list_p, is_str_p, root_p
# Matches a string, or a list of strings/lists (recursively)
str_or_nested = is_str_p | (is_list_p & all_p(root_p))
str_or_nested("hello") # True
str_or_nested(["a", ["b", "c"]]) # True
str_or_nested(["a", 1]) # False
For mutually recursive predicates, use mutual_recur_p.
Runtime instrumentation (Spec)
Instrument functions with predicate-based contracts that are checked at call time:
from predicate import ge_p, instrument, is_int_p, is_str_p, le_p
@instrument({"args": {"x": is_int_p & ge_p(0) & le_p(100)}, "ret": is_str_p})
def grade(x: int) -> str:
return "pass" if x >= 50 else "fail"
grade(75) # "pass"
grade(-1) # raises ValueError: Parameter predicate for function grade failed
You can also instrument all functions in a module or class:
from predicate import instrument_module, instrument_class
instrument_module(my_module)
instrument_class(MyClass)
Specs can include cross-argument constraints via fn or fn_p keys, and expected exception types via raises.
Analysis
Check logical properties of predicates:
from predicate import are_equivalent, is_satisfiable, is_tautology, ge_p, le_p, always_p
is_tautology(always_p) # True
is_satisfiable(ge_p(5) & le_p(3)) # False (unsatisfiable)
are_equivalent(~~ge_p(2), ge_p(2)) # True
Serialization
Export predicates to multiple formats:
from predicate import ge_p, le_p, to_json, to_yaml, to_dot, to_latex
p = ge_p(2) & le_p(5)
to_json(p) # JSON string
to_yaml(p) # YAML string
to_dot(p) # Graphviz DOT for visualization
to_latex(p) # LaTeX expression
Deserialize from JSON:
from predicate.formatter.from_json import from_json
p = from_json('{"and": [{"ge": 2}, {"le": 5}]}')
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_predicate-1.7.0.tar.gz.
File metadata
- Download URL: py_predicate-1.7.0.tar.gz
- Upload date:
- Size: 62.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c1b1b3dd2b4ccc4403ef740169b5c17b79513c490b0d9bdb40695689bbfca24
|
|
| MD5 |
c2c22845de40c976f9f4d23dc8fab51f
|
|
| BLAKE2b-256 |
a68a29310f42ed34feade08520663d0679996394e99d9029a7e84dace7ed60e8
|
File details
Details for the file py_predicate-1.7.0-py3-none-any.whl.
File metadata
- Download URL: py_predicate-1.7.0-py3-none-any.whl
- Upload date:
- Size: 98.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
786ce8fa78719d896ec26bd7c2470cf8fd9e394fa4e8762d48b995364ad4ccc0
|
|
| MD5 |
68dc0aed1edc148dc5d04e1a206234cd
|
|
| BLAKE2b-256 |
0c11ae13993587c08b513ffa76595ddec53f8b6f76c62a830b980a1cf474e12e
|