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 1/6] 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";', '}')) From b3e20fe93299ffc5d377986b6cb4264f422a7ff6 Mon Sep 17 00:00:00 2001 From: xu <44153643+xu-kai-xu@users.noreply.github.com> Date: Tue, 17 May 2022 11:09:49 +0800 Subject: [PATCH 2/6] docs problem in ZigZagGroupIter forget to import ZigZagGroupIter module --- anytree/iterators/zigzaggroupiter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anytree/iterators/zigzaggroupiter.py b/anytree/iterators/zigzaggroupiter.py index a9ce59b..8cdd57c 100644 --- a/anytree/iterators/zigzaggroupiter.py +++ b/anytree/iterators/zigzaggroupiter.py @@ -11,7 +11,7 @@ class ZigZagGroupIter(AbstractIter): (children of `node`) in reversed order. The next level contains the children of the children in forward order, and so on. - >>> from anytree import Node, RenderTree, AsciiStyle + >>> from anytree import Node, RenderTree, AsciiStyle, ZigZagGroupIter >>> f = Node("f") >>> b = Node("b", parent=f) >>> a = Node("a", parent=b) From c8f0dea78492da3db1e683088dfb1205c5f2f796 Mon Sep 17 00:00:00 2001 From: c0fec0de Date: Wed, 11 Oct 2023 01:48:05 +0200 Subject: [PATCH 3/6] fix version reference --- docs/tricks/consistencychecks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tricks/consistencychecks.rst b/docs/tricks/consistencychecks.rst index 692650f..2a4e85d 100644 --- a/docs/tricks/consistencychecks.rst +++ b/docs/tricks/consistencychecks.rst @@ -2,7 +2,7 @@ Consistency Checks vs. Speed ============================ Anytree can run some *costly* internal consistency checks. -With version 2.9.2 these got disabled by default. +With version 2.9.1 these got disabled by default. In case of any concerns about the internal data consistency or just for safety, either * set the environment variable ``ANYTREE_ASSERTIONS=1``, or From 518174e6689bd2cabb58bc6e2b9fcc31bdeec2e3 Mon Sep 17 00:00:00 2001 From: c0fec0de Date: Wed, 11 Oct 2023 02:02:59 +0200 Subject: [PATCH 4/6] fix #209, improve doc (#229) --- anytree/util/__init__.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/anytree/util/__init__.py b/anytree/util/__init__.py index 940a361..65c6e82 100644 --- a/anytree/util/__init__.py +++ b/anytree/util/__init__.py @@ -5,7 +5,7 @@ def commonancestors(*nodes): """ Determine common ancestors of `nodes`. - >>> from anytree import Node + >>> from anytree import Node, util >>> udo = Node("Udo") >>> marc = Node("Marc", parent=udo) >>> lian = Node("Lian", parent=marc) @@ -14,13 +14,13 @@ def commonancestors(*nodes): >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) - >>> commonancestors(jet, joe) + >>> util.commonancestors(jet, joe) (Node('/Udo'), Node('/Udo/Dan')) - >>> commonancestors(jet, marc) + >>> util.commonancestors(jet, marc) (Node('/Udo'),) - >>> commonancestors(jet) + >>> util.commonancestors(jet) (Node('/Udo'), Node('/Udo/Dan')) - >>> commonancestors() + >>> util.commonancestors() () """ ancestors = [node.ancestors for node in nodes] @@ -38,16 +38,18 @@ def leftsibling(node): """ Return Left Sibling of `node`. - >>> from anytree import Node + >>> from anytree import Node, util >>> dan = Node("Dan") >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) - >>> leftsibling(dan) - >>> leftsibling(jet) - >>> leftsibling(jan) + >>> print(util.leftsibling(dan)) + None + >>> print(util.leftsibling(jet)) + None + >>> print(util.leftsibling(jan)) Node('/Dan/Jet') - >>> leftsibling(joe) + >>> print(util.leftsibling(joe)) Node('/Dan/Jan') """ if node.parent: @@ -62,17 +64,19 @@ def rightsibling(node): """ Return Right Sibling of `node`. - >>> from anytree import Node + >>> from anytree import Node, util >>> dan = Node("Dan") >>> jet = Node("Jet", parent=dan) >>> jan = Node("Jan", parent=dan) >>> joe = Node("Joe", parent=dan) - >>> rightsibling(dan) - >>> rightsibling(jet) + >>> print(util.rightsibling(dan)) + None + >>> print(util.rightsibling(jet)) Node('/Dan/Jan') - >>> rightsibling(jan) + >>> print(util.rightsibling(jan)) Node('/Dan/Joe') - >>> rightsibling(joe) + >>> print(util.rightsibling(joe)) + None """ if node.parent: pchildren = node.parent.children From 351aab5080db4ad70ba1d05b617fe67c83994e21 Mon Sep 17 00:00:00 2001 From: c0fec0de Date: Wed, 11 Oct 2023 02:07:00 +0200 Subject: [PATCH 5/6] fix #213, remove obsolete OrderedDict reference --- anytree/exporter/dictexporter.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/anytree/exporter/dictexporter.py b/anytree/exporter/dictexporter.py index 84fc06e..640110f 100644 --- a/anytree/exporter/dictexporter.py +++ b/anytree/exporter/dictexporter.py @@ -23,27 +23,12 @@ class DictExporter: >>> s1 = AnyNode(a="sub1", parent=root) >>> exporter = DictExporter() - >>> pprint(exporter.export(root)) # order within dictionary might vary! + >>> pprint(exporter.export(root)) {'a': 'root', 'children': [{'a': 'sub0', 'children': [{'a': 'sub0A', 'b': 'foo'}, {'a': 'sub0B'}]}, {'a': 'sub1'}]} - Pythons dictionary `dict` does not preserve order. - :any:`collections.OrderedDict` does. - In this case attributes can be ordered via `attriter`. - - >>> from collections import OrderedDict - >>> exporter = DictExporter(dictcls=OrderedDict, attriter=sorted) - >>> pprint(exporter.export(root)) - OrderedDict([('a', 'root'), - ('children', - [OrderedDict([('a', 'sub0'), - ('children', - [OrderedDict([('a', 'sub0A'), ('b', 'foo')]), - OrderedDict([('a', 'sub0B')])])]), - OrderedDict([('a', 'sub1')])])]) - The attribute iterator `attriter` may be used for filtering too. For example, just dump attributes named `a`: From 0a06fe7bb922f940f8c19e86b1e9108643299aae Mon Sep 17 00:00:00 2001 From: c0fec0de Date: Wed, 11 Oct 2023 02:19:06 +0200 Subject: [PATCH 6/6] #194, use hex numbers again --- anytree/exporter/dotexporter.py | 50 ++++++++++++++++----------------- tests/test_uniquedotexporter.py | 10 +++---- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/anytree/exporter/dotexporter.py b/anytree/exporter/dotexporter.py index 88ab8d3..b8e0554 100644 --- a/anytree/exporter/dotexporter.py +++ b/anytree/exporter/dotexporter.py @@ -365,23 +365,23 @@ def __init__( >>> for line in UniqueDotExporter(root): ... print(line) digraph tree { - "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"; + "0x0" [label="root"]; + "0x1" [label="sub0"]; + "0x2" [label="s0"]; + "0x3" [label="s0"]; + "0x4" [label="sub1"]; + "0x5" [label="s1"]; + "0x6" [label="s1"]; + "0x7" [label="s1"]; + "0x8" [label="sub1Ca"]; + "0x0" -> "0x1"; + "0x0" -> "0x4"; + "0x1" -> "0x2"; + "0x1" -> "0x3"; + "0x4" -> "0x5"; + "0x4" -> "0x6"; + "0x4" -> "0x7"; + "0x7" -> "0x8"; } The resulting graph: @@ -400,13 +400,13 @@ def __init__( >>> for line in UniqueDotExporter(root, nodeattrfunc=lambda n: 'label="%s"' % (n.id)): ... print(line) digraph tree { - "0" [label="root"]; - "1" [label="sub0"]; - "2" [label="s0"]; - "3" [label="s0"]; - "0" -> "1"; - "1" -> "2"; - "1" -> "3"; + "0x0" [label="root"]; + "0x1" [label="sub0"]; + "0x2" [label="s0"]; + "0x3" [label="s0"]; + "0x0" -> "0x1"; + "0x1" -> "0x2"; + "0x1" -> "0x3"; } """ super(UniqueDotExporter, self).__init__( @@ -428,7 +428,7 @@ 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)] + return hex(self.node_ids[id(node)]) @staticmethod def _default_nodeattrfunc(node): diff --git a/tests/test_uniquedotexporter.py b/tests/test_uniquedotexporter.py index d720d82..bd180ef 100644 --- a/tests/test_uniquedotexporter.py +++ b/tests/test_uniquedotexporter.py @@ -21,11 +21,11 @@ def test_tree1(): lines, ( "digraph tree {", - ' "0" [label="root"];', - ' "1" [label="sub0"];', - ' "2" [label="sub0B"];', - ' "0" -> "1";', - ' "1" -> "2";', + ' "0x0" [label="root"];', + ' "0x1" [label="sub0"];', + ' "0x2" [label="sub0B"];', + ' "0x0" -> "0x1";', + ' "0x1" -> "0x2";', "}", ), )