Skip to content

Declare concepts

Concepts are the types in your semantic model that represent the entities in your domain, like Customer, Order, and Shipment. This guide covers how to:

  • Declare concepts in your model
  • Create concept hierarchies with inheritance
  • Inspect the concepts declared in a model

For how to define instances of those concepts, called entities, see Define Base Facts.

A concept is a type in your semantic model that you declare with Model.Concept(). It names a kind of thing you want to talk about and gives that kind of thing a consistent meaning everywhere you use it. For example, Customer, Order, and Shipment are concepts.

You use concepts to:

  • Declare the schema of your model.
  • Describe how things are related to each other.
  • Write logic in terms of your domain concepts instead of raw tables and columns.

Concepts are types, not data. Declaring a concept does not create any entity instances. You create instances later with Concept.new() and Model.define().

There are two broad kinds of concepts:

  • Entity types are things in your domain that you expect to have identity. Examples include Customer, Order, Product, and Shipment. Entity types are where you typically care about identity schemes, inheritance, and relationships.
  • Value types are the kinds of values you store on entities. Examples include order IDs, SKUs, amounts, timestamps, and email addresses. Value types are most often used as property value types and as identity field types. They are often declared as subconcepts of primitive concepts like String and Integer.

When you create an entity, the model assigns it an internal key that identifies it. That key is computed from the identifying property values in the concept’s identity scheme. This is similar to a primary key in SQL.

At a high level:

  • If two records have the same identifying property values, they refer to the same entity.
  • If the identifying property values differ, they refer to different entities.

You do not have to define an identity scheme for a concept. In that case, the model derives identity for you.

Primitive concepts are built-in value types that represent common scalar domains. You use them when you declare identity properties and fields in relationships and properties.

Common primitive concepts include:

  • Integer for whole numbers.
  • Float for fractional numbers.
  • String for text.
  • Boolean for true/false values.
  • Date and DateTime for calendar dates and points in time.

When you need a value type that carries domain meaning, declare a named value type by creating a subconcept of a primitive. For example, you can declare EmailAddress as a concept that extends String.

Use the Model.Concept() method to declare a concept.

You can declare a concept with or without an identity scheme. Use the following table to decide which approach to use:

What to useWhen to use it
Use an identity schemeWhen you want to define a clear identity scheme for your concept and have precise control over how entities are created and matched. This is the most common case for entity types.
Use implicit identityWhen you are declaring a value type or a supertype that does not require a specific identity scheme.

There are two ways to declare a concept with an identity scheme. Use the following table to decide which approach to use:

What to useWhen to use it
identify_by parameterWhen you want to declare a concept and create its identifying properties in one step. This is the most common case.
Concept.identify_by()When you need to reference an existing property as an identity field.

Pass the identify_by parameter to Model.Concept()

Section titled “Pass the identify_by parameter to Model.Concept()”

You can declare a concept’s identity scheme by passing the identify_by parameter to Model.Concept() and specifying which properties make up identity in a dictionary:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
# Single property identity
Customer = m.Concept("Customer", identify_by={"id": Integer})
Product = m.Concept("Product", identify_by={"id": Integer})
Order = m.Concept("Order", identify_by={"id": Integer})
# Composite identity
OrderItem = m.Concept("OrderItem", identify_by={"order": Order, "product": Product})
  • Each m.Concept(...) call declares a concept type and returns a Concept object. The Python variables Customer, Order, and OrderItem are just handles you can reference later.
  • identify_by={...} declares which properties make up the concept’s identity scheme. These properties are required when you later create entities with Concept.new().
  • Customer and Order are both identified by a single property: id and id, respectively.
  • OrderItem is identified by a composite key made up of order and product properties of type Order and Product, respectively. Both properties together determine identity for OrderItem.
  • The concept name you pass to Model.Concept() is the canonical name for that concept in your model. It will appear as the key name when you view the model’s concepts.
  • 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.
  • identify_by={...} automatically creates properties on the concept with the specified names and types.

(Advanced) Use Concept.identify_by() to reference existing properties

Section titled “(Advanced) Use Concept.identify_by() to reference existing properties”

Use Concept.identify_by() when a concept’s identity scheme needs to reference a property you have already declared:

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.

To declare a concept with implicit identity, call Model.Concept() without an identify_by parameter:

from relationalai.semantics import Model
m = Model("MyModel")
Customer = m.Concept("Customer")
Order = m.Concept("Order")
Shipment = m.Concept("Shipment")
  • Because no identify_by parameter is provided, these concepts use implicit identity. The model derives an identity scheme for each concept based on the properties that are created when you later define entities with Concept.new().

Use Model.Concept() with the extends parameter to declare a subconcept that inherits from one or more parent concepts. The subconcept inherits all properties and relationships of its parent(s) and can also have its own additional properties and relationships.

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.

Pass a list with one parent Concept object to the extends parameter to create a subconcept that inherits from a single parent:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
Order = m.Concept("Order", identify_by={"id": Integer})
DelayedOrder = m.Concept("DelayedOrder", extends=[Order])
  • DelayedOrder is a subconcept of Order.
  • Order is declared with identity (identify_by={"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. If the parent has an identity scheme, the subconcept inherits that identity scheme and the same identifying properties. DelayedOrder.filter_by(id=123) and Order.filter_by(id=123) refer to the same entity.
  • Subconcepts can also have their own properties and relationships in addition to what they inherit from their parents. For example, DelayedOrder could have a delay_reason property that Order does not have.

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 Integer, Model
m = Model("MyModel")
Shipment = m.Concept("Shipment", identify_by={"id": Integer})
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.
  • 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 first parent in the extends list that has a declared identity scheme is the one that determines the identity scheme for the subconcept. Because Shipment has identity, TrackedShipment inherits the identifying properties from Shipment. TrackedShipment.filter_by(id=123) and Shipment.filter_by(id=123) refer to the same entity.

Subconcepts can declare their own identity scheme that is different from their parent(s) by passing an identify_by parameter to Model.Concept() in addition to extends:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
Shipment = m.Concept("Shipment", identify_by={"id": Integer})
ReturnShipment = m.Concept(
"ReturnShipment", extends=[Shipment], identify_by={"rma": Integer}
)
  • ReturnShipment is a subconcept of Shipment but has an alternate identity scheme based on an rma property instead of id.
  • An alternative identity scheme allows ReturnShipment entities to be identified by either the id property inherited from Shipment or the new rma property declared on ReturnShipment. In other words, if a ReturnShipment entity has an id of 123 and an rma of 456, the same entity can be referred to by either Shipment.filter_by(id=123) or ReturnShipment.filter_by(rma=456).

If you need sibling subconcepts to be distinct from each other even when they share the same identity field values, you can include the concept type in the identity scheme by setting identity_includes_type=True on the shared parent concept:

from relationalai.semantics import Integer, Model
m = Model("MyModel")
Shipment = m.Concept(
"Shipment",
identify_by={"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 name part of the identity key.
  • This keeps OutboundShipment and ReturnShipment distinct even when they share the same id. In other words, OutboundShipment.filter_by(id=123) and ReturnShipment.filter_by(id=123) refer to different entities. This would mean that there are two distinct Shipment entities with id=123.
  • The setting is applied on the shared base concept (Shipment), so it affects all subconcepts that extend it.

Viewing concepts helps you confirm what schema your model has declared so far, especially in notebooks and interactive exploration.

There are two ways to view the concepts declared in the current model instance. Use the following table to decide which approach to use:

What to useWhen to use it
Model.concept_indexWhen you want to look up a concept by name.
Model.conceptsWhen you want to iterate over all concepts declared in the model.

You can view look up a concept by name or view a mapping of all concept names to their corresponding Concept objects by inspecting Model.concept_index:

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

You can get a list of all Concept objects declared in the model by inspecting Model.concepts:

from relationalai.semantics import Model
m = Model("MyModel")
Customer = m.Concept("Customer")
Order = m.Concept("Order")
# View a list of all Concept objects declared in the model
print(m.concepts)
  • m.concepts is a list of every concept object created via Model.Concept().
  • This can be useful for iterating over all concepts declared in the model.