Skip to content

Relationship

relationalai.semantics.frontend.base
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.

  • model

    (Model) - The model this relationship belongs to.
  • reading_str

    (str, default: "") - A reading string describing the relationship fields. When omitted and fields is provided, a default reading is generated.
  • fields

    (list[Field], default: []) - Explicit field definitions for this relationship.
  • short_name

    (str, default: "") - Optional identifier for indexing (for example in model.relationship_index).
  • allow_no_fields

    (bool, default: False) - If True, allow constructing a relationship with zero fields.
  • overloads

    (list[list[Concept]], default: None) - Optional overload signatures for the relationship.
  • is_unresolved

    (bool, default: False) - If True, marks this relationship as unresolved (used for dynamic property access on types like Any).

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.

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:

  • item

    (str) - Relationship/property name to access on this relationship’s output concept.

Returns:

  • Chain - A chained value that represents following this relationship and then reading item on 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_properties config flag is False.
    • Relationships are accessed on a core concept (for example String or Integer).

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.

Relationship.__call__(*args: Any, **kwargs: Any) -> Expression

Return 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 Ref placeholder), 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:

  • *args

    (Any, default: ()) - Values for this relationship’s fields. Provide either:

    • N values to specify all N fields explicitly, or
    • N-1 values to omit the output field, in which case it is auto-filled with a fresh Ref that represents output values matching the provided inputs.
  • **kwargs

    (Any, 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 TableSchema is 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.

Relationship.__getitem__(field: str | int | Concept) -> FieldRef

Return 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:

  • field

    (str | int | Concept) - Field selector.

Returns:

  • FieldRef - A reference to the selected field.

Raises:

  • IndexError - If field is 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()
Relationship.alt(reading_str: str) -> Reading

Add 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_str

    (str) - 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 example f"{Team:team} works on {Project}").

Returns:

  • Reading - A relationship-like handle that renders using reading_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
Relationship.annotate(*annos: Expression | Relationship) -> Relationship

Attach 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:

Returns:

  • Relationship - This relationship (returned to enable fluent chaining).

Raises:

  • relationalai.util.error.RAIException - Raised when an annotation argument has an unsupported type.
Relationship.to_df() -> DataFrame

Materialize 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.

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:

  • end

    (str, default: “\n”) - String appended after the DataFrame is printed. Default is a newline.

Returns:

  • None - This method returns None and 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).

RelationshipVariableDSLBase
 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
 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
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