| 269 | return sorted(leaves) |
| 270 | |
| 271 | def ensure_not_cyclic(self): |
| 272 | # Algo from GvR: |
| 273 | # https://neopythonic.blogspot.com/2009/01/detecting-cycles-in-directed-graph.html |
| 274 | todo = set(self.nodes) |
| 275 | while todo: |
| 276 | node = todo.pop() |
| 277 | stack = [node] |
| 278 | while stack: |
| 279 | top = stack[-1] |
| 280 | for child in self.node_map[top].children: |
| 281 | # Use child.key instead of child to speed up the frequent |
| 282 | # hashing. |
| 283 | node = child.key |
| 284 | if node in stack: |
| 285 | cycle = stack[stack.index(node) :] |
| 286 | raise CircularDependencyError( |
| 287 | ", ".join("%s.%s" % n for n in cycle) |
| 288 | ) |
| 289 | if node in todo: |
| 290 | stack.append(node) |
| 291 | todo.remove(node) |
| 292 | break |
| 293 | else: |
| 294 | node = stack.pop() |
| 295 | |
| 296 | def __str__(self): |
| 297 | return "Graph: %s nodes, %s edges" % self._nodes_and_edges() |