Relationship
Relationship( model: Model, reading_str: str = "", fields: list[Field] = [], short_name: str = "", allow_no_fields: bool = False, overloads: list[list[Concept]] | None = None, is_unresolved: bool = False,)Represents a relationship between entities in a semantic model.
A Relationship describes how one or more Concepts are related.
You usually create relationships via Model.Relationship or
Model.Property constructors.
Relationships are creates from a reading string that describes the relationship
in a human-readable format. These are typically f-strings that interpolate concept
placeholders into one or more fields, which may be given optional names for indexing
via the fields format specification. Field types can be any concept object or one of the
core types in relationalai.semantics.frontend.core, such as
semantics.frontend.core.String and semantics.frontend.core.Integer.
A relationship is a kind of mapping that associates a sequence of input values to an output value.
The last field is the output field, and all preceding fields are input fields.
You can select a relationship’s fields in a Model.select fragment or use them to build
filter expressions in Model.where.
Relationships are multi-valued: for the same input values, there
may be zero, one, or many matching output values (for example, a person can
have many pets). For this reason, relationships are often given plural names,
like “employers” or “pets”. If you want an attribute that is single-valued
(at most one output value for each input), use Property.
Parameters
Section titled “Parameters”
(modelModel) - The model this relationship belongs to.
(reading_strstr, default:"") - A reading string describing the relationship fields. When omitted andfieldsis provided, a default reading is generated.
(fieldslist[Field], default:[]) - Explicit field definitions for this relationship.
(short_namestr, default:"") - Optional identifier for indexing (for example inmodel.relationship_index).
(allow_no_fieldsbool, default:False) - If True, allow constructing a relationship with zero fields.
(overloadslist[list[Concept]], default:None) - Optional overload signatures for the relationship.
(is_unresolvedbool, default:False) - If True, marks this relationship as unresolved (used for dynamic property access on types likeAny).
Examples
Section titled “Examples”Declare a relationship with an explicit reading string:
>>> from relationalai.semantics import Model, String>>> m = Model()>>> Person = m.Concept("Person")>>> Company = m.Concept("Company")>>> Person.employers = m.Relationship(f"{Person} works at {Company:employer}")Call a relationship to create an expression asserting a fact, which can be
used in Model.define to define facts in the model:
>>> m.define(... alice := Person.new(name="Alice"),... acme := Company.new(name="Acme Inc."),... # Add the fact that Alice works at Acme to the model... alice.employers(acme)... )Call a relationship to create a condition in Model.where that filters query results
based on existing facts:
>>> # Get names of people where acme is one of their employers.>>> m.select(Person.name).where(Person.employers(acme))Declare a relationship with explicit fields instead of a reading string:
>>> from relationalai.semantics.frontend.base import Field>>> Person.employers = m.Relationship(... fields=[Field("employee", Person), Field("employer", Company)],... )Most users should not instantiate this class directly. Prefer using the
Model.Relationship factory method instead.
Methods
Section titled “Methods”.__getattr__()
Section titled “.__getattr__()”Relationship.__getattr__(item)Return a chain that references a relationship on this relationship’s output field.
Relationships can be chained through their output field using normal
Python attribute access. Accessing item returns a Chain
that represents following this relationship and then reading the
relationship/property item on the output field’s concept.
If the item starts with an underscore, this method defers to
normal Python attribute access so internal attributes remain accessible.
Parameters:
(itemstr) - Relationship/property name to access on this relationship’s output concept.
Returns:
Chain- A chained value that represents following this relationship and then readingitemon the relationship’s output concept.
Raises:
relationalai.util.error.RAIException- Raised in the following cases:- The output concept does not have a relationship with this name and
the
model.implicit_propertiesconfig flag is False. - Relationships are accessed on a core concept (for example
StringorInteger).
- The output concept does not have a relationship with this name and
the
Examples:
Chain from a relationship to a property on its output concept:
>>> from relationalai.semantics import Model>>> m = Model()>>> Person = m.Concept("Person")>>> Company = m.Concept("Company")>>> Person.employers = m.Relationship(f"{Person} works for {Company:employer}")>>> m.define(... alice := Person.new(name="Alice"),... acme := Company.new(name="Acme Inc."),... alice.employers(acme)... )>>> # Select the names of Alice's employers by chaining from the>>> # relationship to the Company.name property:>>> m.select(alice.employers.name).to_df()Notes:
Attribute access for relationships is only supported on user concepts.
It is not supported on core (built-in) concepts such as String and
Integer.
.__call__()
Section titled “.__call__()”Relationship.__call__(*args: Any, **kwargs: Any) -> ExpressionReturn an expression asserting a fact in this relationship.
Calling a Relationship creates a composable Expression
that you can use in Model.where and Model.define. A common
pattern is to use a relationship call as a readable condition, for
example owns(alice, boots) to mean “Alice owns Boots”.
This method is typically used in one of two ways:
- As a statement/condition: provide all fields (including the output field) to assert that a specific fact holds.
- To return output values: provide only the input fields and
omit the output field; it is auto-filled with a fresh reference (a
Refplaceholder), and the expression evaluates to that output value. This is useful for selecting “all outputs that match these inputs”.
Relationships are often attached as attributes on a concept (for example
Person.pets = model.Relationship(...)). Then Person.pets(alice, boots)
is the same as pets(alice, boots). Calling through a value is shorthand:
alice.pets(boots) means pets(alice, boots). Person.pets(boots)
is another useful form: it means “Boots is some person’s pet” and can be used
to filter which Person values match.
Parameters:
-
(*argsAny, default:()) - Values for this relationship’s fields. Provide either:Nvalues to specify allNfields explicitly, orN-1values to omit the output field, in which case it is auto-filled with a freshRefthat represents output values matching the provided inputs.
-
(**kwargsAny, default:{}) - Extra named values attached to the resulting expression. Currently, these keyword arguments are ignored.
Returns:
Expression- A composable expression representing this relationship applied to the provided values.
Raises:
relationalai.util.error.RAIException- Raised in the following cases:- Too few or too many positional arguments are provided.
- A
TableSchemais passed as an argument.
NotImplementedError- If a Python value cannot be converted to a supported literal.
Examples:
Attach a relationship and assert one fact:
>>> from relationalai.semantics import Model>>> m = Model()>>> Person = m.Concept("Person")>>> Pet = m.Concept("Pet")>>> Person.pets = m.Relationship(f"{Person} cares for {Pet}")>>> m.define(... alice := Person.new(name="Alice"),... boots := Pet.new(),... alice.pets(boots)... )Use the concept form as a readable filter (“Boots is some person’s pet”):
>>> m.select(Person.name).where(Person.pets(boots)).to_df()Return output values for a relationship with multiple input fields:
>>> from relationalai.semantics import String>>> Company = m.Concept("Company")>>> Person.works_as = m.Relationship(f"{Person} works at {Company} as {String:title}")>>> m.define(acme := Company.new(name="Acme Inc."), alice.works_as(acme, "Engineer"))>>> # Select Alice's job title at Acme Inc.>>> m.select(alice.works_as(acme)).to_df()Notes:
This method does not write data by itself. To add facts to a model, use
Model.define with the returned expression.
.__getitem__()
Section titled “.__getitem__()”Relationship.__getitem__(field: str | int | Concept) -> FieldRefReturn a reference to one of this relationship’s fields.
Indexing a Relationship returns a FieldRef that
identifies a specific field in the relationship’s fields list.
This is commonly used when selecting or joining on non-output fields.
The field can be selected by:
- Integer position (0-based):
rel[0] - Exact field name:
rel["owner"] - Field type (concept):
rel[Person]
Parameters:
(fieldstr|int|Concept) - Field selector.
Returns:
FieldRef- A reference to the selected field.
Raises:
IndexError- Iffieldis an integer index that is out of range.KeyError- If no field matches the provided selector.
Examples:
Access fields by name:
>>> from relationalai.semantics import Model, String>>> m = Model()>>> Person = m.Concept("Person")>>> Person.pets = m.Relationship(f"{Person:pet_parent} has pet {String:pet_name}")>>> m.define(alice := Person.new(name="Alice"), alice.pets("boots"))>>> m.select(Person.pets["pet_parent"].name, Person.pets["pet_name"]).to_df()Access fields by position:
>>> m.select(Person.pets[0].name, Person.pets[1]).to_df().alt()
Section titled “.alt()”Relationship.alt(reading_str: str) -> ReadingAdd an alternative reading for this relationship.
An alternative reading is a Reading that refers to the same underlying
relationship fields but uses a different human-readable reading string.
This is commonly used to introduce inverse names (for example, defining
Project.team as an alternative reading of Team.works_on) or to
provide a more natural phrase while keeping a single underlying relationship.
The returned Reading can be used anywhere a relationship can:
you can call it to build expressions, select it in queries, and attach
it as an attribute on a concept.
Parameters:
(reading_strstr) - A reading string describing the relationship using the same field concepts and field names as this relationship. The string is parsed using the same placeholder format as relationship creation, typically written as an f-string (for examplef"{Team:team} works on {Project}").
Returns:
Reading- A relationship-like handle that renders usingreading_str.
Raises:
relationalai.util.error.RAIException- Raised in the following cases:- The reading references a concept name that cannot be found in the model.
- The reading contains the serialized form of a Python type
(for example
<class 'int'>) instead of a concept.
ValueError- If the reading’s fields do not match this relationship’s fields.
Examples:
Create a relationship and add an inverse reading:
>>> from relationalai.semantics import Model>>> m = Model()>>> Team = m.Concept("Team")>>> Project = m.Concept("Project")>>> Team.works_on = m.Relationship(f"{Team} works on {Project}")>>> m.define(... alpha := Team.new(name="Alpha"),... phoenix := Project.new(name="Phoenix"),... alpha.works_on(phoenix)... )>>> # Declare an alternative reading so we can also say "Project involves Team">>> Project.team = Team.works_on.alt(f"{Project} involves {Team}")>>> # Use the alternative reading to select the team for the Phoenix project>>> m.where(Project.name == "Phoenix").select(Project.team.name).to_df()Referenced By:
RelationalAI Documentation └── Build With RelationalAI └── Understand how PyRel works > Build a semantic model └── Declare relationships and properties └── Declare an alternate reading for a relationship
.annotate()
Section titled “.annotate()”Relationship.annotate(*annos: Expression | Relationship) -> RelationshipAttach one or more annotations to this relationship.
An annotation is recorded on the relationship and emitted into the compiled model. Annotations are primarily used by backends and internal tooling for debugging and support workflows (for example, to attach metadata such as tracking labels). In general you should not need to use annotations unless RelationalAI support instructs you to add them for diagnostic purposes.
Parameters:
(*annosExpression|Relationship, default:()) - One or more annotation nodes to attach.
Returns:
Relationship- This relationship (returned to enable fluent chaining).
Raises:
relationalai.util.error.RAIException- Raised when an annotation argument has an unsupported type.
.to_df()
Section titled “.to_df()”Relationship.to_df() -> DataFrameMaterialize this relationship as a pandas DataFrame.
This is a convenience method for inspecting the set of facts currently asserted for the relationship. It selects all fields of the relationship (including non-output fields) and filters to rows where the relationship holds.
If you want to select only certain fields or apply additional filters,
build a query with Model.select
and Model.where instead.
Returns:
pandas.DataFrame- A DataFrame with one column per relationship field and one row per matching tuple.
Examples:
Materialize all tuples of a relationship:
>>> from relationalai.semantics import Model, String>>> m = Model()>>> Person = m.Concept("Person")>>> Pet = m.Concept("Pet")>>> Person.pets = m.Relationship(f"{Person} has pet {String:pet_name}")>>> m.define(... alice := Person.new(name="Alice"),... bob := Person.new(name="Bob"),... boots := Pet.new(name="Boots"),... miso := Pet.new(name="Miso"),... alice.pets(boots),... bob.pets(miso),... )>>> Person.pets.to_df()Notes:
to_df can be expensive for large relationships. Calling this method
executes a query and materializes the full result in memory as a pandas
DataFrame. In particular, it selects all fields of the relationship,
which can produce many rows and wide columns.
.inspect()
Section titled “.inspect()”Relationship.inspect(end: str = "\n")Print this relationship as a pandas DataFrame.
This is a convenience method for quick interactive inspection. It is
equivalent to print(self.to_df()); print(end=end).
Parameters:
(endstr, default:“\n”) - String appended after the DataFrame is printed. Default is a newline.
Returns:
None- This method returnsNoneand prints the DataFrame to stdout.
Examples:
Inspect all instances of a relationship:
>>> from relationalai.semantics import Model, String>>> m = Model()>>> Person = m.Concept("Person")>>> Person.pets = m.Relationship(f"{Person} has pet {String:pet_name}")>>> m.define(alice := Person.new(name="Alice"), alice.pets("boots"))>>> Person.pets.inspect()Notes:
Like Relationship.to_df,
this method executes a query and materializes the full result in memory.
For large relationships, prefer building a query that selects only the
fields you need (for example via
Model.select).
Inheritance Hierarchy
Section titled “Inheritance Hierarchy”Used By
Section titled “Used By”semantics ├── frontend > base │ ├── Aggregate │ ├── Chain │ │ └── annotate │ ├── Concept │ │ └── annotate │ ├── Expression │ ├── FieldRef │ ├── Fragment │ │ └── annotate │ ├── Reading │ └── Relationship │ └── annotate └── reasoners > graph > core └── Graph ├── adamic_adar ├── common_neighbor ├── cosine_similarity ├── degree ├── degree_centrality ├── distance ├── indegree ├── inneighbor ├── jaccard_similarity ├── local_clustering_coefficient ├── neighbor ├── outdegree ├── outneighbor ├── preferential_attachment ├── reachable ├── triangle_count ├── weakly_connected_component ├── weighted_degree ├── weighted_indegree └── weighted_outdegree
Returned By
Section titled “Returned By”semantics ├── frontend > base │ ├── Chain │ │ └── annotate │ ├── Model │ │ └── Relationship │ ├── Relationship │ │ └── annotate │ ├── Table │ │ └── __iter__ │ └── TableSchema │ └── get_columns └── reasoners > graph > core └── Graph ├── adamic_adar ├── average_clustering_coefficient ├── betweenness_centrality ├── common_neighbor ├── cosine_similarity ├── degree ├── degree_centrality ├── diameter_range ├── distance ├── eigenvector_centrality ├── indegree ├── infomap ├── inneighbor ├── is_connected ├── jaccard_similarity ├── label_propagation ├── local_clustering_coefficient ├── louvain ├── neighbor ├── num_edges ├── num_nodes ├── num_triangles ├── outdegree ├── outneighbor ├── pagerank ├── preferential_attachment ├── reachable ├── triangle ├── triangle_count ├── unique_triangle ├── weakly_connected_component ├── weighted_degree ├── weighted_indegree └── weighted_outdegree
Referenced By
Section titled “Referenced By”RelationalAI Documentation ├── Build With RelationalAI │ └── Understand how PyRel works > Build a semantic model │ ├── Declare relationships and properties │ │ └── Declare an alternate reading for a relationship │ └── Define base facts │ └── Define relationship facts └── Release Notes └── Python API Release Notes └── What’s New in Version 1.0.0 └── Breaking Changes