Skip to content

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.

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.

Requirements fail when the condition you require has no matches. There are two main types of requirements:

  • Model-level requirements are added with Model.require and check for the existence of matches across the entire model.
  • Scoped requirements are added with Concept.require or by Fragment.require to a where fragment. 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, Model
from 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 Order has promised_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 Order must satisfy …”, use a scoped requirement like Order.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, Model
from 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 the Order domain.
  • 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 Order entities, this requirement does not fail. Add m.require(Order) when you need to enforce existence.

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, String
from 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/except keeps 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(...).