Quickstart
runic.ogm maps Python classes to graph nodes and edges. You define a model, open a Session, and call methods — the OGM 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 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/falkordbDefine a model
Every graph entity inherits from Node. Declare properties with Field(). The labels keyword controls which graph labels are applied:
from runic.ogm 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
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 drivers for the full set of supported backends and their kwargs.
Create
Add entities to the session and call commit(). The OGM 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 readableEntities 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 = $titleOnly the mutated fields are included in the SET clause — the OGM 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.ogm 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().
INFO
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.
- concepts — object states, dirty tracking, identity map, type converters
- relationships — lazy loading, eager loading,
relate()/unrelate(), edge properties - session — unit-of-work lifecycle, flush, commit, rollback, raw execute
- api — full API reference