Metadata-Version: 2.4
Name: velr
Version: 0.1.63
Summary: Python bindings for Velr, an embedded openCypher graph database built on SQLite
License-Expression: MIT AND LicenseRef-Velr-Runtime-Binary-Redistribution-License
Project-URL: Homepage, https://velr.ai
Keywords: graph,database,cypher,opencypher,sqlite,embedded,arrow,pandas,polars
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Rust
Classifier: Topic :: Database
Classifier: Topic :: Database :: Database Engines/Servers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSE.runtime
Requires-Dist: cffi>=1.15
Provides-Extra: pandas
Requires-Dist: pandas>=2; extra == "pandas"
Requires-Dist: pyarrow>=15; extra == "pandas"
Provides-Extra: polars
Requires-Dist: polars>=0.20; extra == "polars"
Requires-Dist: pyarrow>=15; extra == "polars"
Provides-Extra: nx
Requires-Dist: networkx>=3.2; extra == "nx"
Provides-Extra: all
Requires-Dist: pandas>=2; extra == "all"
Requires-Dist: polars>=0.20; extra == "all"
Requires-Dist: pyarrow>=15; extra == "all"
Requires-Dist: networkx>=3.2; extra == "all"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: pytest-cov>=4; extra == "test"
Requires-Dist: networkx>=3.2; extra == "test"
Requires-Dist: pandas>=2; extra == "test"
Requires-Dist: polars>=0.20; extra == "test"
Requires-Dist: pyarrow>=15; extra == "test"
Dynamic: license-file

# Velr

Velr is an embedded property-graph database from Velr.ai, written in Rust, built on top of SQLite, and queried using the openCypher language.

It persists to a standard SQLite database file, runs in-process, and is designed for local, embedded, and edge use cases.

Vector data and time-series support are actively in development and will ship after openCypher support has stabilized.

This package provides the **Python bindings** for Velr. It wraps a bundled native runtime with a C ABI, implemented in Rust, and exposes a small, Pythonic API for executing Cypher queries, streaming result tables, working with transactions, and exporting results to Arrow, pandas, and Polars.

**Questions, feedback, or commercial licensing:** tomas@velr.ai

---

## Release status

This release is **alpha**.

- The Python API and query support are still evolving.
- openCypher coverage is already substantial, but some features are still missing.

If you hit a missing feature, please reach out — that helps us prioritize.

Velr is already usable for real workflows and representative use cases, but rough edges remain and the API is not yet stable.

---

## Installation

Install from PyPI:

```bash
pip install velr
```

For Arrow / dataframe workflows, install the optional Python dependencies you want to use:

```bash
pip install pyarrow pandas polars
```

### Licensing in simple terms

* The **Python binding source code** in this package is licensed under **MIT**.
* The **bundled native runtime binaries** may be **used and freely redistributed in unmodified form** under the terms of **`LICENSE.runtime`**.

---

## Quick start

```python
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())
```

Open a file-backed database instead of an in-memory database:

```python
from velr.driver import Velr

with Velr.open("mygraph.db") as db:
    db.run("CREATE (:Person {name:'Alice'})")
```

---

## Query model

A query may produce zero or more result tables.

Velr exposes three main ways to run Cypher:

* `run()` executes a query or script and drains all result tables.
* `exec()` returns a stream of result tables.
* `exec_one()` expects exactly one result table.

### `run()`

Use `run()` when you only care about side effects:

```python
with Velr.open(None) as db:
    db.run("CREATE (:Movie {title:'Interstellar', released:2014})")
```

### `exec_one()`

Use `exec_one()` when the query should yield exactly one table:

```python
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]))
```

### `exec()`

Use `exec()` when a query or script may produce multiple result tables:

```python
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]))
```

---

## Table lifetime and ownership

Table lifetime depends on how a table was obtained.

### Tables from `exec()`

Tables pulled from `exec()` are **stream-scoped**.

They remain valid while the producing stream remains open, and closing the stream closes any still-open tables produced by that stream.

```python
with db.exec("MATCH (n) RETURN n") as stream:
    table = stream.next_table()
    # table is valid here

# stream is now closed, so any still-open table from it is also closed
```

### Tables from `exec_one()`

Tables returned by `exec_one()` are **parent-scoped**, not stream-scoped.

* `Velr.exec_one()` returns a table parented to the connection.
* `VelrTx.exec_one()` returns a table parented to the transaction.

That means the returned table remains usable after the internal stream logic used by `exec_one()` has finished.

Even so, tables should still be closed when no longer needed, ideally by using them as context managers.

---

## Rows and cells

Rows are exposed through `Rows`. Each yielded row is a tuple of `Cell` objects.

`Cell.as_python()` converts values to normal Python objects:

* `NULL` → `None`
* `BOOL` → `bool`
* `INT64` → `int`
* `DOUBLE` → `float`
* `TEXT` → `str` by default
* `JSON` → `str` by default, or parsed Python objects with `parse_json=True`

Example:

```python
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())
```

For convenience and safety, TEXT and JSON payloads are copied into Python `bytes` as rows are read, so row contents remain valid after the next fetch.

---

## Transactions and savepoints

Use `begin_tx()` to open a transaction:

```python
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()
```

If a transaction context exits without `commit()`, it is rolled back.

After `commit()` or `rollback()`, a transaction can no longer be used.

### Savepoints

Velr supports two savepoint styles:

* `savepoint()` creates a scoped, handle-owned savepoint.
* `savepoint_named(name)` creates a transaction-owned named savepoint.

Scoped savepoints are owned by the Python handle:

* dropping the handle closes the savepoint
* `release()` releases it
* `rollback()` rolls back to it and releases it

Named savepoints are owned by the transaction:

* dropping the returned Python handle does not remove the named savepoint
* `rollback_to(name)` rolls back to that named savepoint, discards any newer named savepoints, and keeps the target named savepoint active
* `release_savepoint(name)` releases a named savepoint by name; the named savepoint must be the most recent active named savepoint
* `release()` or `rollback()` on a named savepoint handle consume that named savepoint

Active named savepoints are released automatically during `commit()` so that surviving changes are preserved in the committed transaction.

Example:

```python
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:'a'})")

        tx.savepoint_named("sp2")
        tx.run("CREATE (:Temp {k:'b'})")

        tx.rollback_to("sp1")  # undoes a and b, drops sp2, keeps sp1 active
        tx.run("CREATE (:Temp {k:'c'})")

        tx.release_savepoint("sp1")
        tx.commit()
```
---

## pandas / Polars / PyArrow interop

Velr can export result tables as Arrow IPC and convert them into:

* `pyarrow.Table`
* `pandas.DataFrame`
* `polars.DataFrame`

### pandas

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

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

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

```python
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 Arrow, pandas, Polars, NumPy, and records

Velr can also 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

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

```python
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 support

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()`.

```python
with Velr.open(None) as db:
    with db.explain("MATCH (p:Person) RETURN p.name AS name") as xp:
        print(xp.to_compact_string())
```

---

## Query language support

Velr supports **most of openCypher**, but some features are not yet implemented.

Notable missing features:

* `REMOVE` clause
* 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()`

---

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

---

## Platform support

The Python package wraps a bundled native runtime implemented in Rust.

Supported distributions may include prebuilt binary wheels for common platforms. Where binary wheels are available, the compiled runtime is included with the package.

Currently bundled targets:

    * macOS (arm64)
    * Linux x86_64
    * Linux aarch64
    * Windows x86_64

---

## License

See [`LICENSE`](LICENSE) and [`LICENSE.runtime`](LICENSE.runtime) for the full license texts.

