From 3d38550453966c0f98f5d2e0ece3fc9e6fec360e Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Tue, 16 Jun 2026 23:29:33 +0200 Subject: [PATCH 1/3] Testenv. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ca20b55..4e2a990 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,6 +134,7 @@ commands = codespell {posargs: --quiet-level=2} src/ python3 -m bandit -r src/reflow --severity-level medium --confidence-level high [testenv:test] +extras = pretty setenv = VIRTUALENV_SYSTEM_SITE_PACKAGES=true description = "Run the unit tests." From 5d1bf5908a0a9b07edbd205f267a3eff62e5d215 Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Tue, 16 Jun 2026 23:50:47 +0200 Subject: [PATCH 2/3] Make phart more robust. --- src/reflow/_dag_render.py | 35 ++++++++++++++++++----------------- tests/test_cli.py | 6 +++--- tests/test_dag_render.py | 12 ++++++++---- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/reflow/_dag_render.py b/src/reflow/_dag_render.py index bc1d747..55a6c95 100644 --- a/src/reflow/_dag_render.py +++ b/src/reflow/_dag_render.py @@ -91,32 +91,33 @@ def render_dot(wf: Workflow) -> str: return "\n".join(lines) -def render_phart(wf: Workflow, *, use_ascii: bool = False) -> str: +def render_phart(wf: "Workflow", *, use_ascii: bool = False) -> str: """Pretty ASCII/Unicode DAG via the optional phart + networkx extra. - Array tasks are decorated with angle brackets ``<>``; singletons - with square brackets ``[name]``. Raises ImportError if the extra is - not installed; the caller should catch this and fall back to text. + Array tasks are suffixed ``[array]`` in their label so they stand out + from singleton tasks. Raises ImportError if the extra is not installed; + the caller should catch this and fall back to text. + + The marker is baked into the node label rather than using phart's + ``custom_decorators``/``NodeStyle.CUSTOM``, because those are only + available in newer phart releases and their behaviour varies across + versions. A plain label suffix renders consistently everywhere. """ import networkx as nx # noqa: PLC0415 - optional dependency - from phart import ASCIIRenderer, NodeStyle # noqa: PLC0415 + from phart import ASCIIRenderer # noqa: PLC0415 array = _array_tasks(wf) + + def label(name: str) -> str: + return f"{name} [array]" if name in array else name + g = nx.DiGraph() # Add all nodes so isolated tasks still render. for tname in wf._topological_order(): - g.add_node(tname) - g.add_edges_from(_edges(wf)) - - decorators = { - name: (("<<", ">>") if name in array else ("[", "]")) for name in g.nodes - } - renderer = ASCIIRenderer( - g, - node_style=NodeStyle.CUSTOM, - custom_decorators=decorators, - use_ascii=use_ascii, - ) + g.add_node(label(tname)) + g.add_edges_from((label(dep), label(t)) for dep, t in _edges(wf)) + + renderer = ASCIIRenderer(g, use_ascii=use_ascii) result: str = renderer.render() return result.rstrip("\n") diff --git a/tests/test_cli.py b/tests/test_cli.py index 216f38c..72c5cc1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -891,9 +891,9 @@ def test_dag_format_phart_when_available( "convert_source", ): assert name in out - # Array tasks are decorated with angle brackets, singletons with [] - assert "<>" in out - assert "[gather_sources]" in out + # Array tasks carry the [array] suffix; singletons do not. + assert "download_source [array]" in out + assert "gather_sources [array]" not in out def test_dag_format_phart_ascii_flag( self, capsys: pytest.CaptureFixture[str] diff --git a/tests/test_dag_render.py b/tests/test_dag_render.py index 473df04..c7c76df 100644 --- a/tests/test_dag_render.py +++ b/tests/test_dag_render.py @@ -71,7 +71,7 @@ def test_render_phart_via_dispatcher(self) -> None: pytest.importorskip("networkx") out = _dag_render.render(_make_wf(), "phart") assert "gather_sources" in out - assert "<>" in out + assert "download_source [array]" in out def test_render_phart_via_dispatcher_ascii(self) -> None: pytest.importorskip("phart") @@ -169,12 +169,16 @@ def test_renders_all_nodes(self) -> None: ): assert name in out - def test_array_and_singleton_decorators(self) -> None: + def test_array_and_singleton_labels(self) -> None: pytest.importorskip("phart") pytest.importorskip("networkx") out = _dag_render.render_phart(_make_wf()) - assert "<>" in out - assert "[gather_sources]" in out + # Array tasks carry the [array] suffix; singletons do not. + assert "download_source [array]" in out + assert "convert_source [array]" in out + # A singleton name appears without the suffix + assert "gather_sources" in out + assert "gather_sources [array]" not in out def test_ascii_mode_no_unicode(self) -> None: pytest.importorskip("phart") From 888263b243473de022a74331613a96f24f263990 Mon Sep 17 00:00:00 2001 From: antarcticrainforest Date: Tue, 16 Jun 2026 23:52:56 +0200 Subject: [PATCH 3/3] Fix linter. --- src/reflow/_dag_render.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/reflow/_dag_render.py b/src/reflow/_dag_render.py index 55a6c95..ac18b1d 100644 --- a/src/reflow/_dag_render.py +++ b/src/reflow/_dag_render.py @@ -19,10 +19,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from .workflow import Workflow +from .workflow import Workflow FORMATS = ("text", "mermaid", "dot", "phart") @@ -91,7 +88,7 @@ def render_dot(wf: Workflow) -> str: return "\n".join(lines) -def render_phart(wf: "Workflow", *, use_ascii: bool = False) -> str: +def render_phart(wf: Workflow, *, use_ascii: bool = False) -> str: """Pretty ASCII/Unicode DAG via the optional phart + networkx extra. Array tasks are suffixed ``[array]`` in their label so they stand out