Repository: tox-dev/pipdeptree
Pull request #209 (2023-03-18 02:52:00)

Handle and test mermaid output for a reversed tree
Take 2 at addressing #207. Yes, closing #208 was silly without access to reopen PRs... :facepalm: 


Patch:
diff --git a/src/pipdeptree/__init__.py b/src/pipdeptree/__init__.py
index 34927fc..8074ead 100644
--- a/src/pipdeptree/__init__.py
+++ b/src/pipdeptree/__init__.py
@@ -605,19 +605,31 @@ def render_mermaid(tree) -> str:
     nodes: set[str] = set()
     edges: set[str] = set()
 
-    for pkg, deps in tree.items():
-        pkg_label = f"{pkg.project_name}\\n{pkg.version}"
-        pkg_key = mermaid_id(pkg.key)
-        nodes.add(f'{pkg_key}["{pkg_label}"]')
-        for dep in deps:
-            edge_label = dep.version_spec or "any"
-            dep_key = mermaid_id(dep.key)
-            if dep.is_missing:
-                dep_label = f"{dep.project_name}\\n(missing)"
-                nodes.add(f'{dep_key}["{dep_label}"]:::missing')
-                edges.add(f"{pkg_key} -.-> {dep_key}")
-            else:
-                edges.add(f'{pkg_key} -- "{edge_label}" --> {dep_key}')
+    if isinstance(tree, ReversedPackageDAG):
+        for package, reverse_dependencies in tree.items():
+            package_label = "\\n".join(
+                (package.project_name, "(missing)" if package.is_missing else package.installed_version)
+            )
+            package_key = mermaid_id(package.key)
+            nodes.add(f'{package_key}["{package_label}"]')
+            for reverse_dependency in reverse_dependencies:
+                edge_label = reverse_dependency.req.version_spec or "any"
+                reverse_dependency_key = mermaid_id(reverse_dependency.key)
+                edges.add(f'{package_key} -- "{edge_label}" --> {reverse_dependency_key}')
+    else:
+        for package, dependencies in tree.items():
+            package_label = "\\n".join((package.project_name, package.version))
+            package_key = mermaid_id(package.key)
+            nodes.add(f'{package_key}["{package_label}"]')
+            for dependency in dependencies:
+                edge_label = dependency.version_spec or "any"
+                dependency_key = mermaid_id(dependency.key)
+                if dependency.is_missing:
+                    dependency_label = f"{dependency.project_name}\\n(missing)"
+                    nodes.add(f'{dependency_key}["{dependency_label}"]:::missing')
+                    edges.add(f"{package_key} -.-> {dependency_key}")
+                else:
+                    edges.add(f'{package_key} -- "{edge_label}" --> {dependency_key}')
 
     # Produce the Mermaid Markdown.
     indent = " " * 4


Test patch:
diff --git a/tests/test_pipdeptree.py b/tests/test_pipdeptree.py
index 5b00005..5eb1d58 100644
--- a/tests/test_pipdeptree.py
+++ b/tests/test_pipdeptree.py
@@ -6,7 +6,7 @@ from contextlib import contextmanager
 from itertools import chain
 from pathlib import Path
 from tempfile import NamedTemporaryFile
-from textwrap import dedent
+from textwrap import dedent, indent
 
 try:
     from unittest import mock
@@ -327,33 +327,64 @@ def randomized_dag_copy(t):
 def test_render_mermaid():
     """Check both the sorted and randomized package tree produces the same sorted Mermaid output.
 
+    Rendering a reverse dependency tree should produce the same set of nodes. Edges should have
+    the same version spec label, but be resorted after swapping node positions.
+
     `See how this renders
     <https://mermaid.ink/img/pako:eNp9kcluwjAURX_FeutgeUhCErWs-IN21boL4yFEzYAyqKWIf-9LCISyqFf2ufe-QT6BaayDDHzZfJm9bnvyulU1wWNK3XVb50lVdF1R56Tr2-bTrazu0NfqY0aii1O_K9BK1ZKGlCn4uNAd0h1SQSXlN2qQGqQR5ezObBHbizm6QYfQIWSUi7sSHrGf2i0sR5Yji2lCZWsWQZPViijYPAs69cPnhuwetIiux1qTZubpl5xkwZOgoZgNdl7karhON4nuQRzTf3N2yaW3geaYX2L8cdj8n9xNk3dLegigcm2lC4v_exqdCvq9q5yCDK_WeT2UvQJVn9Gqh755OdYGsr4dXADDwerebQudt7qCzOuyQ3rQ9VvTVFcTPiE7wTdkgkVUSiHjlLOERUkYB3AcMeXrhKUp53GYcJ6KcwA_UwVGo_MvVqym_A?type=png)](https://mermaid.live/edit#pako:eNp9kcluwjAURX_FeutgeUhCErWs-IN21boL4yFEzYAyqKWIf-9LCISyqFf2ufe-QT6BaayDDHzZfJm9bnvyulU1wWNK3XVb50lVdF1R56Tr2-bTrazu0NfqY0aii1O_K9BK1ZKGlCn4uNAd0h1SQSXlN2qQGqQR5ezObBHbizm6QYfQIWSUi7sSHrGf2i0sR5Yji2lCZWsWQZPViijYPAs69cPnhuwetIiux1qTZubpl5xkwZOgoZgNdl7karhON4nuQRzTf3N2yaW3geaYX2L8cdj8n9xNk3dLegigcm2lC4v_exqdCvq9q5yCDK_WeT2UvQJVn9Gqh755OdYGsr4dXADDwerebQudt7qCzOuyQ3rQ9VvTVFcTPiE7wTdkgkVUSiHjlLOERUkYB3AcMeXrhKUp53GYcJ6KcwA_UwVGo_MvVqym_A>`_.
     """
+
+    nodes = dedent(
+        """\
+        flowchart TD
+            classDef missing stroke-dasharray: 5
+            a["a\\n3.4.0"]
+            b["b\\n2.3.1"]
+            c["c\\n5.10.0"]
+            d["d\\n2.35"]
+            e["e\\n0.12.1"]
+            f["f\\n3.1"]
+            g["g\\n6.8.3rc1"]
+        """
+    )
+    dependency_edges = indent(
+        dedent(
+            """\
+            a -- ">=2.0.0" --> b
+            a -- ">=5.7.1" --> c
+            b -- ">=2.30,<2.42" --> d
+            c -- ">=0.12.1" --> e
+            c -- ">=2.30" --> d
+            d -- ">=0.9.0" --> e
+            f -- ">=2.1.0" --> b
+            g -- ">=0.9.0" --> e
+            g -- ">=3.0.0" --> f
+        """
+        ),
+        " " * 4,
+    ).rstrip()
+    reverse_dependency_edges = indent(
+        dedent(
+            """\
+            b -- ">=2.0.0" --> a
+            b -- ">=2.1.0" --> f
+            c -- ">=5.7.1" --> a
+            d -- ">=2.30" --> c
+            d -- ">=2.30,<2.42" --> b
+            e -- ">=0.12.1" --> c
+            e -- ">=0.9.0" --> d
+            e -- ">=0.9.0" --> g
+            f -- ">=3.0.0" --> g
+        """
+        ),
+        " " * 4,
+    ).rstrip()
+
     for package_tree in (t, randomized_dag_copy(t)):
         output = p.render_mermaid(package_tree)
-        assert output == dedent(
-            """\
-            flowchart TD
-                classDef missing stroke-dasharray: 5
-                a["a\\n3.4.0"]
-                b["b\\n2.3.1"]
-                c["c\\n5.10.0"]
-                d["d\\n2.35"]
-                e["e\\n0.12.1"]
-                f["f\\n3.1"]
-                g["g\\n6.8.3rc1"]
-                a -- ">=2.0.0" --> b
-                a -- ">=5.7.1" --> c
-                b -- ">=2.30,<2.42" --> d
-                c -- ">=0.12.1" --> e
-                c -- ">=2.30" --> d
-                d -- ">=0.9.0" --> e
-                f -- ">=2.1.0" --> b
-                g -- ">=0.9.0" --> e
-                g -- ">=3.0.0" --> f
-            """
-        )
+        assert output.rstrip() == nodes + dependency_edges
+        reversed_output = p.render_mermaid(package_tree.reverse())
+        assert reversed_output.rstrip() == nodes + reverse_dependency_edges
 
 
 def test_mermaid_reserved_ids():