From 831dcfce813896a66ea7c44ddbe3ad3fcd6cfd79 Mon Sep 17 00:00:00 2001 From: appleweiping Date: Thu, 3 Sep 2026 11:52:05 -0500 Subject: [PATCH 1/2] Constrain prettyprinttree so PortTree.print() works again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PortTree.print()` passes `default_orientation` to `PrettyPrintTree` (port_utils.py:483, set when either `default_opts` or `savetofile` is used). PrettyPrintTree removed that argument in 2.0 and takes no `**kwargs`, so with the current unconstrained requirement a fresh install resolves 2.0.2 and the call raises: TypeError: PrettyPrintTree.__init__() got an unexpected keyword argument 'default_orientation' Pinning `<2` alone is not enough. 1.36.4 keeps the argument but does not constrain its own `cmd2` dependency; cmd2 4.x moved `cmd2.ansi`, so `from PrettyPrint import PrettyPrintTree` fails, `port_utils` falls through to its no-op shim, and `print()` silently returns nothing instead of raising — worse than the current loud failure. `cmd2<3` is what actually restores output. Verified in clean virtualenvs: - unconstrained -> PrettyPrintTree 2.0.2, TypeError as above - `<2` only -> 1.36.4 + cmd2 4.2.3, ImportError -> silent no-op shim - `<2` and `cmd2<3` -> 1.36.4 + cmd2 2.7.0, renders a tree as intended Also drops `prettyprint`, an unrelated and unmaintained 2012 package that nothing in the codebase imports. It is harmless but misleading next to `prettyprinttree`, which is the one `port_utils` actually uses. Co-Authored-By: Claude Opus 5 --- setup.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c927da31..caf211cb 100644 --- a/setup.py +++ b/setup.py @@ -31,8 +31,14 @@ # `ModuleNotFoundError: No module named 'docopt'` and the deck # never runs. "docopt", - "prettyprint", - "prettyprinttree", + # `PortTree.print()` passes `default_orientation`, which PrettyPrintTree + # removed in 2.0, so an unconstrained install raises TypeError. 1.36.x + # keeps the argument but does not constrain its own `cmd2` dependency, + # and cmd2 4.x moved `cmd2.ansi`, which makes `PrettyPrint` unimportable + # — `port_utils` then silently falls back to its no-op shim. Both bounds + # are needed for the printer to actually produce output. + "prettyprinttree>=1.36.4,<2", + "cmd2<3", "gdstk", "svgutils", "nltk", From f092508ce12a025a87009ac87c059fcb1f39f347 Mon Sep 17 00:00:00 2001 From: appleweiping Date: Mon, 7 Sep 2026 10:47:58 -0500 Subject: [PATCH 2/2] test: cover PortTree output regression --- .github/workflows/tests.yml | 2 +- tests/test_port_tree.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/test_port_tree.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0dc2f1a2..ff1e43e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: # only. Keeping the environment small is what makes this job fast # enough to run on every push. - name: Install test dependencies - run: pip install pytest gdstk numpy pandas pydantic docopt + run: pip install pytest gdstk numpy pandas pydantic docopt 'prettyprinttree>=1.36.4,<2' 'cmd2<3' # The heavier DRC/LVS workflows run the gdsfactory backend (Python # 3.10, gdsfactory 7.7), so gdstk currently has no CI coverage at all. diff --git a/tests/test_port_tree.py b/tests/test_port_tree.py new file mode 100644 index 00000000..73c56ce4 --- /dev/null +++ b/tests/test_port_tree.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory + + +class PortTreeTests(unittest.TestCase): + def test_print_writes_the_port_hierarchy(self) -> None: + from glayout.backend import Component + from glayout.util.port_utils import PortTree + + component = Component("demo") + component.add_port( + name="device_gate_N", + center=(0, 0), + width=1, + orientation=90, + layer=(1, 0), + ) + + with TemporaryDirectory() as directory: + output = Path(directory) / "ports.txt" + PortTree(component).print(savetofile=True, outfile_name=str(output)) + + self.assertTrue(output.is_file()) + text = output.read_text() + self.assertIn("demo", text) + self.assertIn("device", text) + self.assertIn("gate", text) + self.assertIn("N", text) + + +if __name__ == "__main__": + unittest.main()