Skip to content

Declare concepts

Declare concept types and identity keys to define the stable schema for your semantic model. Use this guide to turn a model design into a small, intentional concept hierarchy before you declare relationships, load data, or write rules.

Declaring a concept creates an entity type, like Customer or Order. Use it to turn your model design into a concrete schema you can attach properties and relationships to. It does not load data or create entities.

To declare a concept, call Model.Concept and pass the concept name:

from relationalai.semantics import Model
m = Model("MyModel")
Customer = m.Concept("Customer")
Order = m.Concept("Order")
Shipment = m.Concept("Shipment")
  • m = Model("MyModel") creates a model you can add concepts to.
  • Each m.Concept("...") call declares a concept type (an entity type in your schema) and returns a concept object. The Python variables Customer, Order, and Shipment are just handles you can reference later.
  • Declaring concepts does not create any entity instances. You create instances later (for example with Concept.new and Model.define).
  • The concept name you pass to Model.Concept is the canonical name for that concept in your model. It is used in generated readings and in query results.
  • Declaring a concept does not create any entity instances. It just defines a type that you can create entities of later.
  • You can declare as many concepts as you need to represent the entities in your model design.

Identity is the key the model uses to decide whether two records refer to the same entity. Declare identity for concepts you expect to join, query, or merge across sources.

Identity is used when you create entities with Concept.new. If you do not declare identity, Concept.new derives identity from the values you pass. That is easy to change by accident, which can merge distinct entities or create duplicates.

Define an identity scheme during concept declaration

Section titled “Define an identity scheme during concept declaration”

For most workflows, pass identify_by to Model.Concept:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
# Single-field identity
Customer = m.Concept("Customer", identify_by={"customer_id": Integer})
Order = m.Concept("Order", identify_by={"order_id": Integer})
# Composite identity
OrderItem = m.Concept(
"OrderItem",
identify_by={"order_id": Integer, "line_number": Integer},
)
  • identify_by={...} declares which fields make up identity for the concept. When you later call Concept.new, those fields are used to compute the entity key.
  • Customer and Order use single-field identity (customer_id and order_id).
  • OrderItem uses composite identity (order_id + line_number). You must provide both fields when you create OrderItem entities.
  • identify_by={...} also creates properties on the concept. For example, you can reference Customer.customer_id and OrderItem.line_number.
  • Those generated properties use a default reading like "{concept} has {type:field_name}".

(Advanced) Mark existing properties as part of an identity scheme

Section titled “(Advanced) Mark existing properties as part of an identity scheme”

Use Concept.identify_by when identity must reference a property you already declared. This comes up when you declared the property first to use a custom reading via Model.Property:

from relationalai.semantics import Model, String
m = Model("MyModel")
Product = m.Concept("Product")
# Declare the property first when you need a custom reading.
Product.sku = m.Property(f"{Product} has SKU {String:sku}")
# Then mark the existing property reading as identity.
Product.identify_by(Product.sku)
  • Product = m.Concept("Product") declares the concept type first.
  • m.Property(...) creates a property reading. Assigning it to Product.sku attaches that property to the Product concept.
  • Product.identify_by(Product.sku) then uses that existing property as the identity field. This affects how Product.new decides whether two records refer to the same Product entity.
  • The reading string (f"{Product} has SKU {String:sku}") is a compact way to define the property’s name and type.
  • For most cases, you should prefer identify_by={...} when you call Model.Concept. Use Concept.identify_by when identity needs to reference a property you already declared.
  • Only pass properties of the concept you are configuring when you call Concept.identify_by. It raises an error if you pass a property from a different concept.

Use the Model.Concept method’s extends parameter when one concept is a more specific type of another. The subconcept inherits the parent’s properties.

Common examples of subconcepts include:

  • A named value type that is a specific kind of primitive type. For example, EmailAddress as a subconcept of String, or Price as a subconcept of Number.
  • A specific category of a more general concept. For example, CriticalTicket as a subconcept of Ticket, or ElectricVehicle as a subconcept of Vehicle.

The following sections show how to declare single-parent and multi-parent subconcepts, and how to keep siblings distinct by including the concept type in the identity scheme.

Pass a list with one parent Concept object to the extends parameter to create a subconcept that is a strict specialization of another:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
Order = m.Concept("Order", identify_by={"order_id": Integer})
DelayedOrder = m.Concept("DelayedOrder", extends=[Order])
  • DelayedOrder is a subconcept of Order.
  • Order is declared with identity (identify_by={"order_id": Integer}), so it has an explicit entity key.
  • DelayedOrder = m.Concept("DelayedOrder", extends=[Order]) declares a new concept type that inherits from Order.
  • Because Order has identity, DelayedOrder inherits the identifying properties from Order.
  • Subconcepts inherit all properties from their parent concepts. In this example, DelayedOrder inherits the order_id property from Order.
  • Subconcepts can also have their own properties and relationships in addition to what they inherit from their parents. For example, you could add a delay_reason property to DelayedOrder without affecting Order.

You can pass a list with multiple parent Concept objects to the extends parameter to create a subconcept that inherits from multiple bases:

from relationalai.semantics import Model
m = Model("MyModel")
Shipment = m.Concept("Shipment")
Trackable = m.Concept("Trackable")
TrackedShipment = m.Concept("TrackedShipment", extends=[Shipment, Trackable])
  • TrackedShipment inherits from both Shipment and Trackable.
  • Shipment and Trackable are two separate base concepts.
  • TrackedShipment = m.Concept("TrackedShipment", extends=[Shipment, Trackable]) declares a subconcept that inherits from both. Use this pattern when you want a concept to share behavior or properties across multiple bases.
  • When you declare a multi-parent subconcept, it inherits properties from all of its parents. If two parents define a property with the same name, the parent listed first in extends wins.
  • The subconcept’s identity key comes from the first parent in extends that has a declared identity scheme. If another parent also declares identity fields, you still must pass those values to Concept.new.
  • If none of the parents declare identity, the subconcept has no declared identity scheme. In that case, Concept.new derives identity from the values you pass.

(Advanced) Include a concept’s type in its identity scheme

Section titled “(Advanced) Include a concept’s type in its identity scheme”

Use identity_includes_type when you have sibling subconcepts that share the same identity fields, but you still want them to be different entities. It is False by default, so identity is based only on the fields in identify_by (for example shipment_id). When it is False, two sibling subconcepts can “collide” and resolve to the same entity key when you call Concept.new.

To include the concrete subconcept type in the key, set identity_includes_type to True on the shared base concept:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
Shipment = m.Concept(
"Shipment",
identify_by={"shipment_id": Integer},
identity_includes_type=True,
)
OutboundShipment = m.Concept("OutboundShipment", extends=[Shipment])
ReturnShipment = m.Concept("ReturnShipment", extends=[Shipment])
  • identity_includes_type=True makes the subconcept type part of the identity key.
  • This keeps OutboundShipment and ReturnShipment distinct even when they share the same shipment_id.
  • The setting is applied on the shared base concept (Shipment), so it affects all subconcepts that extend it.
  • Set identity_includes_type before creating entities, so you don’t change identity keys mid-stream.

To see what this changes:

  1. Create two entities with the same identity values:

    m.define(
    OutboundShipment.new(shipment_id=1),
    ReturnShipment.new(shipment_id=1),
    )
  2. Confirm you have two entities by selecting Shipment.shipment_id:

    m.select(Shipment.shipment_id).to_df()
    Output
    shipment_id
    0 1
    1 1
  • You have two shipments with shipment_id=1, but they are different entities because their keys include their concept type.
  • If identity_includes_type were False, those two Concept.new calls would compute the same entity key, and you would end up with one entity that appears to be both OutboundShipment and ReturnShipment.
  • With identity_includes_type=True, the key includes the concrete concept type, so the entities stay distinct even when they share the same shipment_id.

Viewing concepts helps you confirm what schema your model has declared so far, especially in notebooks and interactive exploration. Choose Model.concepts when you want everything the model has created. Choose Model.concept_index when you want to look up a concept by name.

To view all concepts created in a model, inspect Model.concepts:

from relationalai.semantics import Model
m = Model("MyModel")
Customer = m.Concept("Customer")
Order = m.Concept("Order")
# Print all concepts declared in the model
print(m.concepts)
  • m.concepts is a list of every concept object created via Model.Concept.
  • This is a good first stop when you are not sure which concept types were declared in the current model instance.

To look up a concept by name, use Model.concept_index:

from relationalai.semantics import Model
m = Model("MyModel")
Customer = m.Concept("Customer")
# Look up the concept by its declared name
print(m.concept_index["Customer"])
  • The keys in m.concept_index are the concept names you pass to Model.Concept.
  • m.concept_index["Customer"] returns the underlying Concept object.