diff --git a/hugr-py/src/hugr/hugr/render.py b/hugr-py/src/hugr/hugr/render.py
index 84258b67a..d866af777 100644
--- a/hugr-py/src/hugr/hugr/render.py
+++ b/hugr-py/src/hugr/hugr/render.py
@@ -9,7 +9,7 @@
from typing_extensions import assert_never
from hugr.hugr import Hugr
-from hugr.ops import AsExtOp
+from hugr.ops import AsExtOp, Case
from hugr.tys import CFKind, ConstKind, FunctionKind, Kind, OrderKind, ValueKind
from .node_port import InPort, Node, OutPort
@@ -243,8 +243,22 @@ def _in_order_name(self, n: Node) -> str:
def _out_order_name(self, n: Node) -> str:
return f"{n.idx}:{self._OUTPUT_PREFIX}None"
- def _viz_node(self, node: Node, hugr: Hugr, graph: Digraph) -> None:
- """Render a (possibly nested) node to a graphviz graph."""
+ def _viz_node(
+ self,
+ node: Node,
+ hugr: Hugr,
+ graph: Digraph,
+ *,
+ sibling_order: int | None = None,
+ ) -> None:
+ """Render a (possibly nested) node to a graphviz graph.
+
+ Args:
+ node: The node to render.
+ hugr: The HUGR to render.
+ graph: The graphviz graph to render the node to.
+ sibling_order: The order of the node in the region's sibling list.
+ """
meta = hugr[node].metadata
if len(meta) > 0 and self.config.display_metadata:
if self.config.max_metadata_length is not None:
@@ -278,6 +292,9 @@ def _viz_node(self, node: Node, hugr: Hugr, graph: Digraph) -> None:
op = hugr[node].op
if isinstance(op, AsExtOp) and not self.config.qualify_op_name:
op_name = op.op_def().name
+ elif isinstance(op, Case) and sibling_order is not None:
+ # Indicate the case number
+ op_name = f"{op.name()}[{sibling_order}]"
else:
op_name = op.name()
@@ -311,8 +328,8 @@ def _viz_node(self, node: Node, hugr: Hugr, graph: Digraph) -> None:
if hugr.children(node):
with graph.subgraph(name=f"cluster{node.idx}") as sub:
- for child in hugr.children(node):
- self._viz_node(child, hugr, sub)
+ for sibling_order, child in enumerate(hugr.children(node)):
+ self._viz_node(child, hugr, sub, sibling_order=sibling_order)
html_label = self._format_html_label(**label_config)
sub.node(f"{node.idx}", shape="plain", label=f"<{html_label}>")
sub.attr(
diff --git a/hugr-py/src/hugr/ops.py b/hugr-py/src/hugr/ops.py
index e1accf974..71cfc3a45 100644
--- a/hugr-py/src/hugr/ops.py
+++ b/hugr-py/src/hugr/ops.py
@@ -816,8 +816,18 @@ def __repr__(self) -> str:
def __str__(self) -> str:
if len(self.sum_ty.variant_rows) == 2:
left, right = self.sum_ty.variant_rows
- if len(left) == 0 and self.tag == 1:
- return "Some"
+ if not left and not right:
+ # Boolean
+ if self.tag == 1:
+ return "True"
+ else:
+ return "False"
+ elif not left:
+ # Option
+ if self.tag == 1:
+ return "Some"
+ else:
+ return "None"
elif self.tag == 0:
return "Left"
else:
diff --git a/hugr-py/tests/__snapshots__/test_hugr_build.ambr b/hugr-py/tests/__snapshots__/test_hugr_build.ambr
index 58f017583..9a7d44c7f 100644
--- a/hugr-py/tests/__snapshots__/test_hugr_build.ambr
+++ b/hugr-py/tests/__snapshots__/test_hugr_build.ambr
@@ -2593,7 +2593,7 @@
| Case |
+ COLOR="black">Case[0]
|
@@ -2661,7 +2661,7 @@
| Case |
+ COLOR="black">Case[1]
|
diff --git a/hugr-py/tests/test_ops.py b/hugr-py/tests/test_ops.py
index fd6b8870d..de965a5a8 100644
--- a/hugr-py/tests/test_ops.py
+++ b/hugr-py/tests/test_ops.py
@@ -43,7 +43,10 @@
(DivMod, "arithmetic.int.idivmod_u<5>"),
(MakeTuple(), "MakeTuple"),
(UnpackTuple(), "UnpackTuple"),
- (Tag(0, Bool), "Left"),
+ (Tag(0, Bool), "False"),
+ (Tag(1, Bool), "True"),
+ (Tag(0, tys.Option(Bool)), "None"),
+ (Tag(1, tys.Option(Bool)), "Some"),
(Tag(0, tys.Sum([[Bool, Bool, Bool]])), "Tag(0)"),
(CFG([]), "CFG"),
(DFG([]), "DFG"),
diff --git a/pyproject.toml b/pyproject.toml
index 6d082eb6f..9b2ae7df1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,13 +6,14 @@ members = ["hugr-py"]
dev-dependencies = [
"pre-commit >=3.6.2,<4",
- "pytest >=8.1.1,<9",
- "pytest-cov >=5.0.0,<6",
+ "pytest ~=9.0",
+ "pytest-cov ~=7.0",
+ "pytest-xdist ~= 3.8",
"maturin >=1.7.0,<2",
"mypy >=1.9.0,<2",
"ruff >=0.6.2,<0.7",
"toml >=0.10.0,<0.11",
- "syrupy >=4.7.1,<5",
+ "syrupy ~=5.1",
"types-zstd >= 1.5.6.6",
"pytket >= 1.34.0",
]
@@ -22,8 +23,6 @@ addopts = "--doctest-modules --ignore=test_files"
filterwarnings = "ignore::DeprecationWarning:lark.*"
-
-
[tool.mypy]
# TODO: Fix lints and enable this
#strict = true
diff --git a/uv.lock b/uv.lock
index 88323a0e3..5878a571e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -16,11 +16,12 @@ dev = [
{ name = "maturin", specifier = ">=1.7.0,<2" },
{ name = "mypy", specifier = ">=1.9.0,<2" },
{ name = "pre-commit", specifier = ">=3.6.2,<4" },
- { name = "pytest", specifier = ">=8.1.1,<9" },
- { name = "pytest-cov", specifier = ">=5.0.0,<6" },
+ { name = "pytest", specifier = "~=9.0" },
+ { name = "pytest-cov", specifier = "~=7.0" },
+ { name = "pytest-xdist", specifier = "~=3.8" },
{ name = "pytket", specifier = ">=1.34.0" },
{ name = "ruff", specifier = ">=0.6.2,<0.7" },
- { name = "syrupy", specifier = ">=4.7.1,<5" },
+ { name = "syrupy", specifier = "~=5.1" },
{ name = "toml", specifier = ">=0.10.0,<0.11" },
{ name = "types-zstd", specifier = ">=1.5.6.6" },
]
@@ -327,6 +328,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
+[[package]]
+name = "execnet"
+version = "2.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
+]
+
[[package]]
name = "filelock"
version = "3.20.3"
@@ -1112,7 +1122,7 @@ wheels = [
[[package]]
name = "pytest"
-version = "8.4.2"
+version = "9.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -1123,22 +1133,36 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
[[package]]
name = "pytest-cov"
-version = "5.0.0"
+version = "7.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage", extra = ["toml"] },
+ { name = "pluggy" },
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
+]
+
+[[package]]
+name = "pytest-xdist"
+version = "3.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "execnet" },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" },
]
[[package]]
@@ -1640,14 +1664,14 @@ wheels = [
[[package]]
name = "syrupy"
-version = "4.9.1"
+version = "5.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/8c/f8/022d8704a3314f3e96dbd6bbd16ebe119ce30e35f41aabfa92345652fceb/syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4", size = 52492, upload-time = "2025-03-24T01:36:37.225Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/b0/24bca682d6a6337854be37f242d116cceeda9942571d5804c44bc1bdd427/syrupy-5.1.0.tar.gz", hash = "sha256:df543c7aa50d3cf1246e83d58fe490afe5f7dab7b41e74ecc0d8d23ae19bd4b8", size = 50495, upload-time = "2026-01-25T14:53:06.2Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/9d/aef9ec5fd5a4ee2f6a96032c4eda5888c5c7cec65cef6b28c4fc37671d88/syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda", size = 52214, upload-time = "2025-03-24T01:36:35.278Z" },
+ { url = "https://files.pythonhosted.org/packages/de/70/cf880c3b95a6034ef673e74b369941b42315c01f1554a5637a4f8b911009/syrupy-5.1.0-py3-none-any.whl", hash = "sha256:95162d2b05e61ed3e13f117b88dfab7c58bd6f90e66ebbf918e8a77114ad51c5", size = 51658, upload-time = "2026-01-25T14:53:05.105Z" },
]
[[package]]