213 lines
6.3 KiB
Python
213 lines
6.3 KiB
Python
|
"""
|
||
|
altgraph.ObjectGraph - Graph of objects with an identifier
|
||
|
==========================================================
|
||
|
|
||
|
A graph of objects that have a "graphident" attribute.
|
||
|
graphident is the key for the object in the graph
|
||
|
"""
|
||
|
|
||
|
from altgraph import GraphError
|
||
|
from altgraph.Graph import Graph
|
||
|
from altgraph.GraphUtil import filter_stack
|
||
|
|
||
|
|
||
|
class ObjectGraph(object):
|
||
|
"""
|
||
|
A graph of objects that have a "graphident" attribute.
|
||
|
graphident is the key for the object in the graph
|
||
|
"""
|
||
|
|
||
|
def __init__(self, graph=None, debug=0):
|
||
|
if graph is None:
|
||
|
graph = Graph()
|
||
|
self.graphident = self
|
||
|
self.graph = graph
|
||
|
self.debug = debug
|
||
|
self.indent = 0
|
||
|
graph.add_node(self, None)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "<%s>" % (type(self).__name__,)
|
||
|
|
||
|
def flatten(self, condition=None, start=None):
|
||
|
"""
|
||
|
Iterate over the subgraph that is entirely reachable by condition
|
||
|
starting from the given start node or the ObjectGraph root
|
||
|
"""
|
||
|
if start is None:
|
||
|
start = self
|
||
|
start = self.getRawIdent(start)
|
||
|
return self.graph.iterdata(start=start, condition=condition)
|
||
|
|
||
|
def nodes(self):
|
||
|
for ident in self.graph:
|
||
|
node = self.graph.node_data(ident)
|
||
|
if node is not None:
|
||
|
yield self.graph.node_data(ident)
|
||
|
|
||
|
def get_edges(self, node):
|
||
|
if node is None:
|
||
|
node = self
|
||
|
start = self.getRawIdent(node)
|
||
|
_, _, outraw, incraw = self.graph.describe_node(start)
|
||
|
|
||
|
def iter_edges(lst, n):
|
||
|
seen = set()
|
||
|
for tpl in (self.graph.describe_edge(e) for e in lst):
|
||
|
ident = tpl[n]
|
||
|
if ident not in seen:
|
||
|
yield self.findNode(ident)
|
||
|
seen.add(ident)
|
||
|
|
||
|
return iter_edges(outraw, 3), iter_edges(incraw, 2)
|
||
|
|
||
|
def edgeData(self, fromNode, toNode):
|
||
|
if fromNode is None:
|
||
|
fromNode = self
|
||
|
start = self.getRawIdent(fromNode)
|
||
|
stop = self.getRawIdent(toNode)
|
||
|
edge = self.graph.edge_by_node(start, stop)
|
||
|
return self.graph.edge_data(edge)
|
||
|
|
||
|
def updateEdgeData(self, fromNode, toNode, edgeData):
|
||
|
if fromNode is None:
|
||
|
fromNode = self
|
||
|
start = self.getRawIdent(fromNode)
|
||
|
stop = self.getRawIdent(toNode)
|
||
|
edge = self.graph.edge_by_node(start, stop)
|
||
|
self.graph.update_edge_data(edge, edgeData)
|
||
|
|
||
|
def filterStack(self, filters):
|
||
|
"""
|
||
|
Filter the ObjectGraph in-place by removing all edges to nodes that
|
||
|
do not match every filter in the given filter list
|
||
|
|
||
|
Returns a tuple containing the number of:
|
||
|
(nodes_visited, nodes_removed, nodes_orphaned)
|
||
|
"""
|
||
|
visited, removes, orphans = filter_stack(self.graph, self, filters)
|
||
|
|
||
|
for last_good, tail in orphans:
|
||
|
self.graph.add_edge(last_good, tail, edge_data="orphan")
|
||
|
|
||
|
for node in removes:
|
||
|
self.graph.hide_node(node)
|
||
|
|
||
|
return len(visited) - 1, len(removes), len(orphans)
|
||
|
|
||
|
def removeNode(self, node):
|
||
|
"""
|
||
|
Remove the given node from the graph if it exists
|
||
|
"""
|
||
|
ident = self.getIdent(node)
|
||
|
if ident is not None:
|
||
|
self.graph.hide_node(ident)
|
||
|
|
||
|
def removeReference(self, fromnode, tonode):
|
||
|
"""
|
||
|
Remove all edges from fromnode to tonode
|
||
|
"""
|
||
|
if fromnode is None:
|
||
|
fromnode = self
|
||
|
fromident = self.getIdent(fromnode)
|
||
|
toident = self.getIdent(tonode)
|
||
|
if fromident is not None and toident is not None:
|
||
|
while True:
|
||
|
edge = self.graph.edge_by_node(fromident, toident)
|
||
|
if edge is None:
|
||
|
break
|
||
|
self.graph.hide_edge(edge)
|
||
|
|
||
|
def getIdent(self, node):
|
||
|
"""
|
||
|
Get the graph identifier for a node
|
||
|
"""
|
||
|
ident = self.getRawIdent(node)
|
||
|
if ident is not None:
|
||
|
return ident
|
||
|
node = self.findNode(node)
|
||
|
if node is None:
|
||
|
return None
|
||
|
return node.graphident
|
||
|
|
||
|
def getRawIdent(self, node):
|
||
|
"""
|
||
|
Get the identifier for a node object
|
||
|
"""
|
||
|
if node is self:
|
||
|
return node
|
||
|
ident = getattr(node, "graphident", None)
|
||
|
return ident
|
||
|
|
||
|
def __contains__(self, node):
|
||
|
return self.findNode(node) is not None
|
||
|
|
||
|
def findNode(self, node):
|
||
|
"""
|
||
|
Find the node on the graph
|
||
|
"""
|
||
|
ident = self.getRawIdent(node)
|
||
|
if ident is None:
|
||
|
ident = node
|
||
|
try:
|
||
|
return self.graph.node_data(ident)
|
||
|
except KeyError:
|
||
|
return None
|
||
|
|
||
|
def addNode(self, node):
|
||
|
"""
|
||
|
Add a node to the graph referenced by the root
|
||
|
"""
|
||
|
self.msg(4, "addNode", node)
|
||
|
|
||
|
try:
|
||
|
self.graph.restore_node(node.graphident)
|
||
|
except GraphError:
|
||
|
self.graph.add_node(node.graphident, node)
|
||
|
|
||
|
def createReference(self, fromnode, tonode, edge_data=None):
|
||
|
"""
|
||
|
Create a reference from fromnode to tonode
|
||
|
"""
|
||
|
if fromnode is None:
|
||
|
fromnode = self
|
||
|
fromident, toident = self.getIdent(fromnode), self.getIdent(tonode)
|
||
|
if fromident is None or toident is None:
|
||
|
return
|
||
|
self.msg(4, "createReference", fromnode, tonode, edge_data)
|
||
|
self.graph.add_edge(fromident, toident, edge_data=edge_data)
|
||
|
|
||
|
def createNode(self, cls, name, *args, **kw):
|
||
|
"""
|
||
|
Add a node of type cls to the graph if it does not already exist
|
||
|
by the given name
|
||
|
"""
|
||
|
m = self.findNode(name)
|
||
|
if m is None:
|
||
|
m = cls(name, *args, **kw)
|
||
|
self.addNode(m)
|
||
|
return m
|
||
|
|
||
|
def msg(self, level, s, *args):
|
||
|
"""
|
||
|
Print a debug message with the given level
|
||
|
"""
|
||
|
if s and level <= self.debug:
|
||
|
print("%s%s %s" % (" " * self.indent, s, " ".join(map(repr, args))))
|
||
|
|
||
|
def msgin(self, level, s, *args):
|
||
|
"""
|
||
|
Print a debug message and indent
|
||
|
"""
|
||
|
if level <= self.debug:
|
||
|
self.msg(level, s, *args)
|
||
|
self.indent = self.indent + 1
|
||
|
|
||
|
def msgout(self, level, s, *args):
|
||
|
"""
|
||
|
Dedent and print a debug message
|
||
|
"""
|
||
|
if level <= self.debug:
|
||
|
self.indent = self.indent - 1
|
||
|
self.msg(level, s, *args)
|