ObjectGraph.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. """
  2. altgraph.ObjectGraph - Graph of objects with an identifier
  3. ==========================================================
  4. A graph of objects that have a "graphident" attribute.
  5. graphident is the key for the object in the graph
  6. """
  7. from altgraph import GraphError
  8. from altgraph.Graph import Graph
  9. from altgraph.GraphUtil import filter_stack
  10. class ObjectGraph(object):
  11. """
  12. A graph of objects that have a "graphident" attribute.
  13. graphident is the key for the object in the graph
  14. """
  15. def __init__(self, graph=None, debug=0):
  16. if graph is None:
  17. graph = Graph()
  18. self.graphident = self
  19. self.graph = graph
  20. self.debug = debug
  21. self.indent = 0
  22. graph.add_node(self, None)
  23. def __repr__(self):
  24. return "<%s>" % (type(self).__name__,)
  25. def flatten(self, condition=None, start=None):
  26. """
  27. Iterate over the subgraph that is entirely reachable by condition
  28. starting from the given start node or the ObjectGraph root
  29. """
  30. if start is None:
  31. start = self
  32. start = self.getRawIdent(start)
  33. return self.graph.iterdata(start=start, condition=condition)
  34. def nodes(self):
  35. for ident in self.graph:
  36. node = self.graph.node_data(ident)
  37. if node is not None:
  38. yield self.graph.node_data(ident)
  39. def get_edges(self, node):
  40. if node is None:
  41. node = self
  42. start = self.getRawIdent(node)
  43. _, _, outraw, incraw = self.graph.describe_node(start)
  44. def iter_edges(lst, n):
  45. seen = set()
  46. for tpl in (self.graph.describe_edge(e) for e in lst):
  47. ident = tpl[n]
  48. if ident not in seen:
  49. yield self.findNode(ident)
  50. seen.add(ident)
  51. return iter_edges(outraw, 3), iter_edges(incraw, 2)
  52. def edgeData(self, fromNode, toNode):
  53. if fromNode is None:
  54. fromNode = self
  55. start = self.getRawIdent(fromNode)
  56. stop = self.getRawIdent(toNode)
  57. edge = self.graph.edge_by_node(start, stop)
  58. return self.graph.edge_data(edge)
  59. def updateEdgeData(self, fromNode, toNode, edgeData):
  60. if fromNode is None:
  61. fromNode = self
  62. start = self.getRawIdent(fromNode)
  63. stop = self.getRawIdent(toNode)
  64. edge = self.graph.edge_by_node(start, stop)
  65. self.graph.update_edge_data(edge, edgeData)
  66. def filterStack(self, filters):
  67. """
  68. Filter the ObjectGraph in-place by removing all edges to nodes that
  69. do not match every filter in the given filter list
  70. Returns a tuple containing the number of:
  71. (nodes_visited, nodes_removed, nodes_orphaned)
  72. """
  73. visited, removes, orphans = filter_stack(self.graph, self, filters)
  74. for last_good, tail in orphans:
  75. self.graph.add_edge(last_good, tail, edge_data="orphan")
  76. for node in removes:
  77. self.graph.hide_node(node)
  78. return len(visited) - 1, len(removes), len(orphans)
  79. def removeNode(self, node):
  80. """
  81. Remove the given node from the graph if it exists
  82. """
  83. ident = self.getIdent(node)
  84. if ident is not None:
  85. self.graph.hide_node(ident)
  86. def removeReference(self, fromnode, tonode):
  87. """
  88. Remove all edges from fromnode to tonode
  89. """
  90. if fromnode is None:
  91. fromnode = self
  92. fromident = self.getIdent(fromnode)
  93. toident = self.getIdent(tonode)
  94. if fromident is not None and toident is not None:
  95. while True:
  96. edge = self.graph.edge_by_node(fromident, toident)
  97. if edge is None:
  98. break
  99. self.graph.hide_edge(edge)
  100. def getIdent(self, node):
  101. """
  102. Get the graph identifier for a node
  103. """
  104. ident = self.getRawIdent(node)
  105. if ident is not None:
  106. return ident
  107. node = self.findNode(node)
  108. if node is None:
  109. return None
  110. return node.graphident
  111. def getRawIdent(self, node):
  112. """
  113. Get the identifier for a node object
  114. """
  115. if node is self:
  116. return node
  117. ident = getattr(node, "graphident", None)
  118. return ident
  119. def __contains__(self, node):
  120. return self.findNode(node) is not None
  121. def findNode(self, node):
  122. """
  123. Find the node on the graph
  124. """
  125. ident = self.getRawIdent(node)
  126. if ident is None:
  127. ident = node
  128. try:
  129. return self.graph.node_data(ident)
  130. except KeyError:
  131. return None
  132. def addNode(self, node):
  133. """
  134. Add a node to the graph referenced by the root
  135. """
  136. self.msg(4, "addNode", node)
  137. try:
  138. self.graph.restore_node(node.graphident)
  139. except GraphError:
  140. self.graph.add_node(node.graphident, node)
  141. def createReference(self, fromnode, tonode, edge_data=None):
  142. """
  143. Create a reference from fromnode to tonode
  144. """
  145. if fromnode is None:
  146. fromnode = self
  147. fromident, toident = self.getIdent(fromnode), self.getIdent(tonode)
  148. if fromident is None or toident is None:
  149. return
  150. self.msg(4, "createReference", fromnode, tonode, edge_data)
  151. self.graph.add_edge(fromident, toident, edge_data=edge_data)
  152. def createNode(self, cls, name, *args, **kw):
  153. """
  154. Add a node of type cls to the graph if it does not already exist
  155. by the given name
  156. """
  157. m = self.findNode(name)
  158. if m is None:
  159. m = cls(name, *args, **kw)
  160. self.addNode(m)
  161. return m
  162. def msg(self, level, s, *args):
  163. """
  164. Print a debug message with the given level
  165. """
  166. if s and level <= self.debug:
  167. print("%s%s %s" % (" " * self.indent, s, " ".join(map(repr, args))))
  168. def msgin(self, level, s, *args):
  169. """
  170. Print a debug message and indent
  171. """
  172. if level <= self.debug:
  173. self.msg(level, s, *args)
  174. self.indent = self.indent + 1
  175. def msgout(self, level, s, *args):
  176. """
  177. Dedent and print a debug message
  178. """
  179. if level <= self.debug:
  180. self.indent = self.indent - 1
  181. self.msg(level, s, *args)