Skip to main content

Predicates

Predicates are first-class Python objects that express conditions over a fact's fields. They compose with & | ~ and serialize cleanly.

Basic usage

from airules import Predicate

p = Light.color.eq("yellow") & Light.remaining_time.gt(5)

p.evaluate(Light(color="yellow", remaining_time=10)) # True
p(Light(color="yellow", remaining_time=2)) # False - same thing

# Compose with &, |, ~
either = Light.color.eq("red") | Light.color.eq("yellow")
not_green = ~Light.color.eq("green")

Available operators

ClassMethod shorthandDescription
Eq.eq(value)Equality
Gt.gt(value)Greater than
Ge.ge(value)Greater than or equal
Lt.lt(value)Less than
Le.le(value)Less than or equal
Contains.contains(value)Substring or list membership
StartsWith.startswith(prefix)String prefix
EndsWith.endswith(suffix)String suffix
Andp & qBoolean AND
Orp | qBoolean OR
Not~pBoolean NOT
Always-Always matches (used internally by @Default)

Case-insensitive matching

eq, startswith, endswith, and contains accept a keyword-only case_insensitive=True flag:

User.name.eq("alice", case_insensitive=True) # matches "Alice", "ALICE"
User.name.startswith("dr.", case_insensitive=True) # matches "Dr. Strange"
User.name.contains("smith", case_insensitive=True) # matches "John SMITH"

Notes:

  • eq ignores case only when both values are strings; numbers, None, and enums compare exactly.
  • contains folds case for substring matches only - list/set membership stays exact.
  • The flag survives to_dict() / from_dict() round-trips; it is omitted from the dict when False so older serialized predicates load unchanged.

Serialization

Predicates round-trip through plain dicts:

data = p.to_dict()
restored = Predicate.from_dict(data)

Store them in a database, diff them between deployments, or feed them into a rules-editor UI. See Introspection for dumping the full rule set.