From 4420bbe114cc7f8c170462d75a07e081cd62527b Mon Sep 17 00:00:00 2001 From: Julian Geiger Date: Tue, 19 Nov 2024 11:55:11 +0100 Subject: [PATCH 1/4] Hide metadata inputs for tasks in HTML visualization. --- .gitignore | 1 + aiida_workgraph/widget/src/widget/__init__.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9508c6bc..3aa9dd8a 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,4 @@ dmypy.json tests/work /tests/**/*.png /tests/**/*txt +.vscode/ diff --git a/aiida_workgraph/widget/src/widget/__init__.py b/aiida_workgraph/widget/src/widget/__init__.py index 65a1405b..01a6a550 100644 --- a/aiida_workgraph/widget/src/widget/__init__.py +++ b/aiida_workgraph/widget/src/widget/__init__.py @@ -37,7 +37,7 @@ def from_workgraph(self, workgraph: Any) -> None: wgdata = workgraph_to_short_json(wgdata) self.value = wgdata - def from_node(self, node: Any) -> None: + def from_node(self, node: Any, show_metadata: bool = False) -> None: tdata = node.to_dict() tdata.pop("properties", None) tdata.pop("executor", None) @@ -46,6 +46,14 @@ def from_node(self, node: Any) -> None: tdata["label"] = tdata["identifier"] for input in tdata["inputs"].values(): input.pop("property") + + if not show_metadata: + inputs = {} + for input_k, input_v in tdata["inputs"].items(): + if not input_k.startswith("metadata"): + inputs[input_k] = input_v + tdata["inputs"] = inputs + tdata["inputs"] = list(tdata["inputs"].values()) tdata["outputs"] = list(tdata["outputs"].values()) wgdata = {"name": node.name, "nodes": {node.name: tdata}, "links": []} From 479672d69811c99cbf30f4e22b3172ecc69fb720 Mon Sep 17 00:00:00 2001 From: Julian Geiger Date: Tue, 19 Nov 2024 18:45:07 +0100 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Xing Wang --- aiida_workgraph/widget/src/widget/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida_workgraph/widget/src/widget/__init__.py b/aiida_workgraph/widget/src/widget/__init__.py index 01a6a550..8a8f83f2 100644 --- a/aiida_workgraph/widget/src/widget/__init__.py +++ b/aiida_workgraph/widget/src/widget/__init__.py @@ -50,7 +50,7 @@ def from_node(self, node: Any, show_metadata: bool = False) -> None: if not show_metadata: inputs = {} for input_k, input_v in tdata["inputs"].items(): - if not input_k.startswith("metadata"): + if not input_k.startswith("metadata."): inputs[input_k] = input_v tdata["inputs"] = inputs From 4c5feead88e13b1e6ad042ec07d38021592057b4 Mon Sep 17 00:00:00 2001 From: Julian Geiger Date: Wed, 20 Nov 2024 16:52:06 +0100 Subject: [PATCH 3/4] Generalize socket visualization based on namespace nesting --- aiida_workgraph/task.py | 4 +-- aiida_workgraph/utils/__init__.py | 22 ++++++++++++++++ aiida_workgraph/widget/src/widget/__init__.py | 25 +++++++++---------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/aiida_workgraph/task.py b/aiida_workgraph/task.py index 1bf48ee7..72544226 100644 --- a/aiida_workgraph/task.py +++ b/aiida_workgraph/task.py @@ -178,12 +178,12 @@ def _repr_mimebundle_(self, *args: Any, **kwargs: Any) -> any: else: return self._widget._ipython_display_(*args, **kwargs) - def to_html(self, output: str = None, **kwargs): + def to_html(self, output: str = None, show_socket_depth: int = 0, **kwargs): """Write a standalone html file to visualize the task.""" if self._widget is None: print(WIDGET_INSTALLATION_MESSAGE) return - self._widget.from_node(self) + self._widget.from_node(node=self, show_socket_depth=show_socket_depth) return self._widget.to_html(output=output, **kwargs) diff --git a/aiida_workgraph/utils/__init__.py b/aiida_workgraph/utils/__init__.py index be4fd3fc..9d122e6c 100644 --- a/aiida_workgraph/utils/__init__.py +++ b/aiida_workgraph/utils/__init__.py @@ -781,3 +781,25 @@ def validate_task_inout(inout_list: list[str | dict], list_type: str) -> list[di ) else: raise TypeError(f"Wrong type provided in the `{list_type}` list to the task.") + + +def filter_keys_namespace_depth( + dict_: dict[Any, Any], max_depth: int = 0 +) -> dict[Any, Any]: + """ + Filter top-level keys of a dictionary based on the namespace nesting level (number of periods) in the key. + + :param dict dict_: The dictionary to filter. + :param int max_depth: Maximum depth of namespaces to retain (number of periods). + :return: The filtered dictionary with only keys satisfying the depth condition. + :rtype: dict + """ + result: dict[Any, Any] = {} + + for key, value in dict_.items(): + depth = key.count(".") + + if depth <= max_depth: + result[key] = value + + return result diff --git a/aiida_workgraph/widget/src/widget/__init__.py b/aiida_workgraph/widget/src/widget/__init__.py index 8a8f83f2..a5908534 100644 --- a/aiida_workgraph/widget/src/widget/__init__.py +++ b/aiida_workgraph/widget/src/widget/__init__.py @@ -5,6 +5,7 @@ import anywidget import traitlets from .utils import wait_to_link +from aiida_workgraph.utils import filter_keys_namespace_depth try: __version__ = importlib.metadata.version("widget") @@ -37,24 +38,22 @@ def from_workgraph(self, workgraph: Any) -> None: wgdata = workgraph_to_short_json(wgdata) self.value = wgdata - def from_node(self, node: Any, show_metadata: bool = False) -> None: + def from_node(self, node: Any, show_socket_depth: int = 0) -> None: + tdata = node.to_dict() - tdata.pop("properties", None) - tdata.pop("executor", None) - tdata.pop("node_class", None) - tdata.pop("process", None) - tdata["label"] = tdata["identifier"] + + # Remove certain elements of the dict-representation of the Node that we don't want to show + for key in ("properties", "executor", "node_class", "process"): + tdata.pop(key, None) for input in tdata["inputs"].values(): input.pop("property") - if not show_metadata: - inputs = {} - for input_k, input_v in tdata["inputs"].items(): - if not input_k.startswith("metadata."): - inputs[input_k] = input_v - tdata["inputs"] = inputs + tdata["label"] = tdata["identifier"] - tdata["inputs"] = list(tdata["inputs"].values()) + filtered_inputs = filter_keys_namespace_depth( + dict_=tdata["inputs"], max_depth=show_socket_depth + ) + tdata["inputs"] = list(filtered_inputs.values()) tdata["outputs"] = list(tdata["outputs"].values()) wgdata = {"name": node.name, "nodes": {node.name: tdata}, "links": []} self.value = wgdata From 460ae411294449f4335bbdf02c2c1e31bf0e99d7 Mon Sep 17 00:00:00 2001 From: superstar54 Date: Mon, 2 Dec 2024 13:58:27 +0100 Subject: [PATCH 4/4] add show_socket_depth --- aiida_workgraph/task.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/aiida_workgraph/task.py b/aiida_workgraph/task.py index 72544226..f0943e7e 100644 --- a/aiida_workgraph/task.py +++ b/aiida_workgraph/task.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from node_graph.node import Node as GraphNode from aiida_workgraph import USE_WIDGET from aiida_workgraph.properties import property_pool @@ -56,6 +58,7 @@ def __init__( self._widget = None self.state = "PLANNED" self.action = "" + self.show_socket_depth = 0 def to_dict(self) -> Dict[str, Any]: from aiida.orm.utils.serialize import serialize @@ -172,14 +175,18 @@ def _repr_mimebundle_(self, *args: Any, **kwargs: Any) -> any: print(WIDGET_INSTALLATION_MESSAGE) return # if ipywdigets > 8.0.0, use _repr_mimebundle_ instead of _ipython_display_ - self._widget.from_node(self) + self._widget.from_node(self, show_socket_depth=self.show_socket_depth) if hasattr(self._widget, "_repr_mimebundle_"): return self._widget._repr_mimebundle_(*args, **kwargs) else: return self._widget._ipython_display_(*args, **kwargs) - def to_html(self, output: str = None, show_socket_depth: int = 0, **kwargs): + def to_html( + self, output: str = None, show_socket_depth: Optional[int] = None, **kwargs + ): """Write a standalone html file to visualize the task.""" + if show_socket_depth is None: + show_socket_depth = self.show_socket_depth if self._widget is None: print(WIDGET_INSTALLATION_MESSAGE) return