Quickstart¶
runic.orm maps Python classes to graph nodes and edges. You define
a model, open a Session, and call methods —
the ORM generates Cypher, executes it, and hands back typed Python objects.
The example below uses FalkorDB. Swap the create_driver arguments for any
other supported backend — see Supported Drivers and Installation.
Installation¶
uv add "runic-py[falkordb]" # or: pip install "runic-py[falkordb]"
Start a local FalkorDB instance for this example:
docker run -p 6379:6379 falkordb/falkordb
Define a model¶
Every graph entity inherits from Node.
Declare properties with Field().
The labels keyword controls which graph labels are applied:
from runic.orm import Field, Node, Repository, Session, create_driver
class Language(Node, labels=["Language"]):
id: str = Field(primary_key=True)
title: str = Field()
code: str = Field(unique=True)
Every model must have exactly one primary-key field. runic uses it to
build MATCH (n:Language {id: $id}) predicates and to key the session’s
identity map.
See also
Core Concepts — Node, Edge, Field, Relation, object states, and dirty tracking
Connect¶
Pass a GraphDriver to Session. The driver
holds the connection; the session holds the unit of work. Create the driver
once per application process and share it across sessions:
driver = create_driver("falkordb", host="localhost", port=6379, graph="myapp")
See Supported Drivers for the full set of supported backends and their kwargs.
Create¶
Add entities to the session and call commit(). The ORM emits a
CREATE statement for each new entity on flush(), which happens
automatically when you call commit():
with Session(driver) as session:
lang = Language(id="en", title="English", code="en")
session.add(lang)
session.commit()
# lang is now Persistent — id, title, code are readable
Entities created outside a session are transient. They become pending
after session.add() and persistent after the first successful flush.
Read¶
Use a Repository for collection
reads, or session.get() for a single lookup by primary key:
with Session(driver) as session:
repo = Repository(session, Language)
all_langs: list[Language] = repo.find_all()
english: Language | None = session.get(Language, "en")
session.get() returns None if the key does not exist. Within the
same session, calling session.get(Language, "en") a second time returns
the same Python object (identity map — no extra Cypher).
Update¶
Mutate a field on a persistent entity. The descriptor sets _dirty = True;
commit() emits a MERGE … SET for all dirty fields:
with Session(driver) as session:
en: Language | None = session.get(Language, "en")
assert en is not None
en.title = "English (UK)" # marks _dirty = True
session.commit() # emits MERGE (n:Language {id: $id}) SET n.title = $title
Only the mutated fields are included in the SET clause — the ORM does
not overwrite fields it did not touch.
Delete¶
Mark an entity for deletion with session.delete(); the DETACH DELETE
runs on flush():
with Session(driver) as session:
en: Language | None = session.get(Language, "en")
assert en is not None
session.delete(en)
session.commit()
Query builder¶
For filtered, ordered, or paginated reads use select()
to build a composable statement and execute it via the session:
from runic.orm import select
stmt = (
select(Language)
.where(Language.code == "en")
.order_by(Language.title)
)
with Session(driver) as session:
results: list[Language] = session.scalars(stmt)
The builder is lazy — nothing is sent to the database until you pass the
statement to a session execution method such as session.scalars(),
session.scalar(), or session.count().
Note
The legacy session.query(Language).where(...).all() pattern is still
fully supported; select() is preferred because it lets you build the
statement outside the session scope (e.g. across multiple if branches)
before executing it once.
See also
Query Builder — full query-builder reference with Cypher output
Next steps¶
See also
- examples/orm/01_simple_crud.py
End-to-end runnable example: create, read, update, delete, and basic query-builder usage.
Core Concepts — object states, dirty tracking, identity map, type converters
Relationships — lazy loading, eager loading, relate() / unrelate(), edge properties
Session & Unit of Work — unit-of-work lifecycle, flush, commit, rollback, raw execute
ORM API Reference — full API reference