Facts & Fields
A Fact is the typed input schema your rules evaluate. Annotate its attributes
with one of the field types and the metaclass machinery wires up storage,
defaults, validation, and predicate builders.
Field types
from typing import Literal
from airules import Fact, Field, ListField, NumberField, StringField
Color = Literal["green", "red", "yellow"]
class Light(Fact):
color: Field[Color]
remaining_time: NumberField[int]
class User(Fact):
name: StringField
tags: ListField[str] = ListField(default=None)
| Type | Extra predicates |
|---|---|
Field[T] | .eq(...) |
NumberField[T] | .gt · .ge · .lt · .le |
StringField | .startswith · .endswith · .contains |
ListField[E] | .contains(element) |
Fields without an explicit default are required at construction time;
passing unknown fields raises TypeError. Optional[...] annotations are
inferred as having a default of None.
Embedded facts (dotted paths)
A Fact can hold another Fact and you can build predicates over nested
fields with the same syntax you'd use at the top level:
from airules import EmbeddedField, Fact, NumberField, StringField
class Sensor(Fact):
temperature: NumberField[int] = NumberField(default=0)
class Car(Fact):
plate: StringField
sensor: EmbeddedField[Sensor] = EmbeddedField(Sensor, default=None)
Car.sensor.temperature.ge(10) # predicate over the path "sensor.temperature"
If any segment along the path is None, the predicate evaluates to False
(the Eq predicate is the exception - it compares None == value honestly,
so field.eq(None) works).