Graph OGM
Map Python classes to nodes and edges. Typed fields, indexes, constraints, lazy/eager relationships, and a fluent query builder.
For Cypher-based graph databases — FalkorDB, ArcadeDB, Neo4j, Memgraph, Apache AGE.
Map classes to nodes, write a relationship, then traverse it with the fluent query builder — no raw Cypher:
from runic.ogm import Field, Node, Relation, Session, create_driver, select
class Person(Node, labels=["Person"]):
id: str = Field(primary_key=True)
name: str
email: str = Field(unique=True)
friends: list["Person"] = Relation(
relationship="FRIENDS",
direction="OUTGOING",
target="Person",
)
driver = create_driver("falkordb", host="localhost", port=6379, graph="myapp")
with Session(driver) as session:
alice = Person(id="alice", name="Alice", email="alice@example.com")
bob = Person(id="bob", name="Bob", email="bob@example.com")
session.add(alice)
session.add(bob)
session.relate(alice, Person.friends, bob)
session.commit()
# Traverse the social graph: everyone Alice is friends with
stmt = (
select(Person).alias("p")
.where(Person.id == "alice")
.traverse(Person.friends).alias("f")
.return_target("f")
)
friends: list[Person] = session.scalars(stmt)
print([p.name for p in friends]) # ['Bob']
# MATCH (p:Person) WHERE p.id = $p0
# OPTIONAL MATCH (p)-[:FRIENDS]->(f:Person)
# RETURN fTrack schema changes as versioned, replayable revision scripts. Generate one with runic revision, then describe the change with op.* calls:
# runic/versions/3f9a12c1_add_person_email_index.py
def upgrade(op) -> None:
op.create_range_index("Person", "email")
def downgrade(op) -> None:
op.drop_range_index("Person", "email")Apply and roll back from the CLI:
runic revision -m "add person email index" # scaffold the script above
runic upgrade # apply pending revisions
runic current # 3f9a12c1 — add person email index
runic downgrade base # roll back to an empty schemaThese snippets barely scratch it. runic is built for the hard parts of real graph work — the things you hit on day two, not day one:
.traverse() calls or use .repeat(min_hops, max_hops) to walk org charts, dependency trees, and recommendation paths without hand-writing *1..5 Cypher.Edge, read it back with all_with_edges(), and filter on the edge..knn() — native, not bolted on.await-ed.upgrade/downgrade workflow runs unchanged across FalkorDB, ArcadeDB, Neo4j, Memgraph, and Apache AGE.Start with a quickstart and you'll have something running in five minutes:
Then go deep:
relate(), edge propertiesop.* call at a glanceBring your own backend. Write your models once. runic handles the Cypher.