From 152fe623b249691319ab720520078a9f54ffbe77 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 19:25:16 -0400 Subject: [PATCH 01/26] smart graph support | initial commit --- nx_arangodb/classes/digraph.py | 14 ++ nx_arangodb/classes/graph.py | 245 ++++++++++++++++++------------ nx_arangodb/classes/multigraph.py | 2 + 3 files changed, 161 insertions(+), 100 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index 9477c60..e9d5f6a 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -194,6 +194,7 @@ def __init__( and not self._loaded_incoming_graph_data ): nx.convert.to_networkx_graph(incoming_graph_data, create_using=self) + self._loaded_incoming_graph_data = True ####################### # nx.DiGraph Overides # @@ -241,12 +242,25 @@ def add_node_override(self, node_for_adding, **attr): # attr_dict.update(attr) # New: + + node_attr_dict = self.node_attr_dict_factory() + + if self.is_smart: + if self.smart_field not in attr: + m = f"Node {node_for_adding} missing smart field '{self.smart_field}'" # noqa: E501 + raise KeyError(m) + + node_attr_dict.data[self.smart_field] = attr[self.smart_field] + self._node[node_for_adding] = self.node_attr_dict_factory() self._node[node_for_adding].update(attr) # Reason: # Invoking `update` on the `attr_dict` without `attr_dict.node_id` being set # i.e trying to update a node's attributes before we know _which_ node it is + # Furthermore, support for ArangoDB Smart Graphs requires the smart field + # to be set before adding the node to the graph. This is because the smart + # field is used to generate the node's key. ########################### diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 9cadb06..96136df 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -3,7 +3,8 @@ from typing import Any, Callable, ClassVar import networkx as nx -from adbnx_adapter import ADBNX_Adapter +from adbnx_adapter import ADBNX_Adapter, ADBNX_Controller +from adbnx_adapter.typings import NxData, NxId from arango import ArangoClient from arango.cursor import Cursor from arango.database import StandardDatabase @@ -186,19 +187,18 @@ def __init__( write_async: bool = True, symmetrize_edges: bool = False, use_arango_views: bool = False, + overwrite_graph: bool = False, *args: Any, **kwargs: Any, ): self.__db = None - self.__name = None self.__use_arango_views = use_arango_views self.__graph_exists_in_db = False self.__set_db(db) - if self.__db is not None: - self.__set_graph_name(name) - - self.__set_edge_collections_attributes(edge_collections_attributes) + if all([self.__db, name]): + self.__set_graph(name, default_node_type, edge_type_func) + self.__set_edge_collections_attributes(edge_collections_attributes) # NOTE: Need to revisit these... # self.maintain_node_dict_cache = False @@ -219,96 +219,25 @@ def __init__( # raise ValueError(m) self._loaded_incoming_graph_data = False - - if self.__graph_exists_in_db: - if incoming_graph_data is not None: - m = "Cannot pass both **incoming_graph_data** and **name** yet if the already graph exists" # noqa: E501 - raise NotImplementedError(m) - - if edge_type_func is not None: - m = "Cannot pass **edge_type_func** if the graph already exists" - raise NotImplementedError(m) - - self.adb_graph = self.db.graph(self.__name) - vertex_collections = self.adb_graph.vertex_collections() - edge_definitions = self.adb_graph.edge_definitions() - - if default_node_type is None: - default_node_type = list(vertex_collections)[0] - logger.info(f"Default node type set to '{default_node_type}'") - elif default_node_type not in vertex_collections: - m = f"Default node type '{default_node_type}' not found in graph '{name}'" # noqa: E501 - raise InvalidDefaultNodeType(m) - - node_types_to_edge_type_map: dict[tuple[str, str], str] = {} - for e_d in edge_definitions: - for f in e_d["from_vertex_collections"]: - for t in e_d["to_vertex_collections"]: - if (f, t) in node_types_to_edge_type_map: - # TODO: Should we log a warning at least? - continue - - node_types_to_edge_type_map[(f, t)] = e_d["edge_collection"] - - def edge_type_func(u: str, v: str) -> str: - try: - return node_types_to_edge_type_map[(u, v)] - except KeyError: - m = f"Edge type ambiguity between '{u}' and '{v}'" - raise EdgeTypeAmbiguity(m) - - self.edge_type_func = edge_type_func - self.default_node_type = default_node_type - + if self.graph_exists_in_db: self._set_factory_methods() self.__set_arangodb_backend_config(read_parallelism, read_batch_size) - elif self.__name: + if overwrite_graph: + logger.info("Truncating graph collections...") - prefix = f"{name}_" if name else "" - if default_node_type is None: - default_node_type = f"{prefix}node" - if edge_type_func is None: - edge_type_func = lambda u, v: f"{u}_to_{v}" # noqa: E731 + for col in self.adb_graph.vertex_collections(): + self.db.collection(col).truncate() - self.edge_type_func = edge_type_func - self.default_node_type = default_node_type - - # TODO: Parameterize the edge definitions - # How can we work with a heterogenous **incoming_graph_data**? - default_edge_type = edge_type_func(default_node_type, default_node_type) - edge_definitions = [ - { - "edge_collection": default_edge_type, - "from_vertex_collections": [default_node_type], - "to_vertex_collections": [default_node_type], - } - ] + for col in self.adb_graph.edge_definitions(): + self.db.collection(col["edge_collection"]).truncate() if isinstance(incoming_graph_data, nx.Graph): - self.adb_graph = ADBNX_Adapter(self.db).networkx_to_arangodb( - self.__name, - incoming_graph_data, - edge_definitions=edge_definitions, - batch_size=write_batch_size, - use_async=write_async, - ) - + self._load_nx_graph(incoming_graph_data, write_batch_size, write_async) self._loaded_incoming_graph_data = True - else: - self.adb_graph = self.db.create_graph( - self.__name, - edge_definitions=edge_definitions, - ) - - self._set_factory_methods() - self.__set_arangodb_backend_config(read_parallelism, read_batch_size) - logger.info(f"Graph '{name}' created.") - self.__graph_exists_in_db = True - - if self.__name is not None: - kwargs["name"] = self.__name + if name is not None: + kwargs["name"] = name super().__init__(*args, **kwargs) @@ -333,6 +262,7 @@ def edge_type_func(u: str, v: str) -> str: and not self._loaded_incoming_graph_data ): nx.convert.to_networkx_graph(incoming_graph_data, create_using=self) + self._loaded_incoming_graph_data = True ####################### # Init helper methods # @@ -423,23 +353,118 @@ def __set_db(self, db: Any = None) -> None: self._db_name, self._username, self._password, verify=True ) - def __set_graph_name(self, name: Any = None) -> None: - if self.__db is None: - m = "Cannot set graph name without setting the database first" - raise DatabaseNotSet(m) - - if not name: - self.__graph_exists_in_db = False - logger.warning(f"**name** not set for {self.__class__.__name__}") - return - + def __set_graph( + self, + name: Any, + default_node_type: str | None = None, + edge_type_func: Callable[[str, str], str] | None = None, + ) -> None: if not isinstance(name, str): raise TypeError("**name** must be a string") + if self.db.has_graph(name): + logger.info(f"Graph '{name}' exists.") + + if edge_type_func is not None: + m = "Cannot pass **edge_type_func** if the graph already exists" + raise NotImplementedError(m) + + self.adb_graph = self.db.graph(name) + vertex_collections = self.adb_graph.vertex_collections() + edge_definitions = self.adb_graph.edge_definitions() + + if default_node_type is None: + default_node_type = list(vertex_collections)[0] + logger.info(f"Default node type set to '{default_node_type}'") + + elif default_node_type not in vertex_collections: + m = f"Default node type '{default_node_type}' not found in graph '{name}'" # noqa: E501 + raise InvalidDefaultNodeType(m) + + node_types_to_edge_type_map: dict[tuple[str, str], str] = {} + for e_d in edge_definitions: + for f in e_d["from_vertex_collections"]: + for t in e_d["to_vertex_collections"]: + if (f, t) in node_types_to_edge_type_map: + # TODO: Should we log a warning at least? + continue + + node_types_to_edge_type_map[(f, t)] = e_d["edge_collection"] + + def edge_type_func(u: str, v: str) -> str: + try: + return node_types_to_edge_type_map[(u, v)] + except KeyError: + m = f"Edge type ambiguity between '{u}' and '{v}'" + raise EdgeTypeAmbiguity(m) + + else: + prefix = f"{name}_" if name else "" + + if default_node_type is None: + default_node_type = f"{prefix}node" + + if edge_type_func is None: + edge_type_func = lambda u, v: f"{u}_to_{v}" # noqa: E731 + + # TODO: Parameterize the edge definitions + # How can we work with a heterogenous **incoming_graph_data**? + default_edge_type = edge_type_func(default_node_type, default_node_type) + edge_definitions = [ + { + "edge_collection": default_edge_type, + "from_vertex_collections": [default_node_type], + "to_vertex_collections": [default_node_type], + } + ] + + # Create a general graph if it doesn't exist + self.adb_graph = self.db.create_graph( + name=name, + edge_definitions=edge_definitions, + ) + + logger.info(f"Graph '{name}' created.") + self.__name = name - self.__graph_exists_in_db = self.db.has_graph(name) + self.__graph_exists_in_db = True + self.edge_type_func = edge_type_func + self.default_node_type = default_node_type + + properties = self.adb_graph.properties() + self.__is_smart: bool = properties.get("smart", False) + self.__smart_field: str | None = properties.get("smart_field") + + def _load_nx_graph( + self, nx_graph: nx.Graph, write_batch_size: int, write_async: bool + ) -> None: + controller = ADBNX_Controller + + if all([self.is_smart, self.smart_field]): + smart_field = self.__smart_field - logger.info(f"Graph '{name}' exists: {self.__graph_exists_in_db}") + class SmartController(ADBNX_Controller): + def _keyify_networkx_node( + self, i: int, nx_node_id: NxId, nx_node: NxData, col: str + ) -> str: + if smart_field not in nx_node: + m = f"Node {nx_node_id} missing smart field '{smart_field}'" # noqa: E501 + raise KeyError(m) + + return f"{nx_node[smart_field]}:{str(i)}" + + def _prepare_networkx_edge(self, nx_edge: NxData, col: str) -> None: + del nx_edge["_key"] + + controller = SmartController + logger.info(f"Using smart field '{smart_field}' for node keys") + + ADBNX_Adapter(self.db, controller()).networkx_to_arangodb( + self.adb_graph.name, + nx_graph, + batch_size=write_batch_size, + use_async=write_async, + ) ########### # Getters # @@ -479,6 +504,14 @@ def graph_exists_in_db(self) -> bool: def edge_attributes(self) -> set[str]: return self._edge_collections_attributes + @property + def is_smart(self) -> bool: + return self.__is_smart + + @property + def smart_field(self) -> str | None: + return self.__smart_field + ########### # Setters # ########### @@ -645,12 +678,24 @@ def add_node_override(self, node_for_adding, **attr): # attr_dict.update(attr) # New: - self._node[node_for_adding] = self.node_attr_dict_factory() + node_attr_dict = self.node_attr_dict_factory() + + if self.is_smart: + if self.smart_field not in attr: + m = f"Node {node_for_adding} missing smart field '{self.smart_field}'" # noqa: E501 + raise KeyError(m) + + node_attr_dict.data[self.smart_field] = attr[self.smart_field] + + self._node[node_for_adding] = node_attr_dict self._node[node_for_adding].update(attr) # Reason: # Invoking `update` on the `attr_dict` without `attr_dict.node_id` being set # i.e trying to update a node's attributes before we know _which_ node it is + # Furthermore, support for ArangoDB Smart Graphs requires the smart field + # to be set before adding the node to the graph. This is because the smart + # field is used to generate the node's key. ########################### diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 07c30b7..1100025 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -215,6 +215,8 @@ def __init__( else: nx.convert.to_networkx_graph(incoming_graph_data, create_using=self) + self._loaded_incoming_graph_data = True + ####################### # Init helper methods # ####################### From cd0ea6d7f4745d20bd8eac91c86e359144b33f4f Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 20:16:56 -0400 Subject: [PATCH 02/26] cleanup: `_create_node_attr_dict` --- nx_arangodb/classes/dict/node.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/nx_arangodb/classes/dict/node.py b/nx_arangodb/classes/dict/node.py index 0ef179d..dd04b51 100644 --- a/nx_arangodb/classes/dict/node.py +++ b/nx_arangodb/classes/dict/node.py @@ -221,7 +221,8 @@ def update(self, attrs: Any) -> None: if not attrs: return - self.data.update(build_node_attr_dict_data(self, attrs)) + node_attr_dict_data = build_node_attr_dict_data(self, attrs) + self.data.update(node_attr_dict_data) if not self.node_id: logger.debug("Node ID not set, skipping NodeAttrDict(?).update()") @@ -275,10 +276,12 @@ def __init__( self.FETCHED_ALL_DATA = False self.FETCHED_ALL_IDS = False - def _create_node_attr_dict(self, vertex: dict[str, Any]) -> NodeAttrDict: + def _create_node_attr_dict( + self, node_id: str, node_data: dict[str, Any] + ) -> NodeAttrDict: node_attr_dict = self.node_attr_dict_factory() - node_attr_dict.node_id = vertex["_id"] - node_attr_dict.data = build_node_attr_dict_data(node_attr_dict, vertex) + node_attr_dict.node_id = node_id + node_attr_dict.data = build_node_attr_dict_data(node_attr_dict, node_data) return node_attr_dict @@ -322,7 +325,7 @@ def __getitem__(self, key: str) -> NodeAttrDict: raise KeyError(key) if vertex_db := vertex_get(self.graph, node_id): - node_attr_dict = self._create_node_attr_dict(vertex_db) + node_attr_dict = self._create_node_attr_dict(vertex_db["_id"], vertex_db) self.data[node_id] = node_attr_dict return node_attr_dict @@ -342,7 +345,7 @@ def __setitem__(self, key: str, value: NodeAttrDict) -> None: result = doc_insert(self.db, node_type, node_id, value.data) - node_attr_dict = self._create_node_attr_dict(result) + node_attr_dict = self._create_node_attr_dict(result["_id"], value.data) self.data[node_id] = node_attr_dict @@ -405,10 +408,7 @@ def copy(self) -> Any: @keys_are_strings def __update_local_nodes(self, nodes: Any) -> None: for node_id, node_data in nodes.items(): - node_attr_dict = self.node_attr_dict_factory() - node_attr_dict.node_id = node_id - node_attr_dict.data = build_node_attr_dict_data(node_attr_dict, node_data) - + node_attr_dict = self._create_node_attr_dict(node_id, node_data) self.data[node_id] = node_attr_dict @keys_are_strings @@ -478,7 +478,7 @@ def _fetch_all(self): for node_id, node_data in node_dict.items(): del node_data["_rev"] # TODO: Optimize away via phenolrs - node_attr_dict = self._create_node_attr_dict(node_data) + node_attr_dict = self._create_node_attr_dict(node_data["_id"], node_data) self.data[node_id] = node_attr_dict self.FETCHED_ALL_DATA = True From da41a4a36e2962e96076115729d0edae6f259b82 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 20:17:15 -0400 Subject: [PATCH 03/26] new: `cast_to_string` --- nx_arangodb/classes/function.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/nx_arangodb/classes/function.py b/nx_arangodb/classes/function.py index 993db90..491c0cd 100644 --- a/nx_arangodb/classes/function.py +++ b/nx_arangodb/classes/function.py @@ -199,6 +199,17 @@ def to_dict(self): return cls +def cast_to_string(value: Any) -> str: + """Casts a value to a string.""" + if isinstance(value, str): + return value + + if isinstance(value, (int, float)): + return str(value) + + raise TypeError(f"{value} cannot be casted to string.") + + def key_is_string(func: Callable[..., Any]) -> Any: """Decorator to check if the key is a string. Will attempt to cast the key to a string if it is not. @@ -208,12 +219,7 @@ def wrapper(self: Any, key: Any, *args: Any, **kwargs: Any) -> Any: if key is None: raise ValueError("Key cannot be None.") - if not isinstance(key, str): - if not isinstance(key, (int, float)): - raise TypeError(f"{key} cannot be casted to string.") - - key = str(key) - + key = cast_to_string(key) return func(self, key, *args, **kwargs) return wrapper @@ -270,12 +276,7 @@ def wrapper(self: Any, data: Any, *args: Any, **kwargs: Any) -> Any: raise TypeError(f"Decorator found unsupported type: {type(data)}.") for key, value in items: - if not isinstance(key, str): - if not isinstance(key, (int, float)): - raise TypeError(f"{key} cannot be casted to string.") - - key = str(key) - + key = cast_to_string(key) data_dict[key] = value return func(self, data_dict, *args, **kwargs) @@ -655,7 +656,7 @@ def doc_insert( data: dict[str, Any] = {}, **kwargs: Any, ) -> dict[str, Any]: - """Inserts a document into a collection.""" + """Inserts a document into a collection. Returns document metadata.""" result: dict[str, Any] = db.insert_document( collection, {**data, "_id": id}, overwrite=True, **kwargs ) From 670f25c93f95c05361a43aa06d29875566eb0584 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 20:19:20 -0400 Subject: [PATCH 04/26] cleanup: overrides --- nx_arangodb/classes/digraph.py | 41 ++++++++++++----------- nx_arangodb/classes/graph.py | 59 +++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index e9d5f6a..f2d3c56 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -178,6 +178,7 @@ def __init__( if self.graph_exists_in_db: self.clear_edges = self.clear_edges_override self.add_node = self.add_node_override + # self.add_nodes_from = self.add_nodes_from_override self.remove_node = self.remove_node_override self.reverse = self.reverse_override @@ -226,9 +227,19 @@ def clear_edges_override(self): super().clear_edges() def add_node_override(self, node_for_adding, **attr): + if node_for_adding is None: + raise ValueError("None cannot be a node") + + # New: + # if self.is_smart: + # node_for_adding = self._get_smart_id(node_for_adding, attr) + + # Reason: + # Support for ArangoDB Smart Graphs requires the smart field + # to be set before adding the node to the graph. This is because + # the smart field is used to generate the node's key. + if node_for_adding not in self._succ: - if node_for_adding is None: - raise ValueError("None cannot be a node") self._succ[node_for_adding] = self.adjlist_inner_dict_factory() self._pred[node_for_adding] = self.adjlist_inner_dict_factory() @@ -242,25 +253,15 @@ def add_node_override(self, node_for_adding, **attr): # attr_dict.update(attr) # New: - node_attr_dict = self.node_attr_dict_factory() - - if self.is_smart: - if self.smart_field not in attr: - m = f"Node {node_for_adding} missing smart field '{self.smart_field}'" # noqa: E501 - raise KeyError(m) - - node_attr_dict.data[self.smart_field] = attr[self.smart_field] - - self._node[node_for_adding] = self.node_attr_dict_factory() - self._node[node_for_adding].update(attr) + node_attr_dict.data = attr + self._node[node_for_adding] = node_attr_dict # Reason: - # Invoking `update` on the `attr_dict` without `attr_dict.node_id` being set - # i.e trying to update a node's attributes before we know _which_ node it is - # Furthermore, support for ArangoDB Smart Graphs requires the smart field - # to be set before adding the node to the graph. This is because the smart - # field is used to generate the node's key. + # We can optimize the process of adding a node by creating avoiding + # the creation of a new dictionary and updating it with the attributes. + # Instead, we can create a new node_attr_dict object and set the attributes + # directly. This only makes 1 network call to the database instead of 2. ########################### @@ -269,6 +270,10 @@ def add_node_override(self, node_for_adding, **attr): nx._clear_cache(self) + # TODO: Address in separate PR + # def add_nodes_from_override(self, nodes_for_adding, **attr): + # raise NotImplementedError("Not yet implemented") + def remove_node_override(self, n): if isinstance(n, (str, int)): n = get_node_id(str(n), self.default_node_type) diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 96136df..78c54c8 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -28,7 +28,7 @@ node_attr_dict_factory, node_dict_factory, ) -from .function import get_node_id +from .function import cast_to_string, get_node_id from .reportviews import ArangoEdgeView, ArangoNodeView networkx_api = nxadb.utils.decorators.networkx_class(nx.Graph) # type: ignore @@ -247,6 +247,7 @@ def __init__( self.clear = self.clear_override self.clear_edges = self.clear_edges_override self.add_node = self.add_node_override + # self.add_nodes_from = self.add_nodes_from_override self.number_of_edges = self.number_of_edges_override self.nbunch_iter = self.nbunch_iter_override @@ -599,6 +600,24 @@ def chat( return str(response["result"]) + def _get_smart_id(self, node_for_adding: str, attr: dict[Any, Any]) -> str: + raise NotImplementedError( + "This is broken if node_for_adding is structured like an ArangoDB ID" + ) # noqa: E501 + # Should we really be doing this? Database would catch this error anyways... + + if self.smart_field not in attr: + m = f"Node {node_for_adding} missing smart field '{self.smart_field}'" # noqa: E501 + raise KeyError(m) + + # TODO: Revisit this behaviour. + # Too magical? Raise error instead? Let ArangoDB handle it? + node_for_adding = cast_to_string(node_for_adding) + if ":" not in node_for_adding: + node_for_adding = f"{attr[self.smart_field]}:{node_for_adding}" + + return node_for_adding + ##################### # nx.Graph Overides # ##################### @@ -663,10 +682,19 @@ def clear_edges_override(self): nx._clear_cache(self) def add_node_override(self, node_for_adding, **attr): - if node_for_adding not in self._node: - if node_for_adding is None: - raise ValueError("None cannot be a node") + if node_for_adding is None: + raise ValueError("None cannot be a node") + + # New: + # if self.is_smart: + # node_for_adding = self._get_smart_id(node_for_adding, attr) + # Reason: + # Support for ArangoDB Smart Graphs requires the smart field + # to be set before adding the node to the graph. This is because + # the smart field is used to generate the node's key. + + if node_for_adding not in self._node: self._adj[node_for_adding] = self.adjlist_inner_dict_factory() ###################### @@ -679,23 +707,14 @@ def add_node_override(self, node_for_adding, **attr): # New: node_attr_dict = self.node_attr_dict_factory() - - if self.is_smart: - if self.smart_field not in attr: - m = f"Node {node_for_adding} missing smart field '{self.smart_field}'" # noqa: E501 - raise KeyError(m) - - node_attr_dict.data[self.smart_field] = attr[self.smart_field] - + node_attr_dict.data = attr self._node[node_for_adding] = node_attr_dict - self._node[node_for_adding].update(attr) # Reason: - # Invoking `update` on the `attr_dict` without `attr_dict.node_id` being set - # i.e trying to update a node's attributes before we know _which_ node it is - # Furthermore, support for ArangoDB Smart Graphs requires the smart field - # to be set before adding the node to the graph. This is because the smart - # field is used to generate the node's key. + # We can optimize the process of adding a node by creating avoiding + # the creation of a new dictionary and updating it with the attributes. + # Instead, we can create a new node_attr_dict object and set the attributes + # directly. This only makes 1 network call to the database instead of 2. ########################### @@ -704,6 +723,10 @@ def add_node_override(self, node_for_adding, **attr): nx._clear_cache(self) + # TODO: Address in separate PR + # def add_nodes_from_override(self, nodes_for_adding, **attr): + # raise NotImplementedError("Not yet implemented") + def number_of_edges_override(self, u=None, v=None): if u is not None: return super().number_of_edges(u, v) From 3c74a6a2b946b0b26baa9ce1ad90b2a68c2253cb Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 20:20:07 -0400 Subject: [PATCH 05/26] fix: lint --- nx_arangodb/classes/digraph.py | 2 +- nx_arangodb/classes/graph.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index f2d3c56..0bcbabe 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -272,7 +272,7 @@ def add_node_override(self, node_for_adding, **attr): # TODO: Address in separate PR # def add_nodes_from_override(self, nodes_for_adding, **attr): - # raise NotImplementedError("Not yet implemented") + # raise NotImplementedError("Not yet implemented") def remove_node_override(self, n): if isinstance(n, (str, int)): diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 78c54c8..b90de09 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -725,7 +725,7 @@ def add_node_override(self, node_for_adding, **attr): # TODO: Address in separate PR # def add_nodes_from_override(self, nodes_for_adding, **attr): - # raise NotImplementedError("Not yet implemented") + # raise NotImplementedError("Not yet implemented") def number_of_edges_override(self, u=None, v=None): if u is not None: From 3ca0f12056720da37522425c50c5f8e61527b8ae Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 20:28:06 -0400 Subject: [PATCH 06/26] fix: `overwrite_graph` --- nx_arangodb/classes/digraph.py | 2 ++ nx_arangodb/classes/multidigraph.py | 2 ++ nx_arangodb/classes/multigraph.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index 0bcbabe..db208b3 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -154,6 +154,7 @@ def __init__( write_async: bool = True, symmetrize_edges: bool = False, use_arango_views: bool = False, + overwrite_graph: bool = False, *args: Any, **kwargs: Any, ): @@ -171,6 +172,7 @@ def __init__( write_async, symmetrize_edges, use_arango_views, + overwrite_graph, *args, **kwargs, ) diff --git a/nx_arangodb/classes/multidigraph.py b/nx_arangodb/classes/multidigraph.py index dc05e59..4a7be42 100644 --- a/nx_arangodb/classes/multidigraph.py +++ b/nx_arangodb/classes/multidigraph.py @@ -165,6 +165,7 @@ def __init__( write_async: bool = True, symmetrize_edges: bool = False, use_arango_views: bool = False, + overwrite_graph: bool = False, *args: Any, **kwargs: Any, ): @@ -183,6 +184,7 @@ def __init__( write_async, symmetrize_edges, use_arango_views, + overwrite_graph, *args, **kwargs, ) diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 1100025..84c374d 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -166,6 +166,7 @@ def __init__( write_async: bool = True, symmetrize_edges: bool = False, use_arango_views: bool = False, + overwrite_graph: bool = False, *args: Any, **kwargs: Any, ): @@ -183,6 +184,7 @@ def __init__( write_async, symmetrize_edges, use_arango_views, + overwrite_graph, *args, **kwargs, ) From 22091804d1a38a0ccac5bbfc5b7e91cf44f4d659 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 23 Sep 2024 20:28:16 -0400 Subject: [PATCH 07/26] new: `GraphNotEmpty` exception --- nx_arangodb/classes/graph.py | 14 ++++++++++++++ nx_arangodb/exceptions.py | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index b90de09..546d490 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -15,6 +15,7 @@ DatabaseNotSet, EdgeTypeAmbiguity, GraphNameNotSet, + GraphNotEmpty, InvalidDefaultNodeType, ) from nx_arangodb.logger import logger @@ -439,6 +440,19 @@ def edge_type_func(u: str, v: str) -> str: def _load_nx_graph( self, nx_graph: nx.Graph, write_batch_size: int, write_async: bool ) -> None: + v_count = sum( + self.db.collection(v).count() for v in self.adb_graph.vertex_collections() + ) + + e_count = sum( + self.db.collection(e["edge_collection"]).count() + for e in self.adb_graph.edge_definitions() + ) + + if v_count > 0 or e_count > 0: + m = f"Graph '{self.adb_graph.name}' already has data. Use **overwrite_graph=True** to clear it." # noqa: E501 + raise GraphNotEmpty(m) + controller = ADBNX_Controller if all([self.is_smart, self.smart_field]): diff --git a/nx_arangodb/exceptions.py b/nx_arangodb/exceptions.py index 35e538e..6df5be7 100644 --- a/nx_arangodb/exceptions.py +++ b/nx_arangodb/exceptions.py @@ -14,6 +14,10 @@ class GraphNameNotSet(NetworkXArangoDBException): pass +class GraphNotEmpty(NetworkXArangoDBException): + pass + + class InvalidTraversalDirection(NetworkXArangoDBException): pass From 4fafde9d97033072e95376e3925a9cd95e43069d Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:22:34 -0400 Subject: [PATCH 08/26] lock deps --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7d181d3..e3cee3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,9 +29,9 @@ classifiers = [ ] dependencies = [ "networkx>=3.0,<=3.3", - "phenolrs", - "python-arango", - "adbnx-adapter" + "phenolrs~=0.5", + "python-arango~=8", + "adbnx-adapter~=5" ] [project.optional-dependencies] From ed4df9be8015d05a86014c0758561f5a9f257c88 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:23:35 -0400 Subject: [PATCH 09/26] remove: `_get_smart_id` --- nx_arangodb/classes/digraph.py | 9 --------- nx_arangodb/classes/graph.py | 29 +---------------------------- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index db208b3..dd2f916 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -232,15 +232,6 @@ def add_node_override(self, node_for_adding, **attr): if node_for_adding is None: raise ValueError("None cannot be a node") - # New: - # if self.is_smart: - # node_for_adding = self._get_smart_id(node_for_adding, attr) - - # Reason: - # Support for ArangoDB Smart Graphs requires the smart field - # to be set before adding the node to the graph. This is because - # the smart field is used to generate the node's key. - if node_for_adding not in self._succ: self._succ[node_for_adding] = self.adjlist_inner_dict_factory() diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 546d490..715cf1a 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -29,7 +29,7 @@ node_attr_dict_factory, node_dict_factory, ) -from .function import cast_to_string, get_node_id +from .function import get_node_id from .reportviews import ArangoEdgeView, ArangoNodeView networkx_api = nxadb.utils.decorators.networkx_class(nx.Graph) # type: ignore @@ -614,24 +614,6 @@ def chat( return str(response["result"]) - def _get_smart_id(self, node_for_adding: str, attr: dict[Any, Any]) -> str: - raise NotImplementedError( - "This is broken if node_for_adding is structured like an ArangoDB ID" - ) # noqa: E501 - # Should we really be doing this? Database would catch this error anyways... - - if self.smart_field not in attr: - m = f"Node {node_for_adding} missing smart field '{self.smart_field}'" # noqa: E501 - raise KeyError(m) - - # TODO: Revisit this behaviour. - # Too magical? Raise error instead? Let ArangoDB handle it? - node_for_adding = cast_to_string(node_for_adding) - if ":" not in node_for_adding: - node_for_adding = f"{attr[self.smart_field]}:{node_for_adding}" - - return node_for_adding - ##################### # nx.Graph Overides # ##################### @@ -699,15 +681,6 @@ def add_node_override(self, node_for_adding, **attr): if node_for_adding is None: raise ValueError("None cannot be a node") - # New: - # if self.is_smart: - # node_for_adding = self._get_smart_id(node_for_adding, attr) - - # Reason: - # Support for ArangoDB Smart Graphs requires the smart field - # to be set before adding the node to the graph. This is because - # the smart field is used to generate the node's key. - if node_for_adding not in self._node: self._adj[node_for_adding] = self.adjlist_inner_dict_factory() From 315c52552788583db9947c4de8b7e75445833cc4 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:23:40 -0400 Subject: [PATCH 10/26] new: `test_load_graph_from_nxadb_as_smart_graph` --- tests/test.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/tests/test.py b/tests/test.py index 6a19143..6fe5ea5 100644 --- a/tests/test.py +++ b/tests/test.py @@ -3,7 +3,7 @@ import networkx as nx import pytest -from arango import DocumentDeleteError +from arango.exceptions import DocumentInsertError from phenolrs.networkx.typings import ( DiGraphAdjDict, GraphAdjDict, @@ -17,7 +17,7 @@ from .conftest import create_grid_graph, create_line_graph, db, run_gpu_tests -G_NX = nx.karate_club_graph() +G_NX: nx.Graph = nx.karate_club_graph() G_NX_digraph = nx.DiGraph(G_NX) G_NX_multigraph = nx.MultiGraph(G_NX) G_NX_multidigraph = nx.MultiDiGraph(G_NX) @@ -108,6 +108,73 @@ def test_load_graph_from_nxadb(): db.delete_graph(graph_name, drop_collections=True) +def test_load_graph_from_nxadb_as_smart_graph(): + graph_name = "SmartKarateGraph" + + db.delete_graph(graph_name, drop_collections=True, ignore_missing=True) + db.create_graph( + graph_name, + smart=True, + smart_field="club", + edge_definitions=[ + { + "edge_collection": "smart_person_to_smart_person", + "from_vertex_collections": ["smart_person"], + "to_vertex_collections": ["smart_person"], + } + ], + ) + + G_NX_copy = G_NX.copy() + for _, node in G_NX_copy.nodes(data=True): + node["club"] = node["club"].replace(" ", "") + + G = nxadb.Graph( + name=graph_name, + incoming_graph_data=G_NX_copy, + write_async=False, + ) + + assert db.has_graph(graph_name) + assert db.has_collection("smart_person") + assert db.has_collection("smart_person_to_smart_person") + assert db.collection("smart_person").count() == len(G_NX_copy.nodes) + assert db.collection("smart_person_to_smart_person").count() == len(G_NX_copy.edges) + + assert db.has_document("smart_person/Mr.Hi:0") + + with pytest.raises(DocumentInsertError): + G.add_node(35, club="Officer") + + with pytest.raises(DocumentInsertError): + G.add_node("35", club="Officer") + + with pytest.raises(DocumentInsertError): + G.add_node("smart_person/35", club="Officer") + + with pytest.raises(DocumentInsertError): + G.add_node("smart_person/Officer:35", club="officer") + + with pytest.raises(DocumentInsertError): + G.add_node("smart_person/Officer", club="Officer") + + assert G.nodes["Mr.Hi:0"]["club"] == "Mr.Hi" + G.add_node("Officer:35", club="Officer") + assert G.nodes["smart_person/Officer:35"]["club"] == "Officer" + + assert G["Mr.Hi:0"]["Mr.Hi:1"]["weight"] == 4 + G.add_edge("Mr.Hi:0", "Officer:35", weight=5) + assert G["Mr.Hi:0"]["Officer:35"]["weight"] == 5 + + G.add_nodes_from( + [("Officer:36", {"club": "Officer"}), ("Mr.Hi:37", {"club": "Mr.Hibl"})] + ) + assert G.has_node("Officer:36") + assert G.has_node("Mr.Hi:37") + + db.delete_graph(graph_name, drop_collections=True) + + def test_load_graph_from_nxadb_w_specific_edge_attribute(): graph_name = "KarateGraph" From 0b722cc5465686708f847ba7847e33a375d02cee Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:39:40 -0400 Subject: [PATCH 11/26] new: `add_nodes_from_override` --- nx_arangodb/classes/dict/node.py | 10 +++---- nx_arangodb/classes/digraph.py | 48 +++++++++++++++++++++++++++++--- nx_arangodb/classes/graph.py | 47 ++++++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/nx_arangodb/classes/dict/node.py b/nx_arangodb/classes/dict/node.py index dd04b51..5e4454a 100644 --- a/nx_arangodb/classes/dict/node.py +++ b/nx_arangodb/classes/dict/node.py @@ -334,18 +334,16 @@ def __getitem__(self, key: str) -> NodeAttrDict: @key_is_string def __setitem__(self, key: str, value: NodeAttrDict) -> None: - """G._node['node/1'] = {'foo': 'bar'} - - Not to be confused with: - - G.add_node('node/1', foo='bar') - """ + """G._node['node/1'] = {'foo': 'bar'}""" assert isinstance(value, NodeAttrDict) node_type, node_id = get_node_type_and_id(key, self.default_node_type) result = doc_insert(self.db, node_type, node_id, value.data) - node_attr_dict = self._create_node_attr_dict(result["_id"], value.data) + node_attr_dict = self._create_node_attr_dict( + result["_id"], {**value.data, **result} + ) self.data[node_id] = node_attr_dict diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index dd2f916..86de900 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -180,7 +180,7 @@ def __init__( if self.graph_exists_in_db: self.clear_edges = self.clear_edges_override self.add_node = self.add_node_override - # self.add_nodes_from = self.add_nodes_from_override + self.add_nodes_from = self.add_nodes_from_override self.remove_node = self.remove_node_override self.reverse = self.reverse_override @@ -263,9 +263,49 @@ def add_node_override(self, node_for_adding, **attr): nx._clear_cache(self) - # TODO: Address in separate PR - # def add_nodes_from_override(self, nodes_for_adding, **attr): - # raise NotImplementedError("Not yet implemented") + def add_nodes_from_override(self, nodes_for_adding, **attr): + for n in nodes_for_adding: + try: + newnode = n not in self._node + newdict = attr + except TypeError: + n, ndict = n + newnode = n not in self._node + newdict = attr.copy() + newdict.update(ndict) + if newnode: + if n is None: + raise ValueError("None cannot be a node") + self._succ[n] = self.adjlist_inner_dict_factory() + self._pred[n] = self.adjlist_inner_dict_factory() + + ###################### + # NOTE: monkey patch # + ###################### + + # Old: + # self._node[n] = self.node_attr_dict_factory() + # + # self._node[n].update(newdict) + + # New: + node_attr_dict = self.node_attr_dict_factory() + node_attr_dict.data = newdict + self._node[n] = node_attr_dict + + else: + + self._node[n].update(newdict) + + # Reason: + # We can optimize the process of adding a node by creating avoiding + # the creation of a new dictionary and updating it with the attributes. + # Instead, we can create a new node_attr_dict object and set the attributes + # directly. This only makes 1 network call to the database instead of 2. + + ########################### + + nx._clear_cache(self) def remove_node_override(self, n): if isinstance(n, (str, int)): diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 715cf1a..9601e9b 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -248,7 +248,7 @@ def __init__( self.clear = self.clear_override self.clear_edges = self.clear_edges_override self.add_node = self.add_node_override - # self.add_nodes_from = self.add_nodes_from_override + self.add_nodes_from = self.add_nodes_from_override self.number_of_edges = self.number_of_edges_override self.nbunch_iter = self.nbunch_iter_override @@ -710,9 +710,48 @@ def add_node_override(self, node_for_adding, **attr): nx._clear_cache(self) - # TODO: Address in separate PR - # def add_nodes_from_override(self, nodes_for_adding, **attr): - # raise NotImplementedError("Not yet implemented") + def add_nodes_from_override(self, nodes_for_adding, **attr): + for n in nodes_for_adding: + try: + newnode = n not in self._node + newdict = attr + except TypeError: + n, ndict = n + newnode = n not in self._node + newdict = attr.copy() + newdict.update(ndict) + if newnode: + if n is None: + raise ValueError("None cannot be a node") + self._adj[n] = self.adjlist_inner_dict_factory() + + ###################### + # NOTE: monkey patch # + ###################### + + # Old: + # self._node[n] = self.node_attr_dict_factory() + # + # self._node[n].update(newdict) + + # New: + node_attr_dict = self.node_attr_dict_factory() + node_attr_dict.data = newdict + self._node[n] = node_attr_dict + + else: + + self._node[n].update(newdict) + + # Reason: + # We can optimize the process of adding a node by creating avoiding + # the creation of a new dictionary and updating it with the attributes. + # Instead, we can create a new node_attr_dict object and set the attributes + # directly. This only makes 1 network call to the database instead of 2. + + ########################### + + nx._clear_cache(self) def number_of_edges_override(self, u=None, v=None): if u is not None: From c14c1fbc71c9e05f46db17d619173fb346888db3 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:39:44 -0400 Subject: [PATCH 12/26] fix: typo --- tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index 6fe5ea5..10c2504 100644 --- a/tests/test.py +++ b/tests/test.py @@ -167,7 +167,7 @@ def test_load_graph_from_nxadb_as_smart_graph(): assert G["Mr.Hi:0"]["Officer:35"]["weight"] == 5 G.add_nodes_from( - [("Officer:36", {"club": "Officer"}), ("Mr.Hi:37", {"club": "Mr.Hibl"})] + [("Officer:36", {"club": "Officer"}), ("Mr.Hi:37", {"club": "Mr.Hi"})] ) assert G.has_node("Officer:36") assert G.has_node("Mr.Hi:37") From 766fb9563d129e366ffbeb5d0720eaea39eb017b Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:40:51 -0400 Subject: [PATCH 13/26] fix: lint --- nx_arangodb/classes/digraph.py | 2 +- nx_arangodb/classes/graph.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index 86de900..cad6be4 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -300,7 +300,7 @@ def add_nodes_from_override(self, nodes_for_adding, **attr): # Reason: # We can optimize the process of adding a node by creating avoiding # the creation of a new dictionary and updating it with the attributes. - # Instead, we can create a new node_attr_dict object and set the attributes + # Instead, we create a new node_attr_dict object and set the attributes # directly. This only makes 1 network call to the database instead of 2. ########################### diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 9601e9b..b989348 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -746,7 +746,7 @@ def add_nodes_from_override(self, nodes_for_adding, **attr): # Reason: # We can optimize the process of adding a node by creating avoiding # the creation of a new dictionary and updating it with the attributes. - # Instead, we can create a new node_attr_dict object and set the attributes + # Instead, we create a new node_attr_dict object and set the attributes # directly. This only makes 1 network call to the database instead of 2. ########################### From 4d066a82a2d031a6da4349b370a9084f054e04f7 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:44:18 -0400 Subject: [PATCH 14/26] fix: pyproject --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3cee3c..3853ff2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,8 +30,8 @@ classifiers = [ dependencies = [ "networkx>=3.0,<=3.3", "phenolrs~=0.5", - "python-arango~=8", - "adbnx-adapter~=5" + "python-arango~=8.1", + "adbnx-adapter~=5.0" ] [project.optional-dependencies] From bbaeee446c1a0f0124ff1b5584ca3a55ed328e45 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:44:22 -0400 Subject: [PATCH 15/26] add comment --- tests/test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test.py b/tests/test.py index 10c2504..2201bb0 100644 --- a/tests/test.py +++ b/tests/test.py @@ -125,6 +125,8 @@ def test_load_graph_from_nxadb_as_smart_graph(): ], ) + # Small preprocessing to remove whitespaces from club names, + # as smart graphs do not allow whitespaces in smart fields G_NX_copy = G_NX.copy() for _, node in G_NX_copy.nodes(data=True): node["club"] = node["club"].replace(" ", "") From 80090236e7f0fe47329b93e2f927c43372abaaa1 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 17:45:52 -0400 Subject: [PATCH 16/26] `overwrite_graph` docstring --- nx_arangodb/classes/digraph.py | 6 ++++++ nx_arangodb/classes/graph.py | 6 ++++++ nx_arangodb/classes/multidigraph.py | 6 ++++++ nx_arangodb/classes/multigraph.py | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index cad6be4..f1b7ab6 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -125,6 +125,12 @@ class DiGraph(Graph, nx.DiGraph): whenever possible. NOTE: This feature is experimental and may not work as expected. + overwrite_graph : bool (optional, default: False) + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. + args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index b989348..28641b8 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -159,6 +159,12 @@ class Graph(nx.Graph): whenever possible. NOTE: This feature is experimental and may not work as expected. + overwrite_graph : bool (optional, default: False) + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. + args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/multidigraph.py b/nx_arangodb/classes/multidigraph.py index 4a7be42..451b501 100644 --- a/nx_arangodb/classes/multidigraph.py +++ b/nx_arangodb/classes/multidigraph.py @@ -135,6 +135,12 @@ class MultiDiGraph(MultiGraph, DiGraph, nx.MultiDiGraph): whenever possible. NOTE: This feature is experimental and may not work as expected. + overwrite_graph : bool (optional, default: False) + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. + args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 84c374d..0ce983e 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -136,6 +136,12 @@ class MultiGraph(Graph, nx.MultiGraph): whenever possible. NOTE: This feature is experimental and may not work as expected. + overwrite_graph : bool (optional, default: False) + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. + args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. From 481ce4bb850bbca535f1b0250aba76f770085713 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 24 Sep 2024 18:18:16 -0400 Subject: [PATCH 17/26] update `adbnx-adapter` version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3853ff2..b804121 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "networkx>=3.0,<=3.3", "phenolrs~=0.5", "python-arango~=8.1", - "adbnx-adapter~=5.0" + "adbnx-adapter~=5.0.5" ] [project.optional-dependencies] From 8ab094b95819033ae4d7507dede43ef784a20e67 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Thu, 26 Sep 2024 17:33:27 -0400 Subject: [PATCH 18/26] fix: var name --- nx_arangodb/classes/dict/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nx_arangodb/classes/dict/node.py b/nx_arangodb/classes/dict/node.py index 5e4454a..872b158 100644 --- a/nx_arangodb/classes/dict/node.py +++ b/nx_arangodb/classes/dict/node.py @@ -324,8 +324,8 @@ def __getitem__(self, key: str) -> NodeAttrDict: if node_id not in self.data and self.FETCHED_ALL_IDS: raise KeyError(key) - if vertex_db := vertex_get(self.graph, node_id): - node_attr_dict = self._create_node_attr_dict(vertex_db["_id"], vertex_db) + if node := vertex_get(self.graph, node_id): + node_attr_dict = self._create_node_attr_dict(node["_id"], node) self.data[node_id] = node_attr_dict return node_attr_dict From 753b6327b3412363e647da1fd99c6a58b9077c22 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Thu, 26 Sep 2024 17:33:41 -0400 Subject: [PATCH 19/26] fix: `GraphNotEmpty` logic --- nx_arangodb/classes/graph.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 28641b8..8b27a3e 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -446,18 +446,18 @@ def edge_type_func(u: str, v: str) -> str: def _load_nx_graph( self, nx_graph: nx.Graph, write_batch_size: int, write_async: bool ) -> None: - v_count = sum( - self.db.collection(v).count() for v in self.adb_graph.vertex_collections() - ) + collections = list(self.adb_graph.vertex_collections()) + collections += [e["edge_collection"] for e in self.adb_graph.edge_definitions()] - e_count = sum( - self.db.collection(e["edge_collection"]).count() - for e in self.adb_graph.edge_definitions() - ) + for col in collections: + cursor = self.db.aql.execute( + "FOR doc IN @@collection LIMIT 1 RETURN 1", + bind_vars={"@collection": col}, + ) - if v_count > 0 or e_count > 0: - m = f"Graph '{self.adb_graph.name}' already has data. Use **overwrite_graph=True** to clear it." # noqa: E501 - raise GraphNotEmpty(m) + if not cursor.empty(): + m = f"Graph '{self.adb_graph.name}' already has data (in '{col}'). Use **overwrite_graph=True** to clear it." # noqa: E501 + raise GraphNotEmpty(m) controller = ADBNX_Controller From 69124a40a6015884e0bd082246f5c00077524b42 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Thu, 26 Sep 2024 17:34:38 -0400 Subject: [PATCH 20/26] fix: whitespace --- nx_arangodb/classes/digraph.py | 1 - nx_arangodb/classes/graph.py | 1 - 2 files changed, 2 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index f1b7ab6..09df294 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -300,7 +300,6 @@ def add_nodes_from_override(self, nodes_for_adding, **attr): self._node[n] = node_attr_dict else: - self._node[n].update(newdict) # Reason: diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 8b27a3e..42a153c 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -746,7 +746,6 @@ def add_nodes_from_override(self, nodes_for_adding, **attr): self._node[n] = node_attr_dict else: - self._node[n].update(newdict) # Reason: From 11347c91d521a246f4e1a5694d278f6d32137d8b Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 30 Sep 2024 08:49:31 -0400 Subject: [PATCH 21/26] fix: drop instead of truncate --- nx_arangodb/classes/digraph.py | 9 +++++---- nx_arangodb/classes/graph.py | 20 ++++++++++++-------- nx_arangodb/classes/multidigraph.py | 9 +++++---- nx_arangodb/classes/multigraph.py | 9 +++++---- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index 09df294..cfc8751 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -126,10 +126,11 @@ class DiGraph(Graph, nx.DiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to drop & re-create the graph collections before loading + **incoming_graph_data** into the graph. NOTE: This parameter only + applies if the graph already exists in the database. NOTE: Dropping + the graph collections will erase all properties of the collections, including + created indexes. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 42a153c..7ff312b 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -160,10 +160,11 @@ class Graph(nx.Graph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to drop & re-create the graph collections before loading + **incoming_graph_data** into the graph. NOTE: This parameter only + applies if the graph already exists in the database. NOTE: Dropping + the graph collections will erase all properties of the collections, including + created indexes. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. @@ -231,13 +232,16 @@ def __init__( self.__set_arangodb_backend_config(read_parallelism, read_batch_size) if overwrite_graph: - logger.info("Truncating graph collections...") + logger.info("Overwriting graph collections...") for col in self.adb_graph.vertex_collections(): - self.db.collection(col).truncate() + self.db.delete_collection(col) + self.db.create_collection(col) - for col in self.adb_graph.edge_definitions(): - self.db.collection(col["edge_collection"]).truncate() + for ed in self.adb_graph.edge_definitions(): + col = ed["edge_collection"] + self.db.delete_collection(col) + self.db.create_collection(col, edge=True) if isinstance(incoming_graph_data, nx.Graph): self._load_nx_graph(incoming_graph_data, write_batch_size, write_async) diff --git a/nx_arangodb/classes/multidigraph.py b/nx_arangodb/classes/multidigraph.py index 451b501..ddf394f 100644 --- a/nx_arangodb/classes/multidigraph.py +++ b/nx_arangodb/classes/multidigraph.py @@ -136,10 +136,11 @@ class MultiDiGraph(MultiGraph, DiGraph, nx.MultiDiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to drop & re-create the graph collections before loading + **incoming_graph_data** into the graph. NOTE: This parameter only + applies if the graph already exists in the database. NOTE: Dropping + the graph collections will erase all properties of the collections, including + created indexes. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 0ce983e..491da91 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -137,10 +137,11 @@ class MultiGraph(Graph, nx.MultiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to drop & re-create the graph collections before loading + **incoming_graph_data** into the graph. NOTE: This parameter only + applies if the graph already exists in the database. NOTE: Dropping + the graph collections will erase all properties of the collections, including + created indexes. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. From eb7c2ccf9a1c5179cc10543400f8d5511d2e5c45 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 30 Sep 2024 08:54:59 -0400 Subject: [PATCH 22/26] Revert "fix: drop instead of truncate" This reverts commit 11347c91d521a246f4e1a5694d278f6d32137d8b. --- nx_arangodb/classes/digraph.py | 9 ++++----- nx_arangodb/classes/graph.py | 20 ++++++++------------ nx_arangodb/classes/multidigraph.py | 9 ++++----- nx_arangodb/classes/multigraph.py | 9 ++++----- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index cfc8751..09df294 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -126,11 +126,10 @@ class DiGraph(Graph, nx.DiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to drop & re-create the graph collections before loading - **incoming_graph_data** into the graph. NOTE: This parameter only - applies if the graph already exists in the database. NOTE: Dropping - the graph collections will erase all properties of the collections, including - created indexes. + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 7ff312b..42a153c 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -160,11 +160,10 @@ class Graph(nx.Graph): as expected. overwrite_graph : bool (optional, default: False) - Whether to drop & re-create the graph collections before loading - **incoming_graph_data** into the graph. NOTE: This parameter only - applies if the graph already exists in the database. NOTE: Dropping - the graph collections will erase all properties of the collections, including - created indexes. + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. @@ -232,16 +231,13 @@ def __init__( self.__set_arangodb_backend_config(read_parallelism, read_batch_size) if overwrite_graph: - logger.info("Overwriting graph collections...") + logger.info("Truncating graph collections...") for col in self.adb_graph.vertex_collections(): - self.db.delete_collection(col) - self.db.create_collection(col) + self.db.collection(col).truncate() - for ed in self.adb_graph.edge_definitions(): - col = ed["edge_collection"] - self.db.delete_collection(col) - self.db.create_collection(col, edge=True) + for col in self.adb_graph.edge_definitions(): + self.db.collection(col["edge_collection"]).truncate() if isinstance(incoming_graph_data, nx.Graph): self._load_nx_graph(incoming_graph_data, write_batch_size, write_async) diff --git a/nx_arangodb/classes/multidigraph.py b/nx_arangodb/classes/multidigraph.py index ddf394f..451b501 100644 --- a/nx_arangodb/classes/multidigraph.py +++ b/nx_arangodb/classes/multidigraph.py @@ -136,11 +136,10 @@ class MultiDiGraph(MultiGraph, DiGraph, nx.MultiDiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to drop & re-create the graph collections before loading - **incoming_graph_data** into the graph. NOTE: This parameter only - applies if the graph already exists in the database. NOTE: Dropping - the graph collections will erase all properties of the collections, including - created indexes. + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 491da91..0ce983e 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -137,11 +137,10 @@ class MultiGraph(Graph, nx.MultiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to drop & re-create the graph collections before loading - **incoming_graph_data** into the graph. NOTE: This parameter only - applies if the graph already exists in the database. NOTE: Dropping - the graph collections will erase all properties of the collections, including - created indexes. + Whether to truncate the graph collections when the graph is loaded from + the database. If set to True, the graph collections will be truncated + before loading the graph data. NOTE: This parameter only applies if the + graph already exists in the database. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. From 81a1fba9694e52bbdf79426ec69d12c9fb6097e4 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 30 Sep 2024 08:56:19 -0400 Subject: [PATCH 23/26] add `overwrite_graph` coverage --- tests/test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test.py b/tests/test.py index 2201bb0..c454554 100644 --- a/tests/test.py +++ b/tests/test.py @@ -174,6 +174,18 @@ def test_load_graph_from_nxadb_as_smart_graph(): assert G.has_node("Officer:36") assert G.has_node("Mr.Hi:37") + assert db.collection("smart_person").properties()["smart"] + + G = nxadb.Graph( + name=graph_name, + incoming_graph_data=G_NX_copy, + write_async=False, + overwrite_graph=True, + ) + + assert db.collection("smart_person").properties()["smart"] + assert G.nodes["Mr.Hi:0"]["club"] == "Mr.Hi" + db.delete_graph(graph_name, drop_collections=True) From 35100f9549633c49b621c7acbf29d756d022292b Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 30 Sep 2024 17:24:51 -0400 Subject: [PATCH 24/26] fix: drop graph instead of truncate --- nx_arangodb/classes/graph.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 42a153c..86414e9 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -231,13 +231,21 @@ def __init__( self.__set_arangodb_backend_config(read_parallelism, read_batch_size) if overwrite_graph: - logger.info("Truncating graph collections...") - - for col in self.adb_graph.vertex_collections(): - self.db.collection(col).truncate() - - for col in self.adb_graph.edge_definitions(): - self.db.collection(col["edge_collection"]).truncate() + logger.info("Overwriting graph...") + + properties = self.adb_graph.properties() + self.db.delete_graph(name, drop_collections=True) + self.db.create_graph( + name=name, + edge_definitions=properties["edge_definitions"], + orphan_collections=properties["orphan_collections"], + smart=properties.get("smart"), + disjoint=properties.get("disjoint"), + smart_field=properties.get("smart_field"), + shard_count=properties.get("shard_count"), + replication_factor=properties.get("replication_factor"), + write_concern=properties.get("write_concern"), + ) if isinstance(incoming_graph_data, nx.Graph): self._load_nx_graph(incoming_graph_data, write_batch_size, write_async) From 7549f6a934c7fd85a999d5bf3283c8cdaea273e3 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Mon, 30 Sep 2024 17:29:16 -0400 Subject: [PATCH 25/26] fix: docstring --- nx_arangodb/classes/digraph.py | 8 ++++---- nx_arangodb/classes/graph.py | 8 ++++---- nx_arangodb/classes/multidigraph.py | 8 ++++---- nx_arangodb/classes/multigraph.py | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index 09df294..8467bc9 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -126,10 +126,10 @@ class DiGraph(Graph, nx.DiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to overwrite the graph in the database if it already exists. If + set to True, the graph collections will be dropped and recreated. Note that + this operation is irreversible and will result in the loss of all data in + the graph. NOTE: If set to True, Collection Indexes will also be lost. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 86414e9..27b0c39 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -160,10 +160,10 @@ class Graph(nx.Graph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to overwrite the graph in the database if it already exists. If + set to True, the graph collections will be dropped and recreated. Note that + this operation is irreversible and will result in the loss of all data in + the graph. NOTE: If set to True, Collection Indexes will also be lost. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/multidigraph.py b/nx_arangodb/classes/multidigraph.py index 451b501..7ca7652 100644 --- a/nx_arangodb/classes/multidigraph.py +++ b/nx_arangodb/classes/multidigraph.py @@ -136,10 +136,10 @@ class MultiDiGraph(MultiGraph, DiGraph, nx.MultiDiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to overwrite the graph in the database if it already exists. If + set to True, the graph collections will be dropped and recreated. Note that + this operation is irreversible and will result in the loss of all data in + the graph. NOTE: If set to True, Collection Indexes will also be lost. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 0ce983e..1a4b1eb 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -137,10 +137,10 @@ class MultiGraph(Graph, nx.MultiGraph): as expected. overwrite_graph : bool (optional, default: False) - Whether to truncate the graph collections when the graph is loaded from - the database. If set to True, the graph collections will be truncated - before loading the graph data. NOTE: This parameter only applies if the - graph already exists in the database. + Whether to overwrite the graph in the database if it already exists. If + set to True, the graph collections will be dropped and recreated. Note that + this operation is irreversible and will result in the loss of all data in + the graph. NOTE: If set to True, Collection Indexes will also be lost. args: positional arguments for nx.Graph Additional arguments passed to nx.Graph. From 877049cd6f15ad4d135883ba9ca2a1c14621b77a Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 1 Oct 2024 10:16:09 -0400 Subject: [PATCH 26/26] fix: `name` docstring --- nx_arangodb/classes/digraph.py | 4 ++-- nx_arangodb/classes/graph.py | 4 ++-- nx_arangodb/classes/multidigraph.py | 4 ++-- nx_arangodb/classes/multigraph.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nx_arangodb/classes/digraph.py b/nx_arangodb/classes/digraph.py index 8467bc9..3bfb943 100644 --- a/nx_arangodb/classes/digraph.py +++ b/nx_arangodb/classes/digraph.py @@ -60,8 +60,8 @@ class DiGraph(Graph, nx.DiGraph): name : str (optional, default: None) Name of the graph in the database. If the graph already exists, the user can pass the name of the graph to connect to it. If - the graph does not exist, the user can create a new graph by - passing the name. NOTE: Must be used in conjunction with + the graph does not exist, a General Graph will be created by + passing the **name**. NOTE: Must be used in conjunction with **incoming_graph_data** if the user wants to persist the graph in ArangoDB. diff --git a/nx_arangodb/classes/graph.py b/nx_arangodb/classes/graph.py index 27b0c39..336cc3b 100644 --- a/nx_arangodb/classes/graph.py +++ b/nx_arangodb/classes/graph.py @@ -94,8 +94,8 @@ class Graph(nx.Graph): name : str (optional, default: None) Name of the graph in the database. If the graph already exists, the user can pass the name of the graph to connect to it. If - the graph does not exist, the user can create a new graph by - passing the name. NOTE: Must be used in conjunction with + the graph does not exist, a General Graph will be created by + passing the **name**. NOTE: Must be used in conjunction with **incoming_graph_data** if the user wants to persist the graph in ArangoDB. diff --git a/nx_arangodb/classes/multidigraph.py b/nx_arangodb/classes/multidigraph.py index 7ca7652..f115ab8 100644 --- a/nx_arangodb/classes/multidigraph.py +++ b/nx_arangodb/classes/multidigraph.py @@ -70,8 +70,8 @@ class MultiDiGraph(MultiGraph, DiGraph, nx.MultiDiGraph): name : str (optional, default: None) Name of the graph in the database. If the graph already exists, the user can pass the name of the graph to connect to it. If - the graph does not exist, the user can create a new graph by - passing the name. NOTE: Must be used in conjunction with + the graph does not exist, a General Graph will be created by + passing the **name**. NOTE: Must be used in conjunction with **incoming_graph_data** if the user wants to persist the graph in ArangoDB. diff --git a/nx_arangodb/classes/multigraph.py b/nx_arangodb/classes/multigraph.py index 1a4b1eb..c494d34 100644 --- a/nx_arangodb/classes/multigraph.py +++ b/nx_arangodb/classes/multigraph.py @@ -71,8 +71,8 @@ class MultiGraph(Graph, nx.MultiGraph): name : str (optional, default: None) Name of the graph in the database. If the graph already exists, the user can pass the name of the graph to connect to it. If - the graph does not exist, the user can create a new graph by - passing the name. NOTE: Must be used in conjunction with + the graph does not exist, a General Graph will be created by + passing the **name**. NOTE: Must be used in conjunction with **incoming_graph_data** if the user wants to persist the graph in ArangoDB.