from ..helpers import quote_string, random_string, stringify_param_value from .commands import GraphCommands from .edge import Edge # noqa from .node import Node # noqa from .path import Path # noqa class Graph(GraphCommands): """ Graph, collection of nodes and edges. """ def __init__(self, client, name=random_string()): """ Create a new graph. """ 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 = [None] * len(lbls) for i, l in enumerate(lbls): self._labels[i] = l[0] def _refresh_relations(self): rels = self.relationship_types() # Unpack data. self._relationship_types = [None] * len(rels) for i, r in enumerate(rels): self._relationship_types[i] = r[0] def _refresh_attributes(self): props = self.property_keys() # Unpack data. self._properties = [None] * len(props) for i, p in enumerate(props): self._properties[i] = p[0] 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: propertie = self._properties[idx] except IndexError: # Refresh properties. self._refresh_attributes() propertie = self._properties[idx] return propertie 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 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: 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.relationshipTypes", read_only=True).result_set def property_keys(self): return self.call_procedure("db.propertyKeys", read_only=True).result_set