Mappings

This page explains the building blocks of runic.orm — Node, Edge, Field, object states, dirty tracking, and the identity map.

Node and Edge

Every graph entity inherits from either Node or Edge.

from runic.orm import Edge, Field, Node

class Person(Node, labels=["Person"]):
    id: str = Field(primary_key=True, generated=True)
    name: str
    email: str = Field(index=True, unique=True)

class InvitationEdge(Edge, type="INVITED_TO"):
    role: str
    status: str
    invited_at: str

labels controls which FalkorDB labels are applied. Multi-label nodes implement polymorphic hierarchies — see Relationships.

primary_label (optional) is the label used in MATCH predicates when the node has more than one label:

class Location(Node, labels=["Location"], primary_label="Location"):
    id: str
    title: str

class Country(Location, labels=["Location", "Country"], primary_label="Location"):
    iso_code: str = Field(unique=True)

See also

examples/orm/01_simple_crud.py

Defines a Node with Field descriptors and walks through all object states in one file.

Field and Relation descriptors

Properties and relationships are declared with separate functions for a clean separation of concerns:

  • Field() — scalar properties, index hints, and constraints.

  • Relation() — graph relationships (edges).

Both return FieldDescriptor typed as Any, so name: str = Field() is accepted by type checkers without error.

Field parameters

Parameter

Type

Description

default

Any

Python default value (evaluated lazily via default_factory for mutable types)

index

bool

Create a RANGE index

index_type

str

"FULLTEXT" or "VECTOR"

unique

bool

Unique constraint

required

bool

Validated on save; raises FieldValidationError

converter

TypeConverter

Custom encode/decode; omit for datetime, Enum, Vector, and GeoLocation — converters are assigned automatically

generated

bool

FalkorDB assigns the node ID on CREATE

interned

bool

Store via FalkorDB’s intern() for deduplication of repeated strings (country names, tags, status codes, etc.)

Relation parameters

Parameter

Type

Description

relationship

str

Edge-type string (required)

direction

str

"OUTGOING", "INCOMING", or "BOTH" (required)

target

str | type

Entity class (or forward-reference string) for the other end (required)

edge_model

str | type

Optional Edge subclass holding edge properties

lazy

bool

Delay relationship loading (default True)

cascade

bool

Auto-add related entities when the owning entity is added to a session

default

Any

Default value (defaults to None)

Object states

Each entity lives in exactly one state at any time, mirroring SQLAlchemy:

State

When the object enters it

Transient

Created with Entity(...), not yet known to any session

Pending

After session.add(entity); written on flush()

Persistent

Loaded from the graph, or after the first successful flush()

Deleted

After session.delete(entity); DETACH DELETE``d on ``flush()

Detached

After session.expunge(entity) or session.close()

Dirty tracking

Two private flags drive query selection:

  • _newTrue until the first successful flush. Mapper emits CREATE when this is true.

  • _dirtyTrue when any field is written on a persistent entity. Mapper emits MERGE SET when this is true.

The descriptor __set__ sets _dirty = True automatically. The Session clears _dirty after a successful flush().

Identity map

The Session keeps one Python instance per (EntityClass, primary_key) pair. Calling session.get(Person, "alice") twice within the same session returns the same object.

with Session(graph) as session:
    a = session.get(Person, "alice")
    b = session.get(Person, "alice")
    assert a is b   # True

Repository reads also register entities in the identity map.

See also

examples/orm/02_polymorphic_locations.py

Multi-label nodes (Location Country, City), primary_label, and polymorphic repository queries.

Type converters

The ORM assigns converters automatically for well-known annotation types — no converter= argument needed:

Annotation type

Converter assigned automatically

datetime

DatetimeConverter — stores as ISO-8601 string

Enum subclass

EnumConverter — stores .value

Vector

VectorConverter — stores via vecf32()

GeoLocation

GeoLocationConverter — stores via point()

from datetime import datetime
from enum import StrEnum
from runic.orm import Field, GeoLocation, Node, Vector

class Status(StrEnum):
    ACTIVE = "active"
    ARCHIVED = "archived"

class Place(Node, labels=["Place"]):
    id: str
    status: Status                         # EnumConverter auto-assigned
    created_at: datetime                   # DatetimeConverter auto-assigned
    embedding: Vector = Field(index_type="VECTOR")  # VectorConverter
    location: GeoLocation                  # GeoLocationConverter

An explicit converter= always takes precedence over auto-assignment.

Interned strings

Use interned=True to store a string property via FalkorDB’s intern() function, which deduplicates the value across the database. Useful for high-cardinality-but-low-variety fields like country names or status codes:

class Person(Node, labels=["Person"]):
    id: str = Field()
    country: str = Field(interned=True)

Custom converters

Implement TypeConverter (to_graph / from_graph) for any type not covered above. Set cypher_fn on the converter class to wrap the Cypher parameter with a FalkorDB function:

from runic.orm import TypeConverter

class MyConverter(TypeConverter):
    cypher_fn = "myFunc"   # emits myFunc($field) in Cypher

    def to_graph(self, value): ...
    def from_graph(self, value): ...

See also

examples/orm/06_native_types.py

Vector, GeoLocation, interned strings, datetime and Enum auto-converters in action.

Metadata registry

All Node and Edge subclasses are registered automatically in the global metadata singleton when the class is defined. Forward references in target= strings are resolved at import time.

from runic.orm import metadata

for node_meta in metadata.all_nodes():
    print(node_meta.cls.__name__, node_meta.labels)