Docs
View as markdown

Python API

The relate-db package's module, classes and method signatures, open options, value mapping in both directions, exceptions with their codes, and limits.

python

The relate Python client A language package that runs the relate engine inside a program's process: the Python, Node.js, Rust, Java, Go and Swift clients. Read more in the glossary is the relate-db package, imported as relate: a native extension that opens a store A relate database: a directory, a single .rl file, or :memory:, opened with --database. Read more in the glossary and runs the engine in the calling process. It offers two layers. relate.connect() returns a Connection in the style of Python's database API (PEP 249), with cursors whose rows are dicts. relate.Database is the engine handle underneath, with Arrow output and the raw QueryResult. The signatures on this page are read from the package source and checked against relate-db 0.1.2.

Synopsis

import relate

conn = relate.connect(database=":memory:", namespace="neo4j", read_only=False)
cur = conn.execute(query, params=None)        # a Cursor holding every row
rows = cur.fetchall()                         # a list of Record dicts
summary = cur.consume()                       # a ResultSummary
conn.commit()                                 # a bookmark "relate:<n>", or None
conn.close()

db = relate.Database(path=":memory:", namespace="neo4j", *, read_only=False)
result = db.execute(query, params=None)       # a QueryResult
ipc = db.execute_arrow(query, params=None)    # Arrow IPC stream bytes

Module

NameKindValue or behavior
connect(database=":memory:", namespace="neo4j", read_only=False)functionOpens a store and returns a Connection
Connection, Cursor, Record, ResultSummary, ResultGraphclassesThe database-API layer
Database, QueryResultclassesThe native layer
Node, Relationship, Path, Point, Duration, Vector, TemporalclassesValues in results; Point and Duration are also built by callers
Error (alias RelateError) and its subclasses, Warning, RelateNativeErrorexceptionsSee Errors
apilevelstr"2.0"
threadsafetyint1: threads may share the module, not a connection
paramstylestr"named": parameters are $name with a dict
__version__strThe client version string, "0.1.2" in this release (a literal in the package, not read from the installed metadata)
STRING, BINARY, NUMBER, DATETIME, ROWIDtype objectsThe type_code values of Cursor.description
Date, Time, Timestamp, Binary, DateFromTicks, TimeFromTicks, TimestampFromTicksconstructorsPEP 249 constructors over datetime and bytes
relate.vizmoduleOptional graph visualization over the neo4j-viz package; imported on its own, see relate.viz

Open options

relate.connect(), relate.Connection() and relate.Database() take the same three options.

OptionDefaultAccepted values
database (path on Database)":memory:"":memory:"; a directory path (a directory store); a path ending in .rl (a single-file store). A missing store is created, unless read_only is set
namespace"neo4j""neo4j", "system" or "system_ontologies", in any case; another name raises OperationalError ending Graph not found: <name>
read_onlyFalseTrue loads the store once and refuses writes and checkpoints; a missing store raises OperationalError. Keyword-only on Database
Environment variableEffect on an open
RELATE_SNAPSHOT_FORMATThe codec of a new, empty directory store: parquet (default), json or markdown; an existing store keeps its own
RELATE_LOAD_CSV_DIRThe directory local LOAD CSV may read; unset, local LOAD CSV fails with LOAD CSV file unavailable

A file-backed store has one writing open at a time, in this process or any other; a second raises OperationalError: storage locked: <path>, where <path> is the .rl file, or <store>/.cypherlite.writer for a directory store. A directory store also opens read_only=True beside its writer; a .rl store does not.

Connection

MemberReturnsBehavior
execute(query, params=None)CursorRuns one statement; opens a transaction first when autocommit is False and none is open
executemany(query, params_list)CursorRuns the statement once per dict; the cursor holds the last run
cursor()CursorA new cursor bound to the connection
autocommitboolDefault True: each statement commits on its own. Setting True commits an open transaction
commit()str or NoneCommits the open transaction and returns its bookmark, relate:<n>; None when nothing was committed
rollback()NoneDiscards the open transaction; a no-op without one
namespacestrThe session namespace (read-only property)
set_namespace(name)NoneChanges the session namespace; with a transaction open, raises OperationalError ending Cannot change the namespace while a transaction is open.
flush()NoneCheckpoints a file-backed store; a no-op for :memory: and read-only stores
close()NoneRolls back an open transaction, then checkpoints and closes; a second call is a no-op
closedboolWhether close() ran
with relate.connect(…) as conn:ConnectionCalls close() on exit

Any call after close() raises ProgrammingError: Cannot operate on a closed connection.

Cursor

A cursor holds the whole result from the moment the statement returns; fetching moves a position over rows already in memory.

MemberReturnsBehavior
execute(query, params=None)Cursor (itself)Runs a statement and replaces the result
executemany(query, seq_of_params)Cursor (itself)Runs once per dict; holds the last result
columns, keys()list[str]Column names in RETURN order
descriptionlist[tuple] or NoneOne 7-tuple per column, (name, type_code, None, None, None, None, None); None when the statement returns no columns
rowcountintNumber of rows; -1 for a statement with no columns
statsdict[str, int]The 11 mutation counters
arraysizeintDefault 1; the row count fetchmany() returns
fetchone()Record or NoneThe next row
fetchmany(size=None), fetch(n)list[Record]Up to size rows
fetchall(), data()list[Record]Every remaining row
peek()Record or NoneThe next row without moving the position
single()RecordThe one remaining row; zero or several raise ResultNotSingleError: expected exactly one row, got <n>
iteration, len(cursor)Record; intIterates the remaining rows; len is the total row count
consume()ResultSummarySkips the remaining rows and returns the summary; repeatable. Before any statement: ProgrammingError: no statement has been executed on this cursor
graph()ResultGraphThe distinct nodes and relationships in every row, nested values included
to_df(**kwargs)pandas.DataFrameThe remaining rows; needs the relate-db[pandas] extra, else ImportError: to_df() needs pandas: pip install 'relate-db[pandas]'
to_arrow(**kwargs)pyarrow.TableThe remaining rows; needs the relate-db[arrow] extra, else ImportError: to_arrow() needs pyarrow: pip install 'relate-db[arrow]'
close()NoneA no-op

Record

A row is a relate.Record, a dict subclass. row["name"] and row.name read the same value; assigning row.name raises AttributeError. row.keys() returns the column names in RETURN order, and row.data() returns a plain dict copy.

ResultSummary

FieldTypeMeaning
countersdict[str, int]nodes_created, nodes_deleted, relationships_created, relationships_deleted, properties_set, labels_added, labels_removed, indexes_added, indexes_removed, constraints_added, constraints_removed
query_typestr"r" a read, "w" a write with no columns, "rw" a write that returns columns, "s" a schema statement; EXPLAIN and PROFILE report the wrapped statement's type
namespacestrThe namespace the statement ran in: its USE target, else the session namespace
bookmarkstr or NoneAn auto-commit write's bookmark, relate:<n>; None for a read, an EXPLAIN, a write whose counters are all zero (it changed nothing), and any statement inside an explicit transaction
elapsed_msfloatEngine time for parse, plan and run, in milliseconds
parse_ms, plan_ms, execute_msfloatThe three phases of elapsed_ms; their sum is at most elapsed_ms
notificationslist[dict]Empty on a plain run; filled by EXPLAIN and PROFILE. Keys: code, gql_status, severity, category, title, description, remedy, and operator_id on operator-scoped kinds
plandict or NoneNone on a plain run; for EXPLAIN and PROFILE, a relate.plan/v1 document with keys schema, profiled, statement and root

ResultGraph

MemberTypeMeaning
nodestuple[Node, …]Distinct nodes, ordered by id
relationshipstuple[Relationship, …]Distinct relationships, ordered by id
len(graph)intNodes plus relationships

Database

The native handle. It is thread-affine like a Connection, and runs statements in auto-commit mode until begin_transaction().

MemberReturnsBehavior
Database(path=":memory:", namespace="neo4j", *, read_only=False)DatabaseOpens a store with the open options
execute(query, params=None)QueryResultRuns one statement
execute_arrow(query, params=None)bytesThe result as one Arrow IPC stream message; decoding it needs pyarrow
begin_transaction(), commit_transaction(), rollback_transaction()None; str or None; NoneAn explicit transaction; the commit returns its bookmark
transaction_active()boolWhether a transaction is open
supports_transactions()boolTrue
namespace(), set_namespace(name)str; NoneThe session namespace (a method here, a property on Connection)
flush(), close()NoneCheckpoint; checkpoint and close
reset()NoneCheckpoints, then reopens the same path in the same namespace and mode
node_count(), relationship_count()intCounts over the whole namespace
labels(), relationship_types()list[str]Sorted names

QueryResult

FieldTypeMeaning
columnslist[str]Column names
datalist[dict]Rows
statsdict[str, int]The mutation counters
column_typeslist[str]One type name per column, taken from the first row: STRING, INTEGER, FLOAT, BOOLEAN, NODE, RELATIONSHIP, PATH, LIST, MAP, POINT, DATETIME, DATE, TIME, DURATION, VECTOR, UUID, BINARY or ANY (a null first value, or no rows)
query_type, namespace, bookmarkstr; str; str or NoneAs on ResultSummary
elapsed_ms, parse_ms, plan_ms, execute_msfloatAs on ResultSummary
notifications, planlist[dict]; dict or NoneAs on ResultSummary

Value classes

ClassMembers
Nodeid (int), element_id (str, as elementId()), labels (list[str]), properties (dict), node["key"], "key" in node, items()
Relationshipid, element_id, type, start_node, end_node (aliases start, end), start_node_element_id, end_node_element_id, properties, rel["key"], items()
Pathnodes (list[Node]), relationships (list[Relationship]); len(path) is the relationship count
Point(srid, x, y, z=None)srid, x, y, z, crs (cartesian, cartesian-3d, wgs-84, wgs-84-3d), and the aliases longitude, latitude, height
Duration(months=0, days=0, seconds=0, nanoseconds=0)months, days, seconds, nanoseconds; str() is the ISO 8601 form (P1M, PT1.000000005S); == follows Cypher, so PT36H and P1DT12H differ; to_timedelta() raises ValueError when the duration has months, sub-microsecond nanoseconds, or lies outside timedelta's range
Vectorcoordinate_type (INTEGER8, INTEGER16, INTEGER32, INTEGER, FLOAT16, BFLOAT16, FLOAT32 or FLOAT), dimension, values, len(), ==, hash(); no constructor
Temporaltext (the ISO 8601 form, every digit), kind (datetime, localdatetime, time or localtime), nanosecond, zone_name, offset_seconds, to_native() (rounded down to microseconds, zone reduced to its offset); a result type only

element_id is None in the system namespace, where elementId() fails.

Value mapping

params is a dict with str keys at every level, referenced as $name. Every parameter a statement names must be present; None is a value.

Python parameterCypher value
None, bool, int, float, strNULL, BOOLEAN, INTEGER (64-bit), FLOAT, STRING
datetime.dateDATE
A naive or an aware datetime.datetimeLOCAL DATETIME or ZONED DATETIME
datetime.timeLOCAL TIME, or ZONED TIME when it has a tzinfo
datetime.timedeltaDURATION, normalized: timedelta(hours=36) binds as P1DT12H
relate.DurationDURATION, each component as given
relate.PointPOINT
relate.Vector from a resultVECTOR, unchanged
uuid.UUIDSTRING, lowercase with hyphens
bytes, bytearrayLIST<INTEGER> of byte values 0 to 255
list, tupleLIST
dictMAP, also when its keys look like a point's
Cypher resultPython value
NULL, BOOLEAN, INTEGER, FLOAT, STRINGNone, bool, int, float, str
DATEdatetime.date
LOCAL TIME, ZONED TIMEdatetime.time, aware when the value has an offset
LOCAL DATETIMEnaive datetime.datetime
ZONED DATETIME with an offsetaware datetime.datetime
Any temporal with nanoseconds, or a datetime with a named zonerelate.Temporal
DURATIONrelate.Duration, at every depth
POINTrelate.Point
VECTORrelate.Vector, at every depth
NODE, RELATIONSHIP, PATHrelate.Node, relate.Relationship, relate.Path
LIST, MAPlist, dict in the engine's key order

Cursor.description maps the engine's column type to a PEP 249 type object:

Engine column typetype_code
STRINGrelate.STRING
BINARYrelate.BINARY
INTEGER, FLOAT, BOOLEANrelate.NUMBER
DATETIME, DATE, TIMErelate.DATETIME
every other type, and ANYNone

relate.viz

An optional module that turns a result into a neo4j_viz.VisualizationGraph. It needs the neo4j-viz package, which no relate-db extra installs; import relate does not load it.

FunctionReturnsBehavior
to_visualization_graph(result, node_caption=None)neo4j_viz.VisualizationGraphresult is a Cursor, a QueryResult or an iterable of dicts. Every node, relationship and path in the rows, nested values included, becomes part of the graph. node_caption maps a label to the property that captions its nodes; without one, a node's caption is the first of name, title or display_name it has, else its labels. Raises ImportError without neo4j-viz, and ValueError when the rows hold no node, relationship, or path
render_html(vg, **kwargs)IPython.display.HTMLStandalone HTML whose data block has an id unique to each call, so two renders on one page each show their own graph; kwargs pass to vg.render()

Errors

Every engine error and every parameter-type or range error raises a subclass of relate.Error, in the PEP 249 tree; a cyclic parameter, or one nested more than 128 levels deep, raises Python's own ValueError, which except relate.Error does not catch. The engine's classification is copied onto the exception as attributes; an error the client raises itself carries None in the four engine attributes.

ClassParentRaised for
Error (alias RelateError)ExceptionThe base of every relate database error
InterfaceErrorErrorNative module communication
DatabaseErrorErrorAn engine error no rule maps
DataErrorDatabaseErrorA bad type or value, every capacity refusal, an out-of-range int parameter
OperationalErrorDatabaseErrorOpening, locking and I/O; namespace errors; writes on a read-only store; transaction state
IntegrityErrorDatabaseErrorConstraint violations; deleting a connected node without DETACH
InternalErrorDatabaseErrorAn engine invariant the caller cannot fix
ProgrammingErrorDatabaseErrorSyntax errors, missing parameters, parameter-shape mistakes, closed connections, wrong-thread use
NotSupportedErrorDatabaseErrorUnsupported administration commands
ResultNotSingleErrorProgrammingErrorcursor.single() with zero or several rows
AttributeMeaning
kindThe engine's finest error kind, such as ParameterMissing or WrongThread; None only on an error the client raised itself
tck_classThe openCypher error class
neo4j_codeThe Neo4j status code, such as Neo.ClientError.Statement.SyntaxError
gql_statusThe GQLSTATUS code, such as 42N02
stage"compile", "runtime" or "internal"
retryableTrue only when retrying the transaction can succeed, such as after a write-write conflict
Message the client raisesClass
query parameters must be a dict mapping string keys to values, got '<type>'ProgrammingError
query parameter keys must be strings, got a key of type '<type>'ProgrammingError
map keys must be strings, got a key of type '<type>'ProgrammingError (a nested dict value)
Relate cannot convert a parameter of Python type '<type>' to a graph valueProgrammingError
integer parameter is out of range for a 64-bit Relate integerDataError
timedelta parameter field '<field>' is out of range for a 64-bit Relate durationDataError (a timedelta subclass that overrides days, seconds or microseconds; a standard timedelta stays in range)
parameter contains a cycle: a list or map references itselfValueError, not a relate.Error
parameter nesting exceeds the maximum depth of 128ValueError, not a relate.Error
Cannot operate on a closed connectionProgrammingError
expected exactly one row, got <n>ResultNotSingleError

The errors reference lists the engine messages behind each class.

Threading

A Connection or Database belongs to the thread that opened it. A statement or other call from another thread raises ProgrammingError with kind WrongThread, and the connection stays usable on its own thread. Connection.close() from another thread also raises WrongThread, but it marks the Connection closed: every later call, from any thread, raises ProgrammingError: Cannot operate on a closed connection. Dropping the last reference, on any thread, checkpoints a writable store. The engine releases the GIL while a statement runs.

Limits

LimitValueWhen exceeded
Integer parameterA signed 64-bit integerDataError: integer parameter is out of range for a 64-bit Relate integer
Parameter nesting128 levelsValueError: parameter nesting exceeds the maximum depth of 128
Statement text4194304 bytesProgrammingError: syntax error: query text of <n> bytes exceeds the maximum query size of 4194304 bytes
One value a statement buildsThe engine's expression memory capDataError with kind MemoryLimitExceeded: This statement tried to build a single value of <size>. …
Writing opens of a file-backed storeOne, across every processOperationalError: storage locked: <path>
Threads per connectionThe opening threadProgrammingError with kind WrongThread

Capabilities

FeaturePython
In-memory storeA store that lives only in the process.yesrelate.connect(":memory:")
Directory storeA store in a directory on disk.yesrelate.connect(path)
Single-file .rl storeA store in one .rl file.yesrelate.connect("graph.rl")
Read-only openAn open that loads the store once and refuses every write.yesread_only=True
Explicit transactionsSeveral statements committed or rolled back together.yesautocommit = False, then commit() or rollback()
Parameters$name values passed beside the statement.yesexecute(statement, {"x": 3})
Column types, counters and timingsMetadata returned with each result.yesQueryResult (column_types, stats, query_type, elapsed_ms and its phase split) and the result summary
NotificationsThe engine's query notifications (for example a Cartesian product), each with a remedy.yesQueryResult.notifications
Arrow outputResults as Apache Arrow.yescursor.to_arrow() (the relate-db[arrow] extra), Database.execute_arrow()
Change data captureReading committed changes from a slot.nono change-data-capture call in the client
Full-text searchFull-text indexes and their queries.yesCREATE FULLTEXT INDEX and full-text SEARCH
LOAD CSV from local filesLocal LOAD CSV under a configured directory (denied unless one is set).yesRELATE_LOAD_CSV_DIR; connect takes no CSV option
Load a store from a URLOpening a .rl store fetched over https.nono open from a URL in the client
Agent modeThe CLI's agent defaults: the 100-row cap and the piped toon output.noA CLI setting

Each cell is yes, no, partial or unknown, with the call, flag or reason. Checked on 2026-11-09 against relate 0.1.2: runs of the CLI binary and relate serve (Bolt, HTTP and MCP), the WebAssembly build this site ships, and the Python, Node.js, C, Java, Go, Swift and Rust clients, and each surface's upstream reference. The C, Java, Go and Swift clients load the same C ABI library as the Node.js client.

Example

The native layer reports a type name per column, from the first row, and counts without a statement:

import relate

TEAM = """
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace)
"""

db = relate.Database(":memory:")
db.execute(TEAM)
result = db.execute(
    "MATCH (p:Person {name: 'Grace'})-[w:WORKS_ON]->(proj) "
    "RETURN p.name AS name, w.since AS since, proj AS project ORDER BY since LIMIT 1"
)
print(result.columns, result.column_types, result.query_type)
print(db.node_count(), db.relationship_count(), db.labels(), db.relationship_types())
db.close()
['name', 'since', 'project'] ['STRING', 'INTEGER', 'NODE'] r
5 5 ['Person', 'Project'] ['KNOWS', 'MAINTAINS', 'WORKS_ON']

A node An entity in the graph: it carries zero or more labels and a map of properties, and relationships connect it to other nodes. Read more in the glossary arrives as a relate.Node, a relationship A directed, typed connection between two nodes, with properties of its own; every relationship has exactly one type and a start and an end node. Read more in the glossary as a relate.Relationship, and a path An alternating sequence of nodes and relationships that a pattern matched, bound with p = … and read with nodes(p), relationships(p) and length(p). Read more in the glossary as a relate.Path holding both:

import relate

TEAM = """
CREATE (ada:Person {name: 'Ada', role: 'engineer'}),
       (grace:Person {name: 'Grace', role: 'engineer'}),
       (noor:Person {name: 'Noor', role: 'maintainer'}),
       (relate:Project {name: 'relate', language: 'Rust'}),
       (tck:Project {name: 'opencypher-tck', language: 'Gherkin'}),
       (ada)-[:WORKS_ON {since: 2024}]->(relate),
       (grace)-[:WORKS_ON {since: 2023}]->(relate),
       (grace)-[:WORKS_ON {since: 2022}]->(tck),
       (noor)-[:MAINTAINS]->(relate),
       (ada)-[:KNOWS]->(grace)
"""

with relate.connect(":memory:") as conn:
    conn.execute(TEAM)
    path = conn.execute("MATCH p = (:Person {name: 'Ada'})-[:KNOWS]->(:Person) RETURN p").single()["p"]
    ada, grace = path.nodes
    knows = path.relationships[0]
    print(len(path), ada["name"], ada.element_id, ada.labels)
    print(knows.type, knows.element_id, knows.start_node_element_id, knows.end_node_element_id, grace["name"])
1 Ada 4:relate-neo4j:n0 ['Person']
KNOWS 5:relate-neo4j:r4 4:relate-neo4j:n0 4:relate-neo4j:n1 Grace

See also