Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

### **1.1.0** (June 30, 2026)

### Features

- Support for `WHERE` clause arithmetic with mathematical operators `+`, `-`, `*`, `/`, and `%` (#98, thanks @q-rosiebloxsom!)
- Support for pickling/unpickling of result objects while preserving their semantic `str` subclasses (#99, thanks @q-rosiebloxsom!)

### **1.0.1** (September 23, 2025)

### Features
Expand Down
65 changes: 36 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<h1 align=center>GrandCypher</h1>
<h1 align=center>Grand-Cypher</h1>
<div align=center><img src="https://img.shields.io/pypi/v/grand-cypher?style=for-the-badge" /> <img alt="GitHub Workflow Status (branch)" src="https://img.shields.io/github/actions/workflow/status/aplbrain/grand-cypher/python-package.yml?branch=master&style=for-the-badge"></div>

```shell
pip install grand-cypher
# Note: You will want a version of grandiso>=2.2.0 for best performance!
# pip install -U 'grandiso>=2.2.0'
```

GrandCypher is a partial (and growing!) implementation of the Cypher graph query language written in Python, for Python data structures.
Grand-Cypher is a partial (and growing!) implementation of the Cypher graph query language written in Python, for Python data structures.

You likely already know Cypher from the Neo4j Graph Database. Use it with your favorite graph libraries in Python!

Expand Down Expand Up @@ -62,36 +60,45 @@ RETURN

# Feature Parity

| Feature | Support |
| ----------------------------------------------------------- | ------------------------- |
| Multiple `MATCH` clauses | ✅ |
| `WHERE`-clause filtering on nodes | ✅ |
| Anonymous `-[]-` edges | ✅ |
| `LIMIT` | ✅ |
| `SKIP` | ✅ |
| Node/edge attributes with `{}` syntax | ✅ |
| `WHERE`-clause filtering on edges | ✅ |
| Named `-[]-` edges | ✅ |
| Chained `()-[]->()-[]->()` edges | ✅ Thanks @khoale88! |
| Backwards `()<-[]-()` edges | ✅ Thanks @khoale88! |
| Anonymous `()` nodes | ✅ Thanks @khoale88! |
| Undirected `()-[]-()` edges | ✅ Thanks @khoale88! |
| Boolean Arithmetic (`AND`/`OR`) | ✅ Thanks @khoale88! |
| `(:Type)` node-labels | ✅ Thanks @khoale88! |
| `[:Type]` edge-labels | ✅ Thanks @khoale88! |
| `DISTINCT` | ✅ Thanks @jackboyla! |
| `ORDER BY` | ✅ Thanks @jackboyla! |
| `IN` | ✅ Thanks @davidmezzetti! |
| Aggregation functions (`COUNT`, `SUM`, `MIN`, `MAX`, `AVG`) | ✅ Thanks @jackboyla! |
| Aliasing of returned entities (`return X as Y`) | ✅ Thanks @jackboyla! |
| Negated edges (`where not (a)-->(b)`) | 🥺 |
| `OPTIONAL MATCH` | 🥺 |
| Graph mutations (e.g. `DELETE`, `SET`,...) | 🥺 |
| Feature | Support |
| ----------------------------------------------------------- | -------------------------- |
| Multiple `MATCH` clauses | ✅ |
| `WHERE`-clause filtering on nodes | ✅ |
| Anonymous `-[]-` edges | ✅ |
| `LIMIT` | ✅ |
| `SKIP` | ✅ |
| Node/edge attributes with `{}` syntax | ✅ |
| `WHERE`-clause filtering on edges | ✅ |
| Named `-[]-` edges | ✅ |
| Chained `()-[]->()-[]->()` edges | ✅ Thanks @khoale88! |
| Backwards `()<-[]-()` edges | ✅ Thanks @khoale88! |
| Anonymous `()` nodes | ✅ Thanks @khoale88! |
| Undirected `()-[]-()` edges | ✅ Thanks @khoale88! |
| Boolean Arithmetic (`AND`/`OR`) | ✅ Thanks @khoale88! |
| `(:Type)` node-labels | ✅ Thanks @khoale88! |
| `[:Type]` edge-labels | ✅ Thanks @khoale88! |
| `DISTINCT` | ✅ Thanks @jackboyla! |
| `ORDER BY` | ✅ Thanks @jackboyla! |
| `IN` | ✅ Thanks @davidmezzetti! |
| Aggregation functions (`COUNT`, `SUM`, `MIN`, `MAX`, `AVG`) | ✅ Thanks @jackboyla! |
| Aliasing of returned entities (`return X as Y`) | ✅ Thanks @jackboyla! |
| `WHERE` clause arithmetic support for math / numbers | ✅ Thanks @q-rosiebloxsom! |
| Negated edges (`where not (a)-->(b)`) | 🥺 |
| `OPTIONAL MATCH` | 🥺 |
| Graph mutations (e.g. `DELETE`, `SET`,...) | 🥺 |

| | | | |
| -------------- | -------------- | ----------------- | ---------------- |
| ✅ = Supported | 🛣 = On Roadmap | 🥺 = Help Welcome | 🔴 = Not Planned |

Plus several quality-of-life features for Python users, such as:

| Feature | Support |
| ----------------------------------------------------------------------- | -------------------------------------------- |
| Interoperability with NetworkX, Grand, and other Python graph libraries | ✅ |
| Support for node and edge Labels | ✅ ([[Read More]](docs/Label-Convention.md)) |
| Pickleable results | ✅ Thanks @q-rosiebloxsom! |

## Citing

If this tool is helpful to your research, please consider citing it with:
Expand Down
28 changes: 28 additions & 0 deletions docs/Label-Convention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# The `__labels__` magic attribute

In NetworkX and Grand graph objects, there is no canonical way to "type" a vertex or edge. In Cypher, this is done with labels. For example, a node may have the label `:Person` or `:Book`, and an edge may have the label `[:AUTHORED]`.

By convention, this repository uses the `__labels__` attribute on a vertex to store the labels of a node. This is an iterable of strings. The `__labels__` attribute is not required, but if it is present, it can be used in queries:

```python
from grandcypher import GrandCypher
import networkx as nx

G = nx.Graph()
G.add_node(1, name="Douglas Adams", __labels__=["Person"])
G.add_node(2, name="The Hitchhiker's Guide to the Galaxy", __labels__=["Book"])
G.add_edge(1, 2, __labels__=["AUTHORED"])

GrandCypher(G).run("""
MATCH (a:Person)-[e]->(b:Book)
RETURN a.name, b.name
""")
```

## Data IO

The [`grand-cypher-io`](https://github.com/aplbrain/grand-cypher-io) repository provides a set of tools for reading and writing graphs in OpenCypher format.

The `__labels__` attribute on nodes and edges will be automatically used to populate the "labels" attribute of the node for the purposes of writing to an OpenCypher file.

Likewise, the `__labels__` attribute is used to populate the labels attribute of a node when _reading_ from an OpenCypher file.
Loading