Python Driver¶
A lightweight, in-process Python API for querying, mutating, and inspecting the Velr graph database.
Velr currently requires Python 3.12 or newer.
The Python driver provides:
- Connection management via
Velr.open - Executing Cypher queries via
run,exec, andexec_one - Transactional control via
begin_txandVelrTx - Savepoints via
savepoint,savepoint_named, androllback_to - Streaming result tables via
Stream,Table,Rows, andCell - Converting results to PyArrow, pandas, and Polars
- Binding external data via
bind_arrow,bind_pandas,bind_polars,bind_numpy, andbind_records - EXPLAIN / EXPLAIN ANALYZE via
explainandexplain_analyze
Thread safety¶
Velr connections and active result handles are not safe for concurrent use from multiple threads.
If you need parallelism:
- open one connection per thread
- do not share active connections
- do not share transactions, streams, tables, row iterators, or explain traces across threads
Opening a Database¶
Open a file-backed database instead of an in-memory database:
from velr.driver import Velr
with Velr.open("mygraph.db") as db:
db.run("CREATE (:Person {name:'Alice'})")
Path semantics:
None→ in-memory database"path"→ file-backed database at that path
Executing Queries¶
Queries may produce zero or more result tables.
run: Execute and discard results¶
Use this when you want to execute a query and do not need to consume any returned rows.
from velr.driver import Velr
with Velr.open(None) as db:
db.run("CREATE (:Movie {title:'Interstellar', released:2014})")
exec: Stream result tables¶
Use this when a query or script may produce one or more tables.
from velr.driver import Velr
MOVIES_CREATE = r"""
CREATE
(keanu:Person:Actor {name:'Keanu Reeves', born:1964}),
(nolan:Person:Director {name:'Christopher Nolan'}),
(matrix:Movie {title:'The Matrix', released:1999, genres:['Sci-Fi','Action']}),
(inception:Movie {title:'Inception', released:2010, genres:['Sci-Fi','Heist']}),
(keanu)-[:ACTED_IN {roles:['Neo']}]->(matrix),
(nolan)-[:DIRECTED]->(inception);
"""
with Velr.open(None) as db:
db.run(MOVIES_CREATE)
with db.exec(
"MATCH (m:Movie {title:'The Matrix'}) RETURN m.title AS title; "
"MATCH (m:Movie {title:'Inception'}) RETURN m.released AS released"
) as stream:
for table in stream.iter_tables():
print(table.column_names())
print(table.collect(lambda row: [cell.as_python() for cell in row]))
exec_one: Expect exactly one result table¶
Use this when the query is expected to produce exactly one table.
from velr.driver import Velr
with Velr.open(None) as db:
db.run("CREATE (:Person {name:'Alice', age:30})")
with db.exec_one("MATCH (p:Person) RETURN p.name AS name, p.age AS age") as table:
print(table.column_names())
print(table.collect(lambda row: [cell.as_python() for cell in row]))
Reading Results¶
A Table provides:
column_names()column_count()rows()collect(...)to_pyarrow()to_pandas()to_polars()
Example:
from velr.driver import Velr
MOVIES_CREATE = r"""
CREATE
(keanu:Person:Actor {name:'Keanu Reeves', born:1964}),
(nolan:Person:Director {name:'Christopher Nolan'}),
(matrix:Movie {title:'The Matrix', released:1999, genres:['Sci-Fi','Action']}),
(inception:Movie {title:'Inception', released:2010, genres:['Sci-Fi','Heist']}),
(keanu)-[:ACTED_IN {roles:['Neo']}]->(matrix),
(nolan)-[:DIRECTED]->(inception);
"""
with Velr.open(None) as db:
db.run(MOVIES_CREATE)
with db.exec_one(
"MATCH (m:Movie {title:'Inception'}) "
"RETURN m.title AS title, m.released AS year, m.genres AS genres"
) as table:
print(table.column_names())
with table.rows() as rows:
row = next(rows)
title, year, genres = row
print(title.as_python())
print(year.as_python())
print(genres.as_python())
Cell¶
Rows are yielded as tuples of Cell objects.
Use Cell.as_python() to convert values to normal Python objects.
Supported conversions include:
NULL→NoneBOOL→boolINT64→intDOUBLE→floatTEXT→strby defaultJSON→strby default, or parsed Python objects withparse_json=True
Example:
with db.exec_one("MATCH (p:Person) RETURN p.name AS name, p.age AS age") as table:
with table.rows() as rows:
for row in rows:
print(row[0].as_python(), row[1].as_python())
Table lifetime¶
Table lifetime depends on how a table was obtained.
Tables from exec()¶
Tables produced by exec() remain valid while the stream is open.
Once the stream is closed, any still-open tables produced by it are also closed.
Tables from exec_one()¶
Tables returned by exec_one() are parented to the connection or transaction rather than to a stream.
That means they remain usable after exec_one() returns.
Even so, tables should still be closed when no longer needed, ideally by using them as context managers.
Transactions¶
Velr supports explicit transactions via VelrTx.
Opening a transaction¶
from velr.driver import Velr
with Velr.open(None) as db:
with db.begin_tx() as tx:
tx.run("CREATE (:Movie {title:'Interstellar', released:2014})")
tx.commit()
Running queries inside a transaction¶
from velr.driver import Velr
with Velr.open(None) as db:
with db.begin_tx() as tx:
tx.run("CREATE (:Movie {title:'Interstellar', released:2014})")
with tx.exec_one("MATCH (m:Movie) RETURN count(m) AS c") as table:
with table.rows() as rows:
for (count_cell,) in rows:
print("Count:", count_cell.as_python())
tx.commit()
Commit / rollback¶
If a transaction context exits without commit(), it is rolled back.
After commit() or rollback(), a transaction can no longer be used.
Savepoints¶
Savepoints let you roll back part of a transaction.
Scoped savepoint¶
with Velr.open(None) as db:
with db.begin_tx() as tx:
tx.run("CREATE (:Temp {k:'outer'})")
with tx.savepoint() as sp:
tx.run("CREATE (:Temp {k:'inner'})")
sp.rollback()
tx.commit()
Named savepoint¶
with Velr.open(None) as db:
with db.begin_tx() as tx:
with tx.savepoint_named("sp1") as sp:
tx.run("CREATE (:Temp {k:'inner'})")
sp.rollback() # rollback to savepoint, then release it
tx.commit()
Roll back to a named savepoint¶
with Velr.open(None) as db:
with db.begin_tx() as tx:
tx.run("CREATE (:Temp {k:'outer'})")
tx.savepoint_named("sp1")
tx.run("CREATE (:Temp {k:'inner'})")
tx.rollback_to("sp1")
tx.commit()
Converting Results to PyArrow, pandas, and Polars¶
Velr can export result tables as Arrow IPC and convert them into:
pyarrow.Tablepandas.DataFramepolars.DataFrame
pandas¶
with Velr.open(None) as db:
db.run(MOVIES_CREATE)
df = db.to_pandas(
"MATCH (m:Movie) "
"RETURN m.title AS title, m.released AS released "
"ORDER BY released"
)
print(df)
Polars¶
with Velr.open(None) as db:
db.run(MOVIES_CREATE)
df = db.to_polars(
"MATCH (m:Movie) "
"RETURN m.title AS title, m.released AS released "
"ORDER BY released"
)
print(df)
PyArrow¶
with Velr.open(None) as db:
db.run(MOVIES_CREATE)
tbl = db.to_pyarrow(
"MATCH (m:Movie) "
"RETURN m.title AS title, m.released AS released "
"ORDER BY released"
)
print(tbl)
Export from an existing table¶
with db.exec_one("MATCH (m:Movie) RETURN m.title AS title") as table:
pa_tbl = table.to_pyarrow()
df = table.to_pandas()
pl_df = table.to_polars()
Binding External Data with BIND(...)¶
Velr can bind external columnar data under a logical name and query it from Cypher.
Supported bind helpers include:
bind_arrow()bind_pandas()bind_polars()bind_numpy()bind_records()
Bind a pandas DataFrame¶
import pandas as pd
from velr.driver import Velr
df = pd.DataFrame(
[
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 41},
]
)
with Velr.open(None) as db:
db.bind_pandas("_people", df)
db.run("""
UNWIND BIND('_people') AS r
CREATE (:Person {name:r.name, age:r.age})
""")
out = db.to_pandas("MATCH (p:Person) RETURN p.name AS name, p.age AS age ORDER BY age")
print(out)
Bind a list of dicts¶
rows = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 41},
]
with Velr.open(None) as db:
db.bind_records("_people", rows)
db.run("""
UNWIND BIND('_people') AS r
CREATE (:Person {name:r.name, age:r.age})
""")
EXPLAIN and EXPLAIN ANALYZE¶
The Python driver exposes explain traces on both connections and transactions.
Basic EXPLAIN¶
with Velr.open(None) as db:
with db.explain("MATCH (p:Person) RETURN p.name AS name") as xp:
print(xp.to_compact_string())
Use explain() for planning output and explain_analyze() to include execution details.
Explain APIs¶
Velr exposes explain traces through:
Velr.explain()Velr.explain_analyze()VelrTx.explain()VelrTx.explain_analyze()
These return an ExplainTrace, which can be navigated incrementally or fully materialized with snapshot().
Query language support¶
Velr supports most of openCypher, but some features are not yet implemented.
Notable missing features:
REMOVEclause- Query parameters (for example
$name) - The query planner does not yet use indexes in all cases where expected.
Supported functions¶
Velr currently supports these openCypher functions:
Scalars¶
id()type()length()nodes()relationships()coalesce()labels()properties()keys()
Aggregates¶
count()sum()avg()min()max()collect()
API Summary¶
Velr¶
open(path: str | None) -> Velrrun(query) -> Noneexec(query) -> Streamexec_one(query) -> Tablebegin_tx() -> VelrTxto_pyarrow(query) -> pyarrow.Tableto_pandas(query) -> pandas.DataFrameto_polars(query) -> polars.DataFramebind_arrow(logical, columns) -> Nonebind_pandas(logical, df, ...) -> Nonebind_polars(logical, df, ...) -> Nonebind_numpy(logical, data, ...) -> Nonebind_records(logical, rows, ...) -> Noneexplain(query) -> ExplainTraceexplain_analyze(query) -> ExplainTrace
VelrTx¶
run(query) -> Noneexec(query) -> StreamTxexec_one(query) -> Tablecommit() -> Nonerollback() -> Nonesavepoint() -> Savepointsavepoint_named(name) -> Savepointrollback_to(name) -> Nonebind_arrow(logical, columns) -> Nonebind_pandas(logical, df, ...) -> Nonebind_polars(logical, df, ...) -> Nonebind_numpy(logical, data, ...) -> Nonebind_records(logical, rows, ...) -> Noneexplain(query) -> ExplainTraceexplain_analyze(query) -> ExplainTrace
Savepoint¶
release() -> Nonerollback() -> None
Result types¶
StreamStreamTxTableRowsCellExplainTrace