You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
7.1 KiB
263 lines
7.1 KiB
import warnings
|
|
|
|
from ..helpers import quote_string, random_string, stringify_param_value
|
|
from .commands import AsyncGraphCommands, GraphCommands
|
|
from .edge import Edge # noqa
|
|
from .node import Node # noqa
|
|
from .path import Path # noqa
|
|
|
|
DB_LABELS = "DB.LABELS"
|
|
DB_RAELATIONSHIPTYPES = "DB.RELATIONSHIPTYPES"
|
|
DB_PROPERTYKEYS = "DB.PROPERTYKEYS"
|
|
|
|
|
|
class Graph(GraphCommands):
|
|
"""
|
|
Graph, collection of nodes and edges.
|
|
"""
|
|
|
|
def __init__(self, client, name=random_string()):
|
|
"""
|
|
Create a new graph.
|
|
"""
|
|
warnings.warn(
|
|
DeprecationWarning(
|
|
"RedisGraph support is deprecated as of Redis Stack 7.2 \
|
|
(https://redis.com/blog/redisgraph-eol/)"
|
|
)
|
|
)
|
|
self.NAME = name # Graph key
|
|
self.client = client
|
|
self.execute_command = client.execute_command
|
|
|
|
self.nodes = {}
|
|
self.edges = []
|
|
self._labels = [] # List of node labels.
|
|
self._properties = [] # List of properties.
|
|
self._relationship_types = [] # List of relation types.
|
|
self.version = 0 # Graph version
|
|
|
|
@property
|
|
def name(self):
|
|
return self.NAME
|
|
|
|
def _clear_schema(self):
|
|
self._labels = []
|
|
self._properties = []
|
|
self._relationship_types = []
|
|
|
|
def _refresh_schema(self):
|
|
self._clear_schema()
|
|
self._refresh_labels()
|
|
self._refresh_relations()
|
|
self._refresh_attributes()
|
|
|
|
def _refresh_labels(self):
|
|
lbls = self.labels()
|
|
|
|
# Unpack data.
|
|
self._labels = [l[0] for _, l in enumerate(lbls)]
|
|
|
|
def _refresh_relations(self):
|
|
rels = self.relationship_types()
|
|
|
|
# Unpack data.
|
|
self._relationship_types = [r[0] for _, r in enumerate(rels)]
|
|
|
|
def _refresh_attributes(self):
|
|
props = self.property_keys()
|
|
|
|
# Unpack data.
|
|
self._properties = [p[0] for _, p in enumerate(props)]
|
|
|
|
def get_label(self, idx):
|
|
"""
|
|
Returns a label by it's index
|
|
|
|
Args:
|
|
|
|
idx:
|
|
The index of the label
|
|
"""
|
|
try:
|
|
label = self._labels[idx]
|
|
except IndexError:
|
|
# Refresh labels.
|
|
self._refresh_labels()
|
|
label = self._labels[idx]
|
|
return label
|
|
|
|
def get_relation(self, idx):
|
|
"""
|
|
Returns a relationship type by it's index
|
|
|
|
Args:
|
|
|
|
idx:
|
|
The index of the relation
|
|
"""
|
|
try:
|
|
relationship_type = self._relationship_types[idx]
|
|
except IndexError:
|
|
# Refresh relationship types.
|
|
self._refresh_relations()
|
|
relationship_type = self._relationship_types[idx]
|
|
return relationship_type
|
|
|
|
def get_property(self, idx):
|
|
"""
|
|
Returns a property by it's index
|
|
|
|
Args:
|
|
|
|
idx:
|
|
The index of the property
|
|
"""
|
|
try:
|
|
p = self._properties[idx]
|
|
except IndexError:
|
|
# Refresh properties.
|
|
self._refresh_attributes()
|
|
p = self._properties[idx]
|
|
return p
|
|
|
|
def add_node(self, node):
|
|
"""
|
|
Adds a node to the graph.
|
|
"""
|
|
if node.alias is None:
|
|
node.alias = random_string()
|
|
self.nodes[node.alias] = node
|
|
|
|
def add_edge(self, edge):
|
|
"""
|
|
Adds an edge to the graph.
|
|
"""
|
|
if not (self.nodes[edge.src_node.alias] and self.nodes[edge.dest_node.alias]):
|
|
raise AssertionError("Both edge's end must be in the graph")
|
|
|
|
self.edges.append(edge)
|
|
|
|
def _build_params_header(self, params):
|
|
if params is None:
|
|
return ""
|
|
if not isinstance(params, dict):
|
|
raise TypeError("'params' must be a dict")
|
|
# Header starts with "CYPHER"
|
|
params_header = "CYPHER "
|
|
for key, value in params.items():
|
|
params_header += str(key) + "=" + stringify_param_value(value) + " "
|
|
return params_header
|
|
|
|
# Procedures.
|
|
def call_procedure(self, procedure, *args, read_only=False, **kwagrs):
|
|
args = [quote_string(arg) for arg in args]
|
|
q = f"CALL {procedure}({','.join(args)})"
|
|
|
|
y = kwagrs.get("y", None)
|
|
if y is not None:
|
|
q += f"YIELD {','.join(y)}"
|
|
|
|
return self.query(q, read_only=read_only)
|
|
|
|
def labels(self):
|
|
return self.call_procedure(DB_LABELS, read_only=True).result_set
|
|
|
|
def relationship_types(self):
|
|
return self.call_procedure(DB_RAELATIONSHIPTYPES, read_only=True).result_set
|
|
|
|
def property_keys(self):
|
|
return self.call_procedure(DB_PROPERTYKEYS, read_only=True).result_set
|
|
|
|
|
|
class AsyncGraph(Graph, AsyncGraphCommands):
|
|
"""Async version for Graph"""
|
|
|
|
async def _refresh_labels(self):
|
|
lbls = await self.labels()
|
|
|
|
# Unpack data.
|
|
self._labels = [l[0] for _, l in enumerate(lbls)]
|
|
|
|
async def _refresh_attributes(self):
|
|
props = await self.property_keys()
|
|
|
|
# Unpack data.
|
|
self._properties = [p[0] for _, p in enumerate(props)]
|
|
|
|
async def _refresh_relations(self):
|
|
rels = await self.relationship_types()
|
|
|
|
# Unpack data.
|
|
self._relationship_types = [r[0] for _, r in enumerate(rels)]
|
|
|
|
async def get_label(self, idx):
|
|
"""
|
|
Returns a label by it's index
|
|
|
|
Args:
|
|
|
|
idx:
|
|
The index of the label
|
|
"""
|
|
try:
|
|
label = self._labels[idx]
|
|
except IndexError:
|
|
# Refresh labels.
|
|
await self._refresh_labels()
|
|
label = self._labels[idx]
|
|
return label
|
|
|
|
async def get_property(self, idx):
|
|
"""
|
|
Returns a property by it's index
|
|
|
|
Args:
|
|
|
|
idx:
|
|
The index of the property
|
|
"""
|
|
try:
|
|
p = self._properties[idx]
|
|
except IndexError:
|
|
# Refresh properties.
|
|
await self._refresh_attributes()
|
|
p = self._properties[idx]
|
|
return p
|
|
|
|
async def get_relation(self, idx):
|
|
"""
|
|
Returns a relationship type by it's index
|
|
|
|
Args:
|
|
|
|
idx:
|
|
The index of the relation
|
|
"""
|
|
try:
|
|
relationship_type = self._relationship_types[idx]
|
|
except IndexError:
|
|
# Refresh relationship types.
|
|
await self._refresh_relations()
|
|
relationship_type = self._relationship_types[idx]
|
|
return relationship_type
|
|
|
|
async def call_procedure(self, procedure, *args, read_only=False, **kwagrs):
|
|
args = [quote_string(arg) for arg in args]
|
|
q = f"CALL {procedure}({','.join(args)})"
|
|
|
|
y = kwagrs.get("y", None)
|
|
if y is not None:
|
|
f"YIELD {','.join(y)}"
|
|
return await self.query(q, read_only=read_only)
|
|
|
|
async def labels(self):
|
|
return ((await self.call_procedure(DB_LABELS, read_only=True))).result_set
|
|
|
|
async def property_keys(self):
|
|
return (await self.call_procedure(DB_PROPERTYKEYS, read_only=True)).result_set
|
|
|
|
async def relationship_types(self):
|
|
return (
|
|
await self.call_procedure(DB_RAELATIONSHIPTYPES, read_only=True)
|
|
).result_set
|