From aab1dc0eac86230f8cfeb15f0644c2664b780637 Mon Sep 17 00:00:00 2001 From: paul <37733348+eckp@users.noreply.github.com> Date: Fri, 11 Mar 2022 13:41:10 +0100 Subject: [PATCH] Made UniqueDotExporter output deterministic Changed from python object ids to a simple counter as DOT node name. This way identical trees will produce identical DOT output, regardless of their location in memory. Doctests now work too, because the output does not vary anymore. --- anytree/exporter/dotexporter.py | 65 ++++++++++++++++++--------------- tests/test_uniquedotexporter.py | 14 +++---- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/anytree/exporter/dotexporter.py b/anytree/exporter/dotexporter.py index dabaffa..3cb759d 100644 --- a/anytree/exporter/dotexporter.py +++ b/anytree/exporter/dotexporter.py @@ -1,4 +1,5 @@ import codecs +import itertools import logging import re from os import path @@ -334,26 +335,26 @@ def __init__(self, node, graph="digraph", name="tree", options=None, >>> s1ca = Node("sub1Ca", parent=s1c) >>> from anytree.exporter import UniqueDotExporter - >>> for line in UniqueDotExporter(root): # doctest: +SKIP + >>> for line in UniqueDotExporter(root): ... print(line) digraph tree { - "0x7f1bf2c9c510" [label="root"]; - "0x7f1bf2c9c5a0" [label="sub0"]; - "0x7f1bf2c9c630" [label="s0"]; - "0x7f1bf2c9c6c0" [label="s0"]; - "0x7f1bf2c9c750" [label="sub1"]; - "0x7f1bf2c9c7e0" [label="s1"]; - "0x7f1bf2c9c870" [label="s1"]; - "0x7f1bf2c9c900" [label="s1"]; - "0x7f1bf2c9c990" [label="sub1Ca"]; - "0x7f1bf2c9c510" -> "0x7f1bf2c9c5a0"; - "0x7f1bf2c9c510" -> "0x7f1bf2c9c750"; - "0x7f1bf2c9c5a0" -> "0x7f1bf2c9c630"; - "0x7f1bf2c9c5a0" -> "0x7f1bf2c9c6c0"; - "0x7f1bf2c9c750" -> "0x7f1bf2c9c7e0"; - "0x7f1bf2c9c750" -> "0x7f1bf2c9c870"; - "0x7f1bf2c9c750" -> "0x7f1bf2c9c900"; - "0x7f1bf2c9c900" -> "0x7f1bf2c9c990"; + "0" [label="root"]; + "1" [label="sub0"]; + "2" [label="s0"]; + "3" [label="s0"]; + "4" [label="sub1"]; + "5" [label="s1"]; + "6" [label="s1"]; + "7" [label="s1"]; + "8" [label="sub1Ca"]; + "0" -> "1"; + "0" -> "4"; + "1" -> "2"; + "1" -> "3"; + "4" -> "5"; + "4" -> "6"; + "4" -> "7"; + "7" -> "8"; } The resulting graph: @@ -369,25 +370,29 @@ def __init__(self, node, graph="digraph", name="tree", options=None, >>> s0a = AnyNode(id="s0", parent=s0) >>> from anytree.exporter import UniqueDotExporter - >>> for line in UniqueDotExporter(root, nodeattrfunc=lambda n: 'label="%s"' % (n.id)): # doctest: +SKIP + >>> for line in UniqueDotExporter(root, nodeattrfunc=lambda n: 'label="%s"' % (n.id)): ... print(line) digraph tree { - "0x7f5c70449af8" [label="root"]; - "0x7f5c70449bd0" [label="sub0"]; - "0x7f5c70449c60" [label="s0"]; - "0x7f5c70449cf0" [label="s0"]; - "0x7f5c70449af8" -> "0x7f5c70449bd0"; - "0x7f5c70449bd0" -> "0x7f5c70449c60"; - "0x7f5c70449bd0" -> "0x7f5c70449cf0"; + "0" [label="root"]; + "1" [label="sub0"]; + "2" [label="s0"]; + "3" [label="s0"]; + "0" -> "1"; + "1" -> "2"; + "1" -> "3"; } """ super(UniqueDotExporter, self).__init__(node, graph=graph, name=name, options=options, indent=indent, nodenamefunc=nodenamefunc, nodeattrfunc=nodeattrfunc, edgeattrfunc=edgeattrfunc, edgetypefunc=edgetypefunc) - - @staticmethod - def _default_nodenamefunc(node): - return hex(id(node)) + self.node_ids = {} + self.node_counter = itertools.count() + + def _default_nodenamefunc(self, node): + node_id = id(node) + if node_id not in self.node_ids: + self.node_ids[node_id] = next(self.node_counter) + return self.node_ids[id(node)] @staticmethod def _default_nodeattrfunc(node): diff --git a/tests/test_uniquedotexporter.py b/tests/test_uniquedotexporter.py index 599536a..05e0a4b 100644 --- a/tests/test_uniquedotexporter.py +++ b/tests/test_uniquedotexporter.py @@ -19,15 +19,11 @@ def test_tree1(): s0 = Node("sub0", parent=root) s0b = Node("sub0B", parent=s0) - id_root = hex(id(root)) - id_s0 = hex(id(s0)) - id_s0b = hex(id(s0b)) - lines = tuple(UniqueDotExporter(root)) eq_(lines, ('digraph tree {', - ' "{id_root}" [label="root"];'.format(id_root=id_root), - ' "{id_s0}" [label="sub0"];'.format(id_s0=id_s0), - ' "{id_s0b}" [label="sub0B"];'.format(id_s0b=id_s0b), - ' "{id_root}" -> "{id_s0}";'.format(id_root=id_root, id_s0=id_s0), - ' "{id_s0}" -> "{id_s0b}";'.format(id_s0=id_s0, id_s0b=id_s0b), + ' "0" [label="root"];', + ' "1" [label="sub0"];', + ' "2" [label="sub0B"];', + ' "0" -> "1";', + ' "1" -> "2";', '}'))