From 2a898ce3dd4dea0fa76ed914fdcdc604f955492a Mon Sep 17 00:00:00 2001 From: Shivam Kapoor <4599890+iamsmkr@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:39:02 +0000 Subject: [PATCH] impl import as for py --- .../test_graphdb/test_graphdb_imports.py | 218 ++++++++++++++++++ raphtory/src/python/graph/graph.rs | 87 ++++++- .../src/python/graph/graph_with_deletions.rs | 88 ++++++- 3 files changed, 389 insertions(+), 4 deletions(-) diff --git a/python/tests/test_graphdb/test_graphdb_imports.py b/python/tests/test_graphdb/test_graphdb_imports.py index 836771562..736f79f19 100644 --- a/python/tests/test_graphdb/test_graphdb_imports.py +++ b/python/tests/test_graphdb/test_graphdb_imports.py @@ -56,3 +56,221 @@ def test_import_with_int(): g2.import_node(g.node(1)) g2.import_nodes([g.node(2), g.node(3)]) assert g2.count_nodes() == g.count_nodes() + + +def test_import_node_as(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + + gg = Graph() + res = gg.import_node_as(a, "X") + assert res.name == "X" + assert res.history().tolist() == [1] + + gg.add_node(1, "Y") + + with pytest.raises(Exception) as excinfo: + gg.import_node_as(b, "Y") + + assert "Node already exists" in str(excinfo.value) + + assert gg.nodes.name == ["X", "Y"] + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [1] + assert y.properties.get("temp") is None + assert y.properties.constant.get("con") is None + + +def test_import_node_as_force(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + + gg = Graph() + res = gg.import_node_as(a, "X") + assert res.name == "X" + assert res.history().tolist() == [1] + + gg.add_node(1, "Y") + gg.import_node_as(b, "Y", True) + + assert gg.nodes.name == ["X", "Y"] + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [1] + assert y.properties.get("temp") == True + assert y.properties.constant.get("con") == 11 + + +def test_import_nodes_as(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + + gg = Graph() + gg.add_node(1, "Y") + + with pytest.raises(Exception) as excinfo: + gg.import_nodes_as([a, b], ["X", "Y"]) + + assert "Node already exists" in str(excinfo.value) + + x = gg.node("X") + assert x.name == "X" + assert x.history().tolist() == [1] + + assert sorted(gg.nodes.name) == ["X", "Y"] + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [1] + assert y.properties.get("temp") is None + assert y.properties.constant.get("con") is None + + +def test_import_nodes_as_force(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + + gg = Graph() + gg.add_node(1, "Y") + gg.import_nodes_as([a, b], ["X", "Y"], True) + + assert sorted(gg.nodes.name) == ["X", "Y"] + x = gg.node("X") + assert x.name == "X" + assert x.history().tolist() == [1] + + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [1] + assert y.properties.get("temp") == True + assert y.properties.constant.get("con") == 11 + + +def test_import_edge_as(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + + e_a_b = g.add_edge(2, "A", "B", {"e_temp": True}) + + gg = Graph() + gg.add_edge(1, "X", "Y") + + with pytest.raises(Exception) as excinfo: + gg.import_edge_as(e_a_b, ("X", "Y")) + assert "Edge already exists" in str(excinfo.value) + + assert sorted(gg.nodes.name) == ["X", "Y"] + x = gg.node("X") + assert x.name == "X" + assert x.history().tolist() == [1] + + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [1] + assert y.properties.get("temp") is None + assert y.properties.constant.get("con") is None + + e = gg.edge("X", "Y") + assert e.properties.get("e_temp") is None + + +def test_import_edge_as_force(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + + e_a_b = g.add_edge(2, "A", "B", {"e_temp": True}) + + gg = Graph() + gg.add_edge(3, "X", "Y") + gg.import_edge_as(e_a_b, ("X", "Y"), True) + + assert sorted(gg.nodes.name) == ["X", "Y"] + x = gg.node("X") + assert x.name == "X" + print(x.history()) + assert x.history().tolist() == [2, 3] + + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [2, 3] + assert y.properties.get("temp") is None + assert y.properties.constant.get("con") is None + + e = gg.edge("X", "Y") + assert e.properties.get("e_temp") == True + + +def test_import_edges_as(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + c = g.add_node(1, "C") + + e_a_b = g.add_edge(2, "A", "B", {"e_temp": True}) + e_b_c = g.add_edge(2, "B", "C") + + gg = Graph() + gg.add_edge(1, "Y", "Z") + + with pytest.raises(Exception) as excinfo: + gg.import_edges_as([e_a_b, e_b_c], [("X", "Y"), ("Y", "Z")]) + assert "Edge already exists" in str(excinfo.value) + + assert sorted(gg.nodes.name) == ["X", "Y", "Z"] + x = gg.node("X") + assert x.name == "X" + assert x.history().tolist() == [2] + + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [1, 2] + assert y.properties.get("temp") is None + assert y.properties.constant.get("con") is None + + z = gg.node("Z") + assert z.name == "Z" + assert z.history().tolist() == [1] + + +def test_import_edges_as_force(): + g = Graph() + a = g.add_node(1, "A") + b = g.add_node(1, "B", {"temp": True}) + b.add_constant_properties({"con": 11}) + c = g.add_node(1, "C") + + e_a_b = g.add_edge(2, "A", "B", {"e_temp": True}) + e_b_c = g.add_edge(2, "B", "C") + + gg = Graph() + gg.add_edge(3, "Y", "Z") + gg.import_edges_as([e_a_b, e_b_c], [("X", "Y"), ("Y", "Z")], True) + + assert sorted(gg.nodes.name) == ["X", "Y", "Z"] + + x = gg.node("X") + assert x.name == "X" + assert x.history().tolist() == [2] + + y = gg.node("Y") + assert y.name == "Y" + assert y.history().tolist() == [2, 3] + assert y.properties.get("temp") is None + assert y.properties.constant.get("con") is None + + z = gg.node("Z") + assert z.name == "Z" + assert z.history().tolist() == [2, 3] diff --git a/raphtory/src/python/graph/graph.rs b/raphtory/src/python/graph/graph.rs index 5f4849017..6f5a1adce 100644 --- a/raphtory/src/python/graph/graph.rs +++ b/raphtory/src/python/graph/graph.rs @@ -291,13 +291,34 @@ impl PyGraph { self.graph.import_node(&node.node, force) } + /// Import a single node into the graph with new id. + /// + /// This function takes a PyNode object, a new id for the node and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the node even if it already exists in the graph. + /// + /// Arguments: + /// node (Node): A Node object representing the node to be imported. + /// new_id (str|int): The new node id. + /// force (bool): An optional boolean flag indicating whether to force the import of the node. + /// + /// Returns: + /// Node: A Result object which is Ok if the node was successfully imported, and Err otherwise. + #[pyo3(signature = (node, new_id, force = false))] + pub fn import_node_as( + &self, + node: PyNode, + new_id: GID, + force: bool, + ) -> Result, GraphError> { + self.graph.import_node_as(&node.node, new_id, force) + } + /// Import multiple nodes into the graph. /// /// This function takes a vector of PyNode objects and an optional boolean flag. If the flag is set to true, /// the function will force the import of the nodes even if they already exist in the graph. /// /// Arguments: - /// /// nodes (List[Node]): A vector of PyNode objects representing the nodes to be imported. /// force (bool): An optional boolean flag indicating whether to force the import of the nodes. /// @@ -307,6 +328,27 @@ impl PyGraph { self.graph.import_nodes(node_views, force) } + /// Import multiple nodes into the graph with new ids. + /// + /// This function takes a vector of PyNode objects, a list of new node ids and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the nodes even if they already exist in the graph. + /// + /// Arguments: + /// nodes (List[Node]): A vector of PyNode objects representing the nodes to be imported. + /// new_ids (List[str|int]): A list of node IDs to use for the imported nodes. + /// force (bool): An optional boolean flag indicating whether to force the import of the nodes. + /// + #[pyo3(signature = (nodes, new_ids, force = false))] + pub fn import_nodes_as( + &self, + nodes: Vec, + new_ids: Vec, + force: bool, + ) -> Result<(), GraphError> { + let node_views = nodes.iter().map(|node| &node.node); + self.graph.import_nodes_as(node_views, new_ids, force) + } + /// Import a single edge into the graph. /// /// This function takes a PyEdge object and an optional boolean flag. If the flag is set to true, @@ -328,13 +370,34 @@ impl PyGraph { self.graph.import_edge(&edge.edge, force) } + /// Import a single edge into the graph with new id. + /// + /// This function takes a PyEdge object, a new id for the edge and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the edge even if it already exists in the graph. + /// + /// Arguments: + /// edge (Edge): A PyEdge object representing the edge to be imported. + /// new_id (tuple) : The ID of the new edge. It's a tuple of the source and destination node ids. + /// force (bool): An optional boolean flag indicating whether to force the import of the edge. + /// + /// Returns: + /// Edge: A Result object which is Ok if the edge was successfully imported, and Err otherwise. + #[pyo3(signature = (edge, new_id, force = false))] + pub fn import_edge_as( + &self, + edge: PyEdge, + new_id: (GID, GID), + force: bool, + ) -> Result, GraphError> { + self.graph.import_edge_as(&edge.edge, new_id, force) + } + /// Import multiple edges into the graph. /// /// This function takes a vector of PyEdge objects and an optional boolean flag. If the flag is set to true, /// the function will force the import of the edges even if they already exist in the graph. /// /// Arguments: - /// /// edges (List[Edge]): A list of Edge objects representing the edges to be imported. /// force (bool): An optional boolean flag indicating whether to force the import of the edges. #[pyo3(signature = (edges, force = false))] @@ -343,6 +406,26 @@ impl PyGraph { self.graph.import_edges(edge_views, force) } + /// Import multiple edges into the graph with new ids. + /// + /// This function takes a vector of PyEdge objects, a list of new edge ids and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the edges even if they already exist in the graph. + /// + /// Arguments: + /// edges (List[Edge]): A list of Edge objects representing the edges to be imported. + /// new_ids (List[tuple]) - The IDs of the new edges. It's a vector of tuples of the source and destination node ids. + /// force (bool): An optional boolean flag indicating whether to force the import of the edges. + #[pyo3(signature = (edges, new_ids, force = false))] + pub fn import_edges_as( + &self, + edges: Vec, + new_ids: Vec<(GID, GID)>, + force: bool, + ) -> Result<(), GraphError> { + let edge_views = edges.iter().map(|edge| &edge.edge); + self.graph.import_edges_as(edge_views, new_ids, force) + } + //FIXME: This is reimplemented here to get mutable views. If we switch the underlying graph to enum dispatch, this won't be necessary! /// Gets the node with the specified id /// diff --git a/raphtory/src/python/graph/graph_with_deletions.rs b/raphtory/src/python/graph/graph_with_deletions.rs index e3f78469b..d695a2f0a 100644 --- a/raphtory/src/python/graph/graph_with_deletions.rs +++ b/raphtory/src/python/graph/graph_with_deletions.rs @@ -292,13 +292,34 @@ impl PyPersistentGraph { self.graph.import_node(&node.node, force) } + /// Import a single node into the graph with new id. + /// + /// This function takes a PyNode object, a new node id and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the node even if it already exists in the graph. + /// + /// Arguments: + /// node (Node): A PyNode object representing the node to be imported. + /// new_id (str|int): The new node id. + /// force (bool): An optional boolean flag indicating whether to force the import of the node. + /// + /// Returns: + /// Result, GraphError> - A Result object which is Ok if the node was successfully imported, and Err otherwise. + #[pyo3(signature = (node, new_id, force = false))] + pub fn import_node_as( + &self, + node: PyNode, + new_id: GID, + force: bool, + ) -> Result, GraphError> { + self.graph.import_node_as(&node.node, new_id, force) + } + /// Import multiple nodes into the graph. /// /// This function takes a vector of PyNode objects and an optional boolean flag. If the flag is set to true, /// the function will force the import of the nodes even if they already exist in the graph. /// /// Arguments: - /// /// nodes (List[Node]): A vector of PyNode objects representing the nodes to be imported. /// force (bool): An optional boolean flag indicating whether to force the import of the nodes. /// @@ -308,13 +329,33 @@ impl PyPersistentGraph { self.graph.import_nodes(node_views, force) } + /// Import multiple nodes into the graph with new ids. + /// + /// This function takes a vector of PyNode objects, a list of new node ids and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the nodes even if they already exist in the graph. + /// + /// Arguments: + /// nodes (List[Node]): A vector of PyNode objects representing the nodes to be imported. + /// new_ids (List[str|int]): A list of node IDs to use for the imported nodes. + /// force (bool): An optional boolean flag indicating whether to force the import of the nodes. + /// + #[pyo3(signature = (nodes, new_ids, force = false))] + pub fn import_nodes_as( + &self, + nodes: Vec, + new_ids: Vec, + force: bool, + ) -> Result<(), GraphError> { + let node_views = nodes.iter().map(|node| &node.node); + self.graph.import_nodes_as(node_views, new_ids, force) + } + /// Import a single edge into the graph. /// /// This function takes a PyEdge object and an optional boolean flag. If the flag is set to true, /// the function will force the import of the edge even if it already exists in the graph. /// /// Arguments: - /// /// edge (Edge): A PyEdge object representing the edge to be imported. /// force (bool): An optional boolean flag indicating whether to force the import of the edge. /// @@ -329,6 +370,28 @@ impl PyPersistentGraph { self.graph.import_edge(&edge.edge, force) } + /// Import a single edge into the graph with new id. + /// + /// This function takes a PyEdge object, a new edge id and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the edge even if it already exists in the graph. + /// + /// Arguments: + /// edge (Edge): A PyEdge object representing the edge to be imported. + /// new_id (tuple) : The ID of the new edge. It's a tuple of the source and destination node ids. + /// force (bool): An optional boolean flag indicating whether to force the import of the edge. + /// + /// Returns: + /// Edge: The imported edge. + #[pyo3(signature = (edge, new_id, force = false))] + pub fn import_edge_as( + &self, + edge: PyEdge, + new_id: (GID, GID), + force: bool, + ) -> Result, GraphError> { + self.graph.import_edge_as(&edge.edge, new_id, force) + } + /// Import multiple edges into the graph. /// /// This function takes a vector of PyEdge objects and an optional boolean flag. If the flag is set to true, @@ -345,6 +408,27 @@ impl PyPersistentGraph { self.graph.import_edges(edge_views, force) } + /// Import multiple edges into the graph with new ids. + /// + /// This function takes a vector of PyEdge objects, a list of new edge ids and an optional boolean flag. If the flag is set to true, + /// the function will force the import of the edges even if they already exist in the graph. + /// + /// Arguments: + /// + /// edges (List[Edge]): A vector of PyEdge objects representing the edges to be imported. + /// force (bool): An optional boolean flag indicating whether to force the import of the edges. + /// + #[pyo3(signature = (edges, new_ids, force = false))] + pub fn import_edges_as( + &self, + edges: Vec, + new_ids: Vec<(GID, GID)>, + force: bool, + ) -> Result<(), GraphError> { + let edge_views = edges.iter().map(|edge| &edge.edge); + self.graph.import_edges_as(edge_views, new_ids, force) + } + //****** Saving And Loading ******// // Alternative constructors are tricky, see: https://gist.github.com/redshiftzero/648e4feeff3843ffd9924f13625f839c