From 649e294f365f5540293912a5ae782853f000caa0 Mon Sep 17 00:00:00 2001 From: Rust Saiargaliev Date: Mon, 8 Nov 2021 21:18:45 +0200 Subject: [PATCH] Fix #36 -- switch to graphviz 0.18 (#37) * Fix #36 -- switch to graphviz 0.18 Use staticmethods instead of "%" operator. This reflects https://github.com/xflr6/graphviz/commit/17df6131746d3549eea08c6a7f5934698cf23f8a * Use quoting instead of lang Reflects https://github.com/xflr6/graphviz/commit/673261d70e20dbdb13b145d30261c6735468ce26 * Use new `encoding` argument instead of manual decoding https://github.com/xflr6/graphviz/blob/master/CHANGES.rst#version-018 >Add optional keyword-only encoding argument to pipe(). Returns the decoded stdout from the rendering process (e.g. format='svg'). Delegates encoding/decoding to subprocess in the common case (input and output encoding are the same, e.g. default encoding='utf-8'). Used by the Jupyter notebook integration. * Adjust tests to handle newlines https://github.com/xflr6/graphviz/blob/master/CHANGES.rst#version-018 >Change of undocumented behaviour: When iterating over a Graph, Digraph, or Source instance, the yielded lines now include a final newline ('\n'). This mimics iteration over file object lines in text mode. * Pin graphviz>=0.18 * Blackify * Fix one more test --- joeflow/models.py | 4 ++-- joeflow/utils.py | 16 +++++++------- setup.cfg | 2 +- tests/test_models.py | 16 ++++++++------ tests/test_utils.py | 52 ++++++++++++++++++++++---------------------- 5 files changed, 46 insertions(+), 44 deletions(-) diff --git a/joeflow/models.py b/joeflow/models.py index 266091f..0ec7fea 100644 --- a/joeflow/models.py +++ b/joeflow/models.py @@ -236,7 +236,7 @@ def get_graph_svg(cls): """ graph = cls.get_graph() graph.format = "svg" - return SafeString(graph.pipe().decode("utf-8")) # nosec + return SafeString(graph.pipe(encoding="utf-8")) # nosec get_graph_svg.short_description = t("graph") @@ -326,7 +326,7 @@ def get_instance_graph_svg(self, output_format="svg"): """ graph = self.get_instance_graph() graph.format = output_format - return SafeString(graph.pipe().decode("utf-8")) # nosec + return SafeString(graph.pipe(encoding="utf-8")) # nosec get_instance_graph_svg.short_description = t("instance graph") diff --git a/joeflow/utils.py b/joeflow/utils.py index e7105e8..71dc775 100644 --- a/joeflow/utils.py +++ b/joeflow/utils.py @@ -44,7 +44,7 @@ def __init__(self, *args, **kwargs): def __iter__(self, subgraph=False): """Yield the DOT source code line by line (as graph or subgraph).""" if self.comment: - yield self._comment % self.comment + yield self._comment(self.comment) if subgraph: if self.strict: @@ -52,12 +52,12 @@ def __iter__(self, subgraph=False): head = self._subgraph if self.name else self._subgraph_plain else: head = self._head_strict if self.strict else self._head - yield head % (self._quote(self.name) + " " if self.name else "") + yield head(self._quote(self.name) + " " if self.name else "") for kw in ("graph", "node", "edge"): attrs = getattr(self, "%s_attr" % kw) if attrs: - yield self._attr % (kw, self._attr_list(None, attrs)) + yield self._attr(kw, self._attr_list(None, attrs)) yield from self.body @@ -66,16 +66,16 @@ def __iter__(self, subgraph=False): label = attrs.pop("label", None) _attributes = attrs.pop("_attributes", None) attr_list = self._attr_list(label, attrs, _attributes) - yield self._node % (name, attr_list) + yield self._node(name, attr_list) for edge, attrs in sorted(self._edges.items()): - head_name, tail_name = edge + tail_name, head_name = edge tail_name = self._quote_edge(tail_name) head_name = self._quote_edge(head_name) label = attrs.pop("label", None) _attributes = attrs.pop("_attributes", None) attr_list = self._attr_list(label, attrs, _attributes) - yield self._edge % (head_name, tail_name, attr_list) + yield self._edge(tail=tail_name, head=head_name, attr=attr_list) yield self._tail @@ -89,10 +89,10 @@ def edge(self, tail_name, head_name, **attrs): def _quote(identifier, *args, **kwargs): """Remove underscores from labels.""" identifier = identifier.replace("_", " ") - return gv.lang.quote(identifier, *args, **kwargs) + return gv.quoting.quote(identifier, *args, **kwargs) @staticmethod def _quote_edge(identifier): """Remove underscores from labels.""" identifier = identifier.replace("_", " ") - return gv.lang.quote_edge(identifier) + return gv.quoting.quote_edge(identifier) diff --git a/setup.cfg b/setup.cfg index 5096bf1..346241b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ packages = joeflow install_requires = django>=2.2 django-appconf - graphviz + graphviz>=0.18 setup_requires = setuptools_scm sphinx diff --git a/tests/test_models.py b/tests/test_models.py index 4ecad08..ee94e7c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -109,7 +109,7 @@ def test_get_graph(self, fixturedir): with open(str(fixturedir / "simpleworkflow.dot")) as fp: expected_graph = fp.read().splitlines() print(str(graph)) - assert set(graph) == set(expected_graph) + assert set(str(graph).splitlines()) == set(expected_graph) def test_get_graph_svg(self, fixturedir): svg = workflows.SimpleWorkflow.get_graph_svg() @@ -121,7 +121,9 @@ def test_get_instance_graph(self, db, fixturedir): graph = wf.get_instance_graph() print(str(graph)) with open(str(fixturedir / "simpleworkflow_instance.dot")) as fp: - assert set(graph) == set(fp.read().replace("{url}", task_url).splitlines()) + assert set(str(graph).splitlines()) == set( + fp.read().replace("{url}", task_url).splitlines() + ) def test_get_instance_graph__override( self, db, stub_worker, fixturedir, admin_client @@ -136,14 +138,14 @@ def test_get_instance_graph__override( print(str(graph)) assert ( - f'\t"{task.name} {task.pk}" [peripheries=1 style="filled, rounded, dashed"]' + f'\t"{task.name} {task.pk}" [peripheries=1 style="filled, rounded, dashed"]\n' in list(graph) ) assert ( - f'\t"save the princess" -> "{task.name} {task.pk}" [style=dashed]' + f'\t"save the princess" -> "{task.name} {task.pk}" [style=dashed]\n' in list(graph) ) - assert f'\t"{task.name} {task.pk}" -> end [style=dashed]' in list(graph) + assert f'\t"{task.name} {task.pk}" -> end [style=dashed]\n' in list(graph) def test_get_instance_graph__obsolete(self, db, fixturedir, admin_client): workflow = workflows.SimpleWorkflow.objects.create() @@ -159,8 +161,8 @@ def test_get_instance_graph__obsolete(self, db, fixturedir, admin_client): '\tobsolete [color=black fontcolor=black peripheries=1 style="filled, dashed, bold"]' in str(graph) ) - assert '\t"start method" -> obsolete [style=dashed]' in list(graph) - assert "\tobsolete -> end [style=dashed]" in list(graph) + assert '\t"start method" -> obsolete [style=dashed]\n' in list(graph) + assert "\tobsolete -> end [style=dashed]\n" in list(graph) def test_get_instance_graph_svg(self, db, fixturedir): wf = workflows.SimpleWorkflow.start_method() diff --git a/tests/test_utils.py b/tests/test_utils.py index 2e93756..d1a70e8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,30 +6,30 @@ def test_node(self): graph = NoDashDiGraph() graph.node("foo", color="blue") assert list(graph) == [ - "digraph {", - "\tfoo [color=blue]", - "}", + "digraph {\n", + "\tfoo [color=blue]\n", + "}\n", ] graph.node("foo", color="red") assert list(graph) == [ - "digraph {", - "\tfoo [color=red]", - "}", + "digraph {\n", + "\tfoo [color=red]\n", + "}\n", ] def test_edge(self): graph = NoDashDiGraph() graph.edge("foo", "bar", color="blue") assert list(graph) == [ - "digraph {", - "\tfoo -> bar [color=blue]", - "}", + "digraph {\n", + "\tfoo -> bar [color=blue]\n", + "}\n", ] graph.edge("foo", "bar", color="red") assert list(graph) == [ - "digraph {", - "\tfoo -> bar [color=red]", - "}", + "digraph {\n", + "\tfoo -> bar [color=red]\n", + "}\n", ] def test_iter(self): @@ -40,13 +40,13 @@ def test_iter(self): graph.comment = "This is a comment." print(str(graph)) assert list(graph.__iter__()) == [ - "// This is a comment.", - "digraph {", - "\tnode [style=filled]", - "\tbar [color=green]", - "\tfoo [color=red]", - "\tfoo -> bar [color=blue]", - "}", + "// This is a comment.\n", + "digraph {\n", + "\tnode [style=filled]\n", + "\tbar [color=green]\n", + "\tfoo [color=red]\n", + "\tfoo -> bar [color=blue]\n", + "}\n", ] def test_iter__subgraph(self): @@ -57,13 +57,13 @@ def test_iter__subgraph(self): graph.comment = "This is a comment." print(str(graph)) assert list(graph.__iter__(subgraph=True)) == [ - "// This is a comment.", - "{", - "\tnode [style=filled]", - "\tbar [color=green]", - "\tfoo [color=red]", - "\tfoo -> bar [color=blue]", - "}", + "// This is a comment.\n", + "{\n", + "\tnode [style=filled]\n", + "\tbar [color=green]\n", + "\tfoo [color=red]\n", + "\tfoo -> bar [color=blue]\n", + "}\n", ] def test_quote(self):