Skip to content

Commit 7f0d32f

Browse files
authored
Merge pull request #529 from graphistry/dev/gfql-serialization
Dev/gfql serialization
2 parents 3fd294a + 87cb5c7 commit 7f0d32f

26 files changed

+678
-101
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
## [Development]
99

10+
## [0.32.0 - 2023-12-22]
11+
12+
### Added
13+
14+
* GFQL `Chain` AST object
15+
* GFQL query serialization - `Chain`, `ASTObject`, and `ASTPredict` implement `ASTSerializable`
16+
- Ex:`Chain.from_json(Chain([n(), e(), n()]).to_json())`
17+
* GFQL predicate `is_year_end`
18+
19+
### Docs
20+
21+
* GFQL in readme.md
22+
23+
### Changes
24+
25+
* Refactor `ASTEdge`, `ASTNode` field naming convention to match other `ASTSerializable`s
26+
27+
### Breaking 🔥
28+
29+
* GFQL `e()` now aliases `e_undirected` instead of the base class `ASTEdge`
30+
1031
## [0.31.1 - 2023-12-05]
1132

1233
### Docs

README.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ It is easy to turn arbitrary data into insightful graphs. PyGraphistry comes wit
147147
g2.plot()
148148
```
149149

150-
* Cypher-style graph pattern mining queries on dataframes ([ipynb demo](demos/more_examples/graphistry_features/hop_and_chain_graph_pattern_mining.ipynb))
150+
* GFQL: Cypher-style graph pattern mining queries on dataframes ([ipynb demo](demos/more_examples/graphistry_features/hop_and_chain_graph_pattern_mining.ipynb))
151151

152-
Run Cypher-style graph queries natively on dataframes without going to a database or Java:
152+
Run Cypher-style graph queries natively on dataframes without going to a database or Java with GFQL:
153153

154154
```python
155155
from graphistry import n, e_undirected, is_in
@@ -1133,7 +1133,7 @@ g2.plot() # nodes are values from cols s, d, k1
11331133
destination_node_match={"k2": 2},
11341134
destination_node_query='k2 == 2 or k2 == 4',
11351135
)
1136-
.chain([ # filter to subgraph
1136+
.chain([ # filter to subgraph with Cypher-style GFQL
11371137
n(),
11381138
n({'k2': 0, "m": 'ok'}), #specific values
11391139
n({'type': is_in(["type1", "type2"])}), #multiple valid values
@@ -1156,7 +1156,7 @@ g2.plot() # nodes are values from cols s, d, k1
11561156
.collapse(node='some_id', column='some_col', attribute='some val')
11571157
```
11581158

1159-
Both `hop()` and `chain()` match dictionary expressions support dataframe series *predicates*. The above examples show `is_in([x, y, z, ...])`. Additional predicates include:
1159+
Both `hop()` and `chain()` (GFQL) match dictionary expressions support dataframe series *predicates*. The above examples show `is_in([x, y, z, ...])`. Additional predicates include:
11601160

11611161
* categorical: is_in, duplicated
11621162
* temporal: is_month_start, is_month_end, is_quarter_start, is_quarter_end, is_year_start, is_year_end
@@ -1233,7 +1233,7 @@ assert 'pagerank' in g2._nodes.columns
12331233

12341234
#### Graph pattern matching
12351235

1236-
PyGraphistry supports a PyData-native variant of the popular Cypher graph query language, meaning you can do graph pattern matching directly from Pandas dataframes without installing a database or Java
1236+
PyGraphistry supports GFQL, its PyData-native variant of the popular Cypher graph query language, meaning you can do graph pattern matching directly from Pandas dataframes without installing a database or Java
12371237

12381238
See also [graph pattern matching tutorial](demos/more_examples/graphistry_features/hop_and_chain_graph_pattern_mining.ipynb)
12391239

@@ -1316,6 +1316,17 @@ print('# end edges: ', len(g3._edges[ g3._edges.final_edge ]))
13161316

13171317
See table above for more predicates like `is_in()` and `gt()`
13181318

1319+
Queries can be serialized and deserialized, such as for saving and remote execution:
1320+
1321+
```python
1322+
from graphistry.compute.chain import Chain
1323+
1324+
pattern = Chain([n(), e(), n()])
1325+
pattern_json = pattern.to_json()
1326+
pattern2 = Chain.from_json(pattern_json)
1327+
g.chain(pattern2).plot()
1328+
```
1329+
13191330
#### Pipelining
13201331

13211332
```python

docs/source/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
('py:class', '3'),
4949
('py:class', "<class 'dict'>"),
5050
('py:class', "<class 'str'>"),
51+
('py:class', "graphistry.compute.ASTSerializable.ASTSerializable"),
52+
('py:class', "graphistry.compute.chain.Chain"),
5153
('py:class', "graphistry.compute.predicates.ASTPredicate.ASTPredicate"),
5254
('py:class', 'graphistry.compute.predicates.categorical.Duplicated'),
5355
('py:class', 'graphistry.compute.predicates.is_in.IsIn'),
@@ -60,6 +62,7 @@
6062
('py:class', 'graphistry.compute.predicates.numeric.LT'),
6163
('py:class', 'graphistry.compute.predicates.numeric.NE'),
6264
('py:class', 'graphistry.compute.predicates.numeric.NotNA'),
65+
('py:class', 'graphistry.compute.predicates.numeric.NumericASTPredicate'),
6366
('py:class', 'graphistry.compute.predicates.str.Contains'),
6467
('py:class', 'graphistry.compute.predicates.str.Endswith'),
6568
('py:class', 'graphistry.compute.predicates.str.IsAlnum'),
@@ -81,6 +84,7 @@
8184
('py:class', 'graphistry.compute.predicates.temporal.IsQuarterEnd'),
8285
('py:class', 'graphistry.compute.predicates.temporal.IsQuarterStart'),
8386
('py:class', 'graphistry.compute.predicates.temporal.IsYearStart'),
87+
('py:class', 'graphistry.compute.predicates.temporal.IsYearEnd'),
8488
('py:class', 'graphistry.Engine.Engine'),
8589
('py:class', 'graphistry.gremlin.CosmosMixin'),
8690
('py:class', 'graphistry.gremlin.GremlinMixin'),

graphistry/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
from graphistry.compute import (
5353
n, e_forward, e_reverse, e_undirected,
54+
Chain,
5455

5556
is_in, IsIn,
5657

@@ -61,6 +62,7 @@
6162
is_quarter_start, IsQuarterStart,
6263
is_quarter_end, IsQuarterEnd,
6364
is_year_start, IsYearStart,
65+
is_year_end, IsYearEnd,
6466
is_leap_year, IsLeapYear,
6567

6668
gt, GT,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from abc import ABC, abstractmethod
2+
from typing import Dict
3+
import pandas as pd
4+
5+
from graphistry.utils.json import JSONVal, serialize_to_json_val
6+
7+
8+
class ASTSerializable(ABC):
9+
"""
10+
Internal, not intended for use outside of this module.
11+
Class name becomes o['type'], and all non reserved_fields become JSON-typed key
12+
"""
13+
14+
reserved_fields = ['type']
15+
16+
def validate(self) -> None:
17+
pass
18+
19+
def to_json(self, validate=True) -> Dict[str, JSONVal]:
20+
"""
21+
Returns JSON-compatible dictionry {"type": "ClassName", "arg1": val1, ...}
22+
Emits all non-reserved instance fields
23+
"""
24+
if validate:
25+
self.validate()
26+
data: Dict[str, JSONVal] = {'type': self.__class__.__name__}
27+
for key, value in self.__dict__.items():
28+
if key not in self.reserved_fields:
29+
data[key] = serialize_to_json_val(value)
30+
return data
31+
32+
@classmethod
33+
def from_json(cls, d: Dict[str, JSONVal]) -> 'ASTSerializable':
34+
"""
35+
Given c.to_json(), hydrate back c
36+
37+
Corresponding c.__class__.__init__ must accept all non-reserved instance fields
38+
"""
39+
constructor_args = {k: v for k, v in d.items() if k not in cls.reserved_fields}
40+
return cls(**constructor_args)

graphistry/compute/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .ast import (
33
n, e_forward, e_reverse, e_undirected
44
)
5+
from .chain import Chain
56
from .predicates.is_in import (
67
is_in, IsIn
78
)
@@ -14,6 +15,7 @@
1415
is_quarter_start, IsQuarterStart,
1516
is_quarter_end, IsQuarterEnd,
1617
is_year_start, IsYearStart,
18+
is_year_end, IsYearEnd,
1719
is_leap_year, IsLeapYear
1820
)
1921
from .predicates.numeric import (

0 commit comments

Comments
 (0)