From 31ca96def8282a76925e84029485df631f21a063 Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Fri, 14 Feb 2025 12:58:04 +0000 Subject: [PATCH 1/3] Implement caching in `AiiDANodeViewWidget` --- .../static/styles/global.css | 7 +++++ aiidalab_widgets_base/viewers.py | 26 +++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/aiidalab_widgets_base/static/styles/global.css b/aiidalab_widgets_base/static/styles/global.css index e69de29bb..950b9acac 100644 --- a/aiidalab_widgets_base/static/styles/global.css +++ b/aiidalab_widgets_base/static/styles/global.css @@ -0,0 +1,7 @@ +.aiida-node-view-widget { + border: var(--jp-widgets-border-width) solid var(--jp-border-color1); + padding: 12px; + margin: 2px; + height: auto; + width: auto; +} diff --git a/aiidalab_widgets_base/viewers.py b/aiidalab_widgets_base/viewers.py index 6b58dfc3b..0853591cf 100644 --- a/aiidalab_widgets_base/viewers.py +++ b/aiidalab_widgets_base/viewers.py @@ -24,6 +24,7 @@ from matplotlib.colors import to_rgb from .dicts import RGB_COLORS, Colors, Radius +from .loaders import LoadingWidget from .misc import CopyToClipboardButton, ReversePolishNotation from .utils import ase2spglib, list_to_string_range, string_range_to_list @@ -67,20 +68,29 @@ class AiidaNodeViewWidget(ipw.VBox): def __init__(self, **kwargs): self._output = ipw.Output() - super().__init__( - children=[ - self._output, - ], - **kwargs, - ) + self.node_views = {} + self.node_view_loading_message = LoadingWidget("Loading node view") + super().__init__(**kwargs) + self.add_class("aiida-node-view-widget") @tl.observe("node") def _observe_node(self, change): - if change["new"] != change["old"]: + if not ((node := change["new"]) and node != change["old"]): + return + if node.uuid in self.node_views: + self.children = [self.node_views[node.uuid]] + return + self.children = [self.node_view_loading_message] + node_view = viewer(node) + if isinstance(node_view, ipw.DOMWidget): + self.node_views[node.uuid] = node_view + self.children = [node_view] + else: with self._output: clear_output() if change["new"]: - display(viewer(change["new"])) + display(node_view) + self.children = [self._output] @register_viewer_widget("data.core.dict.Dict.") From 84d58b717f6645386df57e3088f2bde87813368c Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Fri, 14 Feb 2025 14:32:11 +0000 Subject: [PATCH 2/3] Add `LoadingWidget` test --- tests/test_loaders.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_loaders.py b/tests/test_loaders.py index 23cee1b18..5bfb2506e 100644 --- a/tests/test_loaders.py +++ b/tests/test_loaders.py @@ -1,5 +1,6 @@ from pathlib import Path +from aiidalab_widgets_base.loaders import LoadingWidget from aiidalab_widgets_base.utils.loaders import load_css @@ -8,3 +9,12 @@ def test_load_css(): css_dir = Path("aiidalab_widgets_base/static/styles") load_css(css_path=css_dir) load_css(css_path=css_dir / "global.css") + + +def test_loading_widget(): + """Test `LoadingWidget`.""" + widget = LoadingWidget(message="Loading some widget") + assert widget.message.value == "Loading some widget" + assert widget.children[0].value == "Loading some widget" + assert widget.children[1].value == "" + assert "loading" in widget._dom_classes From 16799f2bdee11443ed7a0bd92dcb431fd4051f25 Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Sat, 15 Feb 2025 05:30:41 +0000 Subject: [PATCH 3/3] Add `AiidaNodeViewWidget` tests --- tests/test_viewers.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_viewers.py b/tests/test_viewers.py index adfb176f3..e10d655fd 100644 --- a/tests/test_viewers.py +++ b/tests/test_viewers.py @@ -313,3 +313,31 @@ def __init__(self, node=None): "Viewer is not an instance of the expected viewer class." ) assert viewer.node == process, "Viewer's node does not match the test process node." + + +def test_node_view_for_non_widget_viewer(): + """Test that a node with no registered viewer is displayed in an output widget""" + import sys + from io import StringIO + + # Intercepting stdout because `ipw.Output` does not + # store outputs in non-interactive environments. + captured = StringIO() + sys.stdout = captured + + node_view = viewers.AiidaNodeViewWidget() + node = orm.Int(1) + node_view.node = node + assert node_view.children[0] is node_view._output + assert str(node) in sys.stdout.getvalue() + + +def test_node_view_caching(): + """Test that providing a given node a second time returns the cached viewer.""" + node_view = viewers.AiidaNodeViewWidget() + node = orm.Int(1) + node_view.node = node + viewer = node_view.children[0] + node_view.node = None + node_view.node = node + assert node_view.children[0] is viewer