implica 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of implica might be problematic. Click here for more details.
- implica/__init__.py +44 -0
- implica/core/__init__.py +4 -0
- implica/core/combinator.py +149 -0
- implica/core/types.py +140 -0
- implica/graph/__init__.py +5 -0
- implica/graph/connection.py +389 -0
- implica/graph/elements.py +194 -0
- implica/graph/graph.py +443 -0
- implica/mutations/__init__.py +29 -0
- implica/mutations/add_edge.py +45 -0
- implica/mutations/add_many_edges.py +60 -0
- implica/mutations/add_many_nodes.py +60 -0
- implica/mutations/add_node.py +45 -0
- implica/mutations/base.py +51 -0
- implica/mutations/remove_edge.py +54 -0
- implica/mutations/remove_many_edges.py +64 -0
- implica/mutations/remove_many_nodes.py +69 -0
- implica/mutations/remove_node.py +62 -0
- implica/mutations/try_add_edge.py +53 -0
- implica/mutations/try_add_node.py +50 -0
- implica/mutations/try_remove_edge.py +58 -0
- implica/mutations/try_remove_node.py +62 -0
- implica-0.3.0.dist-info/LICENSE +21 -0
- implica-0.3.0.dist-info/METADATA +945 -0
- implica-0.3.0.dist-info/RECORD +26 -0
- implica-0.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mutation system for transactional graph modifications.
|
|
3
|
+
|
|
4
|
+
This module defines mutations that can be applied to a graph with forward
|
|
5
|
+
and backward operations, enabling transactional rollback on failures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..graph.graph import Graph
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Mutation(ABC):
|
|
16
|
+
"""
|
|
17
|
+
Abstract base class for graph mutations.
|
|
18
|
+
|
|
19
|
+
Each mutation must implement both forward (apply) and backward (revert)
|
|
20
|
+
operations to support transactional rollback.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def forward(self, graph: "Graph") -> None:
|
|
25
|
+
"""
|
|
26
|
+
Apply the mutation to the graph.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
graph: The graph to modify
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
Exception: If the mutation cannot be applied
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def backward(self, graph: "Graph") -> None:
|
|
38
|
+
"""
|
|
39
|
+
Revert the mutation from the graph.
|
|
40
|
+
|
|
41
|
+
This operation should undo what forward() did.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
graph: The graph to modify
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
"""Return a string representation of the mutation."""
|
|
51
|
+
pass
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph.elements import Edge
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph.graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RemoveEdge(Mutation):
|
|
11
|
+
"""Mutation to remove an edge from the graph."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, edge_uid: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the RemoveEdge mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
edge_uid: The uid of the edge to remove
|
|
19
|
+
"""
|
|
20
|
+
self.edge_uid = edge_uid
|
|
21
|
+
self._removed_edge: Edge | None = None
|
|
22
|
+
|
|
23
|
+
def forward(self, graph: "Graph") -> None:
|
|
24
|
+
"""
|
|
25
|
+
Remove the edge from the graph.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
graph: The graph to modify
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
KeyError: If the edge doesn't exist
|
|
32
|
+
"""
|
|
33
|
+
# Store the edge for rollback
|
|
34
|
+
self._removed_edge = graph.get_edge(self.edge_uid)
|
|
35
|
+
|
|
36
|
+
# Remove the edge
|
|
37
|
+
graph._remove_edge(self.edge_uid)
|
|
38
|
+
|
|
39
|
+
def backward(self, graph: "Graph") -> None:
|
|
40
|
+
"""
|
|
41
|
+
Re-add the edge to the graph.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
graph: The graph to modify
|
|
45
|
+
"""
|
|
46
|
+
if self._removed_edge is None:
|
|
47
|
+
raise RuntimeError("Cannot revert RemoveEdge: edge was not stored")
|
|
48
|
+
|
|
49
|
+
# Add the edge back
|
|
50
|
+
graph._add_edge(self._removed_edge)
|
|
51
|
+
|
|
52
|
+
def __str__(self) -> str:
|
|
53
|
+
"""Return a string representation."""
|
|
54
|
+
return f"RemoveEdge({self.edge_uid})"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph.elements import Edge
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph.graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RemoveManyEdges(Mutation):
|
|
11
|
+
"""Mutation to remove multiple edges from the graph."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, edge_uids: list[str]):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the RemoveManyEdges mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
edge_uids: List of edge uids to remove
|
|
19
|
+
"""
|
|
20
|
+
self.edge_uids = edge_uids
|
|
21
|
+
self._removed_edges: list[Edge] = []
|
|
22
|
+
|
|
23
|
+
def forward(self, graph: "Graph") -> None:
|
|
24
|
+
"""
|
|
25
|
+
Remove all edges from the graph.
|
|
26
|
+
|
|
27
|
+
If any edge fails to be removed, all previously removed edges are restored.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
graph: The graph to modify
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
KeyError: If any edge doesn't exist
|
|
34
|
+
"""
|
|
35
|
+
self._removed_edges = []
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
for edge_uid in self.edge_uids:
|
|
39
|
+
# Store the edge for rollback
|
|
40
|
+
edge = graph.get_edge(edge_uid)
|
|
41
|
+
self._removed_edges.append(edge)
|
|
42
|
+
|
|
43
|
+
# Remove the edge
|
|
44
|
+
graph._remove_edge(edge_uid)
|
|
45
|
+
except Exception:
|
|
46
|
+
# Rollback: restore all edges that were removed
|
|
47
|
+
for edge in self._removed_edges:
|
|
48
|
+
graph._add_edge(edge)
|
|
49
|
+
self._removed_edges = []
|
|
50
|
+
raise
|
|
51
|
+
|
|
52
|
+
def backward(self, graph: "Graph") -> None:
|
|
53
|
+
"""
|
|
54
|
+
Re-add all removed edges to the graph.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
graph: The graph to modify
|
|
58
|
+
"""
|
|
59
|
+
for edge in reversed(self._removed_edges):
|
|
60
|
+
graph._add_edge(edge)
|
|
61
|
+
|
|
62
|
+
def __str__(self) -> str:
|
|
63
|
+
"""Return a string representation."""
|
|
64
|
+
return f"RemoveManyEdges(count={len(self.edge_uids)})"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph.elements import Node, Edge
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph.graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RemoveManyNodes(Mutation):
|
|
11
|
+
"""Mutation to remove multiple nodes from the graph."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, node_uids: list[str]):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the RemoveManyNodes mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
node_uids: List of node uids to remove
|
|
19
|
+
"""
|
|
20
|
+
self.node_uids = node_uids
|
|
21
|
+
self._removed_data: list[tuple[Node, list[Edge]]] = []
|
|
22
|
+
|
|
23
|
+
def forward(self, graph: "Graph") -> None:
|
|
24
|
+
"""
|
|
25
|
+
Remove all nodes from the graph.
|
|
26
|
+
|
|
27
|
+
If any node fails to be removed, all previously removed nodes are restored.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
graph: The graph to modify
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
KeyError: If any node doesn't exist
|
|
34
|
+
"""
|
|
35
|
+
self._removed_data = []
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
for node_uid in self.node_uids:
|
|
39
|
+
# Store the node and its edges for rollback
|
|
40
|
+
node = graph.get_node(node_uid)
|
|
41
|
+
edges = graph.get_edges_for_node(node_uid)
|
|
42
|
+
self._removed_data.append((node, edges))
|
|
43
|
+
|
|
44
|
+
# Remove the node
|
|
45
|
+
graph._remove_node(node_uid)
|
|
46
|
+
except Exception:
|
|
47
|
+
# Rollback: restore all nodes that were removed
|
|
48
|
+
for node, edges in self._removed_data:
|
|
49
|
+
graph._add_node(node)
|
|
50
|
+
for edge in edges:
|
|
51
|
+
graph._add_edge(edge)
|
|
52
|
+
self._removed_data = []
|
|
53
|
+
raise
|
|
54
|
+
|
|
55
|
+
def backward(self, graph: "Graph") -> None:
|
|
56
|
+
"""
|
|
57
|
+
Re-add all removed nodes and their edges to the graph.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
graph: The graph to modify
|
|
61
|
+
"""
|
|
62
|
+
for node, edges in reversed(self._removed_data):
|
|
63
|
+
graph._add_node(node)
|
|
64
|
+
for edge in edges:
|
|
65
|
+
graph._add_edge(edge)
|
|
66
|
+
|
|
67
|
+
def __str__(self) -> str:
|
|
68
|
+
"""Return a string representation."""
|
|
69
|
+
return f"RemoveManyNodes(count={len(self.node_uids)})"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph import Edge, Node
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RemoveNode(Mutation):
|
|
11
|
+
"""Mutation to remove a node from the graph."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, node_uid: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the RemoveNode mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
node_uid: The uid of the node to remove
|
|
19
|
+
"""
|
|
20
|
+
self.node_uid = node_uid
|
|
21
|
+
self._removed_node: Node | None = None
|
|
22
|
+
self._removed_edges: list[Edge] = []
|
|
23
|
+
|
|
24
|
+
def forward(self, graph: "Graph") -> None:
|
|
25
|
+
"""
|
|
26
|
+
Remove the node from the graph.
|
|
27
|
+
|
|
28
|
+
Also stores the node and its connected edges for potential rollback.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
graph: The graph to modify
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
KeyError: If the node doesn't exist
|
|
35
|
+
"""
|
|
36
|
+
# Store the node and its edges for rollback
|
|
37
|
+
self._removed_node = graph.get_node(self.node_uid)
|
|
38
|
+
self._removed_edges = graph.get_edges_for_node(self.node_uid)
|
|
39
|
+
|
|
40
|
+
# Remove the node (this will also remove connected edges)
|
|
41
|
+
graph._remove_node(self.node_uid)
|
|
42
|
+
|
|
43
|
+
def backward(self, graph: "Graph") -> None:
|
|
44
|
+
"""
|
|
45
|
+
Re-add the node and its edges to the graph.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
graph: The graph to modify
|
|
49
|
+
"""
|
|
50
|
+
if self._removed_node is None:
|
|
51
|
+
raise RuntimeError("Cannot revert RemoveNode: node was not stored")
|
|
52
|
+
|
|
53
|
+
# Add the node back
|
|
54
|
+
graph._add_node(self._removed_node)
|
|
55
|
+
|
|
56
|
+
# Add the edges back
|
|
57
|
+
for edge in self._removed_edges:
|
|
58
|
+
graph._add_edge(edge)
|
|
59
|
+
|
|
60
|
+
def __str__(self) -> str:
|
|
61
|
+
"""Return a string representation."""
|
|
62
|
+
return f"RemoveNode({self.node_uid})"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph.elements import Edge
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph.graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TryAddEdge(Mutation):
|
|
11
|
+
"""Mutation to add an edge to the graph, or do nothing if it already exists."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, edge: Edge):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the TryAddEdge mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
edge: The edge to add
|
|
19
|
+
"""
|
|
20
|
+
self.edge = edge
|
|
21
|
+
self._was_added: bool = False
|
|
22
|
+
|
|
23
|
+
def forward(self, graph: "Graph") -> None:
|
|
24
|
+
"""
|
|
25
|
+
Try to add the edge to the graph.
|
|
26
|
+
|
|
27
|
+
If the edge already exists, this is a no-op.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
graph: The graph to modify
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
KeyError: If source or destination nodes don't exist
|
|
34
|
+
"""
|
|
35
|
+
if graph.has_edge(self.edge.uid):
|
|
36
|
+
self._was_added = False
|
|
37
|
+
else:
|
|
38
|
+
graph._add_edge(self.edge)
|
|
39
|
+
self._was_added = True
|
|
40
|
+
|
|
41
|
+
def backward(self, graph: "Graph") -> None:
|
|
42
|
+
"""
|
|
43
|
+
Remove the edge from the graph if it was added.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
graph: The graph to modify
|
|
47
|
+
"""
|
|
48
|
+
if self._was_added:
|
|
49
|
+
graph._remove_edge(self.edge.uid)
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
"""Return a string representation."""
|
|
53
|
+
return f"TryAddEdge({self.edge})"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph import Edge, Node
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TryAddNode(Mutation):
|
|
11
|
+
"""Mutation to add a node to the graph, or do nothing if it already exists."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, node: Node):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the TryAddNode mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
node: The node to add
|
|
19
|
+
"""
|
|
20
|
+
self.node = node
|
|
21
|
+
self._was_added: bool = False
|
|
22
|
+
|
|
23
|
+
def forward(self, graph: "Graph") -> None:
|
|
24
|
+
"""
|
|
25
|
+
Try to add the node to the graph.
|
|
26
|
+
|
|
27
|
+
If the node already exists, this is a no-op.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
graph: The graph to modify
|
|
31
|
+
"""
|
|
32
|
+
if graph.has_node(self.node.uid):
|
|
33
|
+
self._was_added = False
|
|
34
|
+
else:
|
|
35
|
+
graph._add_node(self.node)
|
|
36
|
+
self._was_added = True
|
|
37
|
+
|
|
38
|
+
def backward(self, graph: "Graph") -> None:
|
|
39
|
+
"""
|
|
40
|
+
Remove the node from the graph if it was added.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
graph: The graph to modify
|
|
44
|
+
"""
|
|
45
|
+
if self._was_added:
|
|
46
|
+
graph._remove_node(self.node.uid)
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
"""Return a string representation."""
|
|
50
|
+
return f"TryAddNode({self.node})"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph.elements import Edge
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph.graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TryRemoveEdge(Mutation):
|
|
11
|
+
"""Mutation to remove an edge from the graph, or do nothing if it doesn't exist."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, edge_uid: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the TryRemoveEdge mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
edge_uid: The uid of the edge to remove
|
|
19
|
+
"""
|
|
20
|
+
self.edge_uid = edge_uid
|
|
21
|
+
self._was_removed: bool = False
|
|
22
|
+
self._removed_edge: Edge | None = None
|
|
23
|
+
|
|
24
|
+
def forward(self, graph: "Graph") -> None:
|
|
25
|
+
"""
|
|
26
|
+
Try to remove the edge from the graph.
|
|
27
|
+
|
|
28
|
+
If the edge doesn't exist, this is a no-op.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
graph: The graph to modify
|
|
32
|
+
"""
|
|
33
|
+
if not graph.has_edge(self.edge_uid):
|
|
34
|
+
self._was_removed = False
|
|
35
|
+
else:
|
|
36
|
+
# Store the edge for rollback
|
|
37
|
+
self._removed_edge = graph.get_edge(self.edge_uid)
|
|
38
|
+
|
|
39
|
+
# Remove the edge
|
|
40
|
+
graph._remove_edge(self.edge_uid)
|
|
41
|
+
self._was_removed = True
|
|
42
|
+
|
|
43
|
+
def backward(self, graph: "Graph") -> None:
|
|
44
|
+
"""
|
|
45
|
+
Re-add the edge if it was removed.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
graph: The graph to modify
|
|
49
|
+
"""
|
|
50
|
+
if self._was_removed:
|
|
51
|
+
if self._removed_edge is None:
|
|
52
|
+
raise RuntimeError("Cannot revert TryRemoveEdge: edge was not stored")
|
|
53
|
+
|
|
54
|
+
graph._add_edge(self._removed_edge)
|
|
55
|
+
|
|
56
|
+
def __str__(self) -> str:
|
|
57
|
+
"""Return a string representation."""
|
|
58
|
+
return f"TryRemoveEdge({self.edge_uid})"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..graph import Edge, Node
|
|
4
|
+
from .base import Mutation
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..graph import Graph
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TryRemoveNode(Mutation):
|
|
11
|
+
"""Mutation to remove a node from the graph, or do nothing if it doesn't exist."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, node_uid: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the TryRemoveNode mutation.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
node_uid: The uid of the node to remove
|
|
19
|
+
"""
|
|
20
|
+
self.node_uid = node_uid
|
|
21
|
+
self._was_removed: bool = False
|
|
22
|
+
self._removed_node: Node | None = None
|
|
23
|
+
self._removed_edges: list[Edge] = []
|
|
24
|
+
|
|
25
|
+
def forward(self, graph: "Graph") -> None:
|
|
26
|
+
"""
|
|
27
|
+
Try to remove the node from the graph.
|
|
28
|
+
|
|
29
|
+
If the node doesn't exist, this is a no-op.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
graph: The graph to modify
|
|
33
|
+
"""
|
|
34
|
+
if not graph.has_node(self.node_uid):
|
|
35
|
+
self._was_removed = False
|
|
36
|
+
else:
|
|
37
|
+
# Store the node and its edges for rollback
|
|
38
|
+
self._removed_node = graph.get_node(self.node_uid)
|
|
39
|
+
self._removed_edges = graph.get_edges_for_node(self.node_uid)
|
|
40
|
+
|
|
41
|
+
# Remove the node
|
|
42
|
+
graph._remove_node(self.node_uid)
|
|
43
|
+
self._was_removed = True
|
|
44
|
+
|
|
45
|
+
def backward(self, graph: "Graph") -> None:
|
|
46
|
+
"""
|
|
47
|
+
Re-add the node and its edges if it was removed.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
graph: The graph to modify
|
|
51
|
+
"""
|
|
52
|
+
if self._was_removed:
|
|
53
|
+
if self._removed_node is None:
|
|
54
|
+
raise RuntimeError("Cannot revert TryRemoveNode: node was not stored")
|
|
55
|
+
|
|
56
|
+
graph._add_node(self._removed_node)
|
|
57
|
+
for edge in self._removed_edges:
|
|
58
|
+
graph._add_edge(edge)
|
|
59
|
+
|
|
60
|
+
def __str__(self) -> str:
|
|
61
|
+
"""Return a string representation."""
|
|
62
|
+
return f"TryRemoveNode({self.node_uid})"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Carlos Fernandez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|