Define requirements
Enforce semantic validity in your model by adding requirements that fail when key assumptions have no matches. Use this when you want to encode data quality checks and “must be true” business rules alongside your schema and logic.
- PyRel is installed and importable in Python. See Set Up Your Environment for instructions.
- You have a model instance with working configuration. See Create a Model Instance.
- You have declared concepts, relationships, and properties for the facts you want to load. See Declare Concepts and Declare Relationships and Properties.
What a requirement is
Section titled “What a requirement is”A requirement is a constraint that must have at least one match for the model to be considered valid. In PyRel, requirements are how you encode data quality checks and business rules as part of your semantic model.
Requirements validate your model.
They do not derive new facts the way Model.define does, and they do not change what a query returns the way Model.where does.
How a requirement works
Section titled “How a requirement works”Requirements fail when the condition you require has no matches. There are two main types of requirements:
- Model-level requirements are added with
Model.requireand check for the existence of matches across the entire model. - Scoped requirements are added with
Concept.requireor byFragment.requireto awherefragment. These enforce that every match in the scope satisfies the required condition.
Requirements are checked when you materialize a query, for example by calling to_df().
If any requirement fails, the materialization fails with an error.
Add a global requirement with Model.require
Section titled “Add a global requirement with Model.require”Add a model-wide requirement with Model.require when it is enough for the condition to match at least one set of facts:
from relationalai.semantics import DateTime, Integer, Modelfrom relationalai.semantics.std.datetime import datetime
m = Model("MyModel")
Order = m.Concept("Order", identify_by={"id": Integer})Order.created_at = m.Property(f"{Order} created at {DateTime}")Order.promised_ship_date = m.Property(f"{Order} promised ship date is {DateTime}")
m.define( Order.new(id=1, created_at=datetime(2025, 12, 1), promised_ship_date=datetime(2025, 12, 10)), Order.new(id=2, created_at=datetime(2025, 12, 10), promised_ship_date=datetime(2025, 12, 1)),)
# Global requirement: must have at least one match.m.require(Order.promised_ship_date >= Order.created_at)
# Requirements are checked when you materialize a query.m.select(Order.id, Order.created_at, Order.promised_ship_date).to_df()m.require(Order.promised_ship_date >= Order.created_at)adds a model-scoped requirement.- The requirement passes because at least one
Orderhaspromised_ship_date >= created_at. - The final
to_df()call materializes a query, which triggers requirement checking.
- If no facts match the requirement (for example, if every order has
promised_ship_date < created_at), materializing a query fails. - If you meant “every
Ordermust satisfy …”, use a scoped requirement likeOrder.require(Order.promised_ship_date >= Order.created_at).
Add a per-entity requirement with Concept.require
Section titled “Add a per-entity requirement with Concept.require”Add a per-concept requirement with Concept.require when you want “every instance of this concept must satisfy …”:
from relationalai.semantics import DateTime, Integer, Modelfrom relationalai.semantics.std.datetime import datetime
m = Model("MyModel")
Order = m.Concept("Order", identify_by={"id": Integer})Order.created_at = m.Property(f"{Order} created at {DateTime}")Order.promised_ship_date = m.Property(f"{Order} promised ship date is {DateTime}")
m.define( Order.new(id=1, created_at=datetime(2025, 12, 1), promised_ship_date=datetime(2025, 12, 10)), Order.new(id=2, created_at=datetime(2025, 12, 5), promised_ship_date=datetime(2025, 12, 10)),)
# Require that every Order has a promised ship date on or after created_at.Order.require(Order.promised_ship_date >= Order.created_at)
# Requirements are checked when you materialize a query.m.select(Order.id, Order.created_at, Order.promised_ship_date).to_df()In this example:
Order.require(Order.promised_ship_date >= Order.created_at)adds a scoped requirement over theOrderdomain.- The requirement enforces a per-entity check (equivalent to
m.where(Order).require(Order.promised_ship_date >= Order.created_at)). - The final
to_df()call materializes a query, which triggers requirement checking.
- If you define no
Orderentities, this requirement does not fail. Addm.require(Order)when you need to enforce existence.
Add a scoped requirement with Model.where
Section titled “Add a scoped requirement with Model.where”Add a scoped requirement by starting with Model.where and chaining Fragment.require to enforce rules for every match in the scope:
from relationalai.semantics import DateTime, Integer, Model, Stringfrom relationalai.semantics.std.datetime import datetime
m = Model("MyModel")
Order = m.Concept("Order", identify_by={"id": Integer})Order.promised_ship_date = m.Property(f"{Order} promised ship date is {DateTime}")Order.status = m.Property(f"{Order} status is {String}")
Shipment = m.Concept("Shipment", identify_by={"id": Integer})Shipment.shipped_at = m.Property(f"{Shipment} shipped at {DateTime}")
Order.shipments = m.Relationship(f"{Order} has shipment {Shipment}")
m.define( o1 := Order.new(id=1, promised_ship_date=datetime(2025, 12, 10), status="delayed"), s1 := Shipment.new(id=1, shipped_at=datetime(2025, 12, 15)), o1.shipments(s1), o2 := Order.new(id=2, promised_ship_date=datetime(2025, 12, 10), status="shipped"), s2 := Shipment.new(id=2, shipped_at=datetime(2025, 12, 15)), o2.shipments(s2),)
# Scoped requirement: if an order ships late, it must be marked as delayed.m.where( Order.shipments(Shipment), Shipment.shipped_at > Order.promised_ship_date,).require( Order.status == "delayed")
try: m.where(Order.shipments(Shipment)).select( Order.id.alias("order_id"), Order.status, Shipment.id.alias("shipment_id"), Shipment.shipped_at, Order.promised_ship_date, ).to_df()except Exception as ex: print(f"Materialization failed with error: {ex}")In this example:
m.where(Order.shipments(Shipment), Shipment.shipped_at > Order.promised_ship_date)scopes the requirement to late shipments..require(Order.status == "delayed")enforces the rule for every match in that scope.- The
try/exceptkeeps the example runnable while still showing that a scoped requirement can fail when a single late order is not marked as delayed.
- If you forget the scope, you often get a weaker check than intended.
- This requirement does not constrain on-time shipments because they are outside the
where(...)scope. - If you meant a global existence check (“at least one delayed order exists”), use a model-level requirement like
m.require(...).