Meet PyRel
This tutorial introduces you to PyRel by walking you through a small supply chain model that finds products at risk during a supply chain disruption. You’ll learn PyRel’s core ideas, learn how to build the model step-by-step, then deploy it to Snowflake and query it with Python or SQL.
- Estimated time
- 30–45 minutes
- Prerequisites
- You’ve finished Install and run PyRel, so you have the starter project, the
RAI_DEVELOPERrole, and araiconfig.yamlfile with adeploymentsection. - You can use the deployment and metadata schemas your administrator created in Set up RelationalAI in Snowflake.
- For SQL queries, you can open a Snowsight worksheet in the same Snowflake account.
- You’ve finished Install and run PyRel, so you have the starter project, the
What can you do with PyRel?
Section titled “What can you do with PyRel?”PyRel is a Python library for building a model of how your business works.
The fundamental class in PyRel is the Model class, which represents a semantic model: a map of your business that gives your data meaning so people and AI agents can reason about it the same way.
A PyRel model captures the key concepts, relationships, and data sources that define your business domain. For example, a model of a manufacturing business would include concepts like suppliers, parts, and products:
- Supplier
supplier_idname - suppliessupplied by
- Part
part_idname - required byrequires
- Product
product_idname
Relationships like “a supplier supplies a part” and “a part is used in a product” define how the concepts are connected. The model also specifies how data maps to the concepts and relationships.
PyRel can compile the model and deploy it as Snowflake objects that can be consumed by SQL queries, dashboards, applications, automation, and AI agents:
- Views
- Tables
- Procedures
- Tasks
Why does that matter? Business rules often get copied into many queries and scripts, and the copies drift apart. With PyRel, you can bake the logic into your database so that it is centralized, consistent, and automatically available to anyone querying the model.
Explore the starter model
Section titled “Explore the starter model”The starter project answers one question: if Northstar Components can’t deliver, which products are affected? The model explorer below shows the model that answers it. Blocks are concepts, and the labeled paths between them are relationships.
Try these:
- Select the Supplier block to see its properties and where its data comes from.
- Select the supplies path to see which two concepts it connects.
- Expand Preview source data to see the CSV rows behind the model.
Select a concept or relationship to see what it means and where its facts come from.
Supplier disruption
Three concepts describe the business objects. Two relationships connect them: a Supplier supplies a Part, and a Product requires a Part.
Supplier
A company that provides parts used by products.
Properties
Scroll horizontally to see all columns.
| Name | Type |
|---|---|
supplier_id (identity) | String |
name | String |
Relationships
- supplies
- Supplier supplies Part
Preview source datasuppliers.csv · 3 Supplier entities
Provides supplier identifiers and names.
data/suppliers.csv
Scroll horizontally to see all columns.
| Column | Maps to |
|---|---|
supplier_id | Identity |
supplier_name | name |
Scroll horizontally to see all columns.
| supplier_id | supplier_name |
|---|---|
| S001 | Northstar Components |
| S002 | Harbor Metals |
| S003 | Apex Plastics |
Part
A component used to assemble or support a product.
Properties
Scroll horizontally to see all columns.
| Name | Type |
|---|---|
part_id (identity) | String |
name | String |
Relationships
- supplies
- Supplier supplies Part
- requires
- Product requires Part
Preview source dataparts.csv · 4 Part entities
Provides part identifiers and names.
data/parts.csv
Scroll horizontally to see all columns.
| Column | Maps to |
|---|---|
part_id | Identity |
part_name | name |
Scroll horizontally to see all columns.
| part_id | part_name |
|---|---|
| P001 | Control board |
| P002 | Temperature sensor |
| P003 | Aluminum enclosure |
| P004 | Mounting bracket |
Product
An item assembled from one or more required parts.
Properties
Scroll horizontally to see all columns.
| Name | Type |
|---|---|
product_id (identity) | String |
name | String |
Relationships
- requires
- Product requires Part
Preview source dataproducts.csv · 3 Product entities
Provides product identifiers and names.
data/products.csv
Scroll horizontally to see all columns.
| Column | Maps to |
|---|---|
product_id | Identity |
product_name | name |
Scroll horizontally to see all columns.
| product_id | product_name |
|---|---|
| PR001 | Cold-chain tracker |
| PR002 | Factory gateway |
| PR003 | Smart thermostat |
Supplier supplies Part
Connects a supplier to a part that the supplier provides.
Fields
Scroll horizontally to see all columns.
| Field | Value |
|---|---|
| Name | supplies |
| From | Supplier |
| To | Part |
Preview source datasupplier_parts.csv · 4 supplies facts
Records which supplier provides each part.
data/supplier_parts.csv
Scroll horizontally to see all columns.
| Column | Maps to |
|---|---|
supplier_id | supplier |
part_id | part |
Scroll horizontally to see all columns.
| supplier_id | part_id |
|---|---|
| S001 | P001 |
| S001 | P002 |
| S002 | P003 |
| S003 | P004 |
Product requires Part
Connects a product to a part needed by that product.
Fields
Scroll horizontally to see all columns.
| Field | Value |
|---|---|
| Name | requires |
| From | Product |
| To | Part |
Preview source dataproduct_parts.csv · 8 requires facts
Records which parts each product needs.
data/product_parts.csv
Scroll horizontally to see all columns.
| Column | Maps to |
|---|---|
product_id | product |
part_id | part |
Scroll horizontally to see all columns.
| product_id | part_id |
|---|---|
| PR001 | P001 |
| PR001 | P002 |
| PR001 | P003 |
| PR002 | P001 |
| PR002 | P003 |
| PR002 | P004 |
| PR003 | P002 |
| PR003 | P004 |
suppliers.csv
Provides supplier identifiers and names.
Populates
- Model item
- Supplier
Source mapping
- supplier_id
- Supplier identity
- supplier_name
- Supplier.name
Preview: 3 rows
Scroll horizontally to see all columns.
| supplier_id | supplier_name |
|---|---|
| S001 | Northstar Components |
| S002 | Harbor Metals |
| S003 | Apex Plastics |
parts.csv
Provides part identifiers and names.
Populates
- Model item
- Part
Source mapping
- part_id
- Part identity
- part_name
- Part.name
Preview: 4 rows
Scroll horizontally to see all columns.
| part_id | part_name |
|---|---|
| P001 | Control board |
| P002 | Temperature sensor |
| P003 | Aluminum enclosure |
| P004 | Mounting bracket |
products.csv
Provides product identifiers and names.
Populates
- Model item
- Product
Source mapping
- product_id
- Product identity
- product_name
- Product.name
Preview: 3 rows
Scroll horizontally to see all columns.
| product_id | product_name |
|---|---|
| PR001 | Cold-chain tracker |
| PR002 | Factory gateway |
| PR003 | Smart thermostat |
supplier_parts.csv
Records which supplier provides each part.
Populates
- Model item
- supplies
Source mapping
- supplier_id
- supplier field
- part_id
- part field
Preview: 4 rows
Scroll horizontally to see all columns.
| supplier_id | part_id |
|---|---|
| S001 | P001 |
| S001 | P002 |
| S002 | P003 |
| S003 | P004 |
product_parts.csv
Records which parts each product needs.
Populates
- Model item
- requires
Source mapping
- product_id
- product field
- part_id
- part field
Preview: 8 rows
Scroll horizontally to see all columns.
| product_id | part_id |
|---|---|
| PR001 | P001 |
| PR001 | P002 |
| PR001 | P003 |
| PR002 | P001 |
| PR002 | P003 |
| PR002 | P004 |
| PR003 | P002 |
| PR003 | P004 |
Understand the project structure
Section titled “Understand the project structure”Open the supplier-disruption folder you used in Install and run PyRel.
Each file has one job:
Directorysupplier-disruption/
Directorydata/ starter facts as CSV files
- suppliers.csv
- parts.csv
- products.csv
- supplier_parts.csv which supplier supplies which part
- product_parts.csv which product requires which part
Directorymodel/ the semantic model
- __init__.py makes the model ready to import
- schema.py what things are
- source.py where the facts come from
- find_affected_products.py the query
- pyproject.toml the Python version and packages the project needs
- raiconfig.yaml your connection and deployment settings
The model is split into two files on purpose:
schema.pydescribes what things are: the concepts, properties, and relationships.source.pydescribes where the facts come from: which rows create which things.
This split means you can change where the data comes from without changing what the model means.
The raiconfig.yaml file isn’t in the download.
You created it in Configure your project.
Define concepts and properties
Section titled “Define concepts and properties”Open model/schema.py.
Here is the whole file:
from relationalai.semantics import Model, String
model = Model("SupplierDisruption")
Supplier = model.Concept("Supplier", identify_by={"supplier_id": String})Supplier.name = model.Property(f"{Supplier} has {String:name}")
Part = model.Concept("Part", identify_by={"part_id": String})Part.name = model.Property(f"{Part} has {String:name}")
Product = model.Concept("Product", identify_by={"product_id": String})Product.name = model.Property(f"{Product} has {String:name}")
Supplier.supplies = model.Relationship(f"{Supplier} supplies {Part}")Product.requires = model.Relationship(f"{Product} requires {Part}")Let’s go through it one piece at a time.
from relationalai.semantics import Model, String
model = Model("SupplierDisruption")Model("SupplierDisruption") creates a new, empty model named SupplierDisruption.
The code saves it in a variable named model.
Everything else in the project adds to this model object.
For details, see the Model class reference.
Supplier = model.Concept("Supplier", identify_by={"supplier_id": String})Supplier.name = model.Property(f"{Supplier} has {String:name}")model.Concept("Supplier", ...)creates a concept namedSupplier.identify_by={"supplier_id": String}tells PyRel that a text value namedsupplier_idmakes each supplier unique. Two rows with the samesupplier_iddescribe the same supplier.model.Property(...)adds a property. The text inside reads like a sentence: “Supplier has name.”{Supplier}is the thing the fact is about, and{String:name}is a text value namedname.Supplier.name = ...attaches the property to the concept, so later code can usesupplier.name.
Part and Product follow the same pattern, with part_id and product_id as their IDs.
Connect concepts with relationships
Section titled “Connect concepts with relationships”The last two lines of schema.py connect the concepts:
Supplier.supplies = model.Relationship(f"{Supplier} supplies {Part}")Product.requires = model.Relationship(f"{Product} requires {Part}")model.Relationship(f"{Supplier} supplies {Part}")creates a relationship between suppliers and parts. The text reads like a sentence and names both ends.Supplier.supplies = ...attaches it toSupplier, so you can writesupplier.supplies(part).Product.requiresworks the same way for products and parts.
One supplier can supply many parts, and one part can be required by many products. These two relationships are the arrows in the concept picture. They let a query move from a supplier to its parts, and then from those parts to products.
Connect source data to the model
Section titled “Connect source data to the model”Open model/source.py.
This file fills the model with facts.
The starter reads small CSV files so it runs anywhere.
Real projects usually read Snowflake tables instead.
To learn how, see Use a Snowflake table with Model.Table.
Here is the whole file:
from pathlib import Path
from pandas import read_csv
from .schema import Part, Product, Supplier, model
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
supplier_rows = model.data(read_csv(DATA_DIR / "suppliers.csv"))model.define( supplier := Supplier.new(supplier_id=supplier_rows["supplier_id"]), supplier.name(supplier_rows["supplier_name"]),)
part_rows = model.data(read_csv(DATA_DIR / "parts.csv"))model.define( part := Part.new(part_id=part_rows["part_id"]), part.name(part_rows["part_name"]),)
product_rows = model.data(read_csv(DATA_DIR / "products.csv"))model.define( product := Product.new(product_id=product_rows["product_id"]), product.name(product_rows["product_name"]),)
supplier_part_rows = model.data(read_csv(DATA_DIR / "supplier_parts.csv"))model.define( Supplier.lookup(supplier_id=supplier_part_rows["supplier_id"]).supplies( Part.lookup(part_id=supplier_part_rows["part_id"]), ))
product_part_rows = model.data(read_csv(DATA_DIR / "product_parts.csv"))model.define( Product.lookup(product_id=product_part_rows["product_id"]).requires( Part.lookup(part_id=product_part_rows["part_id"]), ))Create things from rows
Section titled “Create things from rows”Each concept gets its facts from one CSV file.
Here is data/suppliers.csv:
supplier_id,supplier_nameS001,Northstar ComponentsS002,Harbor MetalsS003,Apex PlasticsAnd here is the code that reads it:
supplier_rows = model.data(read_csv(DATA_DIR / "suppliers.csv"))model.define( supplier := Supplier.new(supplier_id=supplier_rows["supplier_id"]), supplier.name(supplier_rows["supplier_name"]),)read_csv(...)reads the file into a table of rows.model.data(...)gives those rows to the model.Supplier.new(supplier_id=...)creates one supplier for each row, using thesupplier_idcolumn as its ID. The:=saves the new supplier in a variable namedsupplier, so the next line can use it.supplier.name(...)sets each supplier’s name from thesupplier_namecolumn.model.define(...)adds these facts to the model.
So the row S001,Northstar Components becomes one supplier with the ID S001 and the name Northstar Components.
The Part and Product blocks work the same way.
Connect things from rows
Section titled “Connect things from rows”Relationship files hold pairs of IDs.
Here is data/supplier_parts.csv:
supplier_id,part_idS001,P001S001,P002S002,P003S003,P004And here is the code that reads it:
supplier_part_rows = model.data(read_csv(DATA_DIR / "supplier_parts.csv"))model.define( Supplier.lookup(supplier_id=supplier_part_rows["supplier_id"]).supplies( Part.lookup(part_id=supplier_part_rows["part_id"]), ))Supplier.lookup(supplier_id=...)finds the supplier that already has that ID.Part.lookup(part_id=...)finds the part with that ID..supplies(...)connects the two.
So the row S001,P001 means “Northstar Components supplies the Control board.”
The product_parts.csv block connects products to parts with Product.requires in the same way.
Load everything with one import
Section titled “Load everything with one import”The last file in the folder is model/__init__.py:
from .schema import Part, Product, Supplier, modelfrom . import source as source
__all__ = ["Part", "Product", "Supplier", "model"]The line from . import source runs source.py whenever another file imports model.
That’s how the query script gets a model that already has all its facts.
Deploy the model to a dev schema
Section titled “Deploy the model to a dev schema”You deployed the starter model once in Install and run PyRel.
This time, you’ll deploy it with a dev profile.
A profile is a named group of settings in raiconfig.yaml.
With a dev profile, you can later add a prod profile for the same model and switch between them.
-
Open
raiconfig.yamland find thedeploymentsection. It looks like this, with your own schema names:raiconfig.yaml (before) deployment:public_schema: MY_DATABASE.MY_SCHEMAidentity:internal_schema: MY_DATABASE.MY_SCHEMA_METAschedules:on_deploy:interval_s: 0run_on_deploy: trueschedule: on_deploy -
Move the whole
deploymentsection underprofile.dev, and addactive_profile: devbelow it. Indent thedeploymentsection two more levels. Leave yourconnectionssection and other settings as they are.raiconfig.yaml (after) profile:dev:deployment:public_schema: MY_DATABASE.MY_SCHEMAidentity:internal_schema: MY_DATABASE.MY_SCHEMA_METAschedules:on_deploy:interval_s: 0run_on_deploy: trueschedule: on_deployactive_profile: devactive_profile: devtells PyRel to use thedevsettings. -
In the
supplier-disruptionfolder, deploy the model:uvTerminal window uv run rai models deploy --path modelpipTerminal window rai models deploy --path model--path modeltells the command where the model code is. The command creates the deployed objects, fills them with data, and waits until that first data load finishes. This can take a minute or two.If the deploy fails with an
EXECUTE TASKerror, ask your administrator to runGRANT EXECUTE TASK ON ACCOUNT TO ROLE RAI_DEVELOPER;. Then deploy again. -
In Snowsight, open your deployment schema to see what the deploy created:
- Views named
SUPPLIER,PART, andPRODUCT. Each concept becomes a view with a column for its ID and a column for its name. - Tables named
SUPPLIER_SUPPLIESandPRODUCT_REQUIRES. Each relationship becomes a table. Each row holds the IDs of two connected things, such as a supplier ID and a part ID.
The deploy filled the tables once. They don’t change until you deploy again. The metadata schema holds the deployment’s own records, so you don’t need to open it.
- Views named
A prod profile works the same way, with a different schema.
To learn more, see Dev, Staging, and Production environments and Deploy a model.
Query the deployed model
Section titled “Query the deployed model”Now ask the deployed model the starter question: which products need a part from Northstar Components? Choose a tab. Both tabs get the same answer from the same deployed model.
Run the query script:
uv run python find_affected_products.pypython find_affected_products.py product part0 Cold-chain tracker Control board1 Cold-chain tracker Temperature sensor2 Factory gateway Control board3 Smart thermostat Temperature sensorNow that the model is deployed, the script gets its answers from the views and tables in your dev schema. Here is the script:
from model import Part, Product, Supplier, model
unavailable_supplier = Supplier.ref()affected_part = Part.ref()affected_product = Product.ref()
( model.where( unavailable_supplier.name == "Northstar Components", unavailable_supplier.supplies(affected_part), affected_product.requires(affected_part), ) .select( affected_product.name.alias("product"), affected_part.name.alias("part"), ) .inspect())Supplier.ref(),Part.ref(), andProduct.ref()create placeholders. Each one stands for “some supplier,” “some part,” or “some product.”model.where(...)lists the rules the placeholders must follow: the supplier’s name is Northstar Components, the supplier supplies the part, and the product requires the part..select(...)picks what to return: the product name and the part name..alias("product")and.alias("part")name the output columns..inspect()runs the query and prints the results.
Open a Snowsight worksheet.
Use the RAI_DEVELOPER role and your warehouse.
Replace MY_DATABASE.MY_SCHEMA with your deployment schema, then run:
USE SCHEMA MY_DATABASE.MY_SCHEMA;
SELECT p.NAME AS PRODUCT, pt.NAME AS PARTFROM SUPPLIER sJOIN SUPPLIER_SUPPLIES ss ON ss.SUPPLIER = s.SUPPLIER_IDJOIN PART pt ON pt.PART_ID = ss.PARTJOIN PRODUCT_REQUIRES pr ON pr.PART = pt.PART_IDJOIN PRODUCT p ON p.PRODUCT_ID = pr.PRODUCTWHERE s.NAME = 'Northstar Components'ORDER BY 1, 2;| PRODUCT | PART |
|---|---|
| Cold-chain tracker | Control board |
| Cold-chain tracker | Temperature sensor |
| Factory gateway | Control board |
| Smart thermostat | Temperature sensor |
The query follows the same path as the PyRel script:
- It starts at the
SUPPLIERview and finds Northstar Components. - It joins
SUPPLIER_SUPPLIESto find the parts that supplier supplies. - It joins
PRODUCT_REQUIRESto find the products that require those parts.
Because each concept and relationship is a view or table, any SQL tool or BI tool can read the model.
- Each product that depends on a part supplied by Northstar Components.
- The part supplied by Northstar that each product depends on.
- Whether production will stop.
- Which supplier should replace Northstar.
- Answers to those questions, which RelationalAI can provide with a richer model that includes current inventory, lead times, and alternative suppliers, together with the appropriate reasoning.
Clean up
Section titled “Clean up”Deployed objects take up storage and clutter your schema. When you’re done, remove them.
-
Preview what will be removed:
uvTerminal window uv run rai models teardownpipTerminal window rai models teardownThis only prints a list. Nothing is removed yet.
-
Remove the deployed objects:
uvTerminal window uv run rai models teardown --forcepipTerminal window rai models teardown --forceThis removes the views, tables, and other objects the deploy created. Your schemas and your
raiconfig.yamlfile stay, so you can deploy again at any time.
Where to go next
Section titled “Where to go next”Choose a guide based on what you want to learn next: