Skip to content

runicGraph schema migrations and OGM

For Cypher-based graph databases — FalkorDB, ArcadeDB, Neo4j, Memgraph, Apache AGE.

runic

Quick look — OGM

Map classes to nodes, write a relationship, then traverse it with the fluent query builder — no raw Cypher:

python
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 f

Quick look — Migration

Track schema changes as versioned, replayable revision scripts. Generate one with runic revision, then describe the change with op.* calls:

python
# 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:

bash
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 schema

There's more under the surface

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

  • Multi-hop and variable-length traversals — chain .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 properties as first-class data — model the relationship itself with Edge, read it back with all_with_edges(), and filter on the edge.
  • Lazy vs. eager loading, on your terms — no hidden N+1 surprises; you decide what gets fetched and when.
  • Vector KNN and fulltext search — declare the index on your model and query it with .knn() — native, not bolted on.
  • Async that mirrors the sync API — the same calls, await-ed.
  • Migrations that travel — the same upgrade/downgrade workflow runs unchanged across FalkorDB, ArcadeDB, Neo4j, Memgraph, and Apache AGE.

Where to go next

Start with a quickstart and you'll have something running in five minutes:

Then go deep:

Bring your own backend. Write your models once. runic handles the Cypher.

runic - Graph schema migrations and OGM for Cypher-based graph databases. · Impressum