""" 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)