Skip to content

Commit f351939

Browse files
Implement caching in AiidaNodeViewWidget (#686)
Moving node view caching mechanism implemented by @superstar54 in the QE app (aiidalab/aiidalab-qe#921) into `AiidaNodeViewWidget`
1 parent 9601736 commit f351939

File tree

4 files changed

+63
-8
lines changed

4 files changed

+63
-8
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.aiida-node-view-widget {
2+
border: var(--jp-widgets-border-width) solid var(--jp-border-color1);
3+
padding: 12px;
4+
margin: 2px;
5+
height: auto;
6+
width: auto;
7+
}

aiidalab_widgets_base/viewers.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from matplotlib.colors import to_rgb
2525

2626
from .dicts import RGB_COLORS, Colors, Radius
27+
from .loaders import LoadingWidget
2728
from .misc import CopyToClipboardButton, ReversePolishNotation
2829
from .utils import ase2spglib, list_to_string_range, string_range_to_list
2930

@@ -67,20 +68,29 @@ class AiidaNodeViewWidget(ipw.VBox):
6768

6869
def __init__(self, **kwargs):
6970
self._output = ipw.Output()
70-
super().__init__(
71-
children=[
72-
self._output,
73-
],
74-
**kwargs,
75-
)
71+
self.node_views = {}
72+
self.node_view_loading_message = LoadingWidget("Loading node view")
73+
super().__init__(**kwargs)
74+
self.add_class("aiida-node-view-widget")
7675

7776
@tl.observe("node")
7877
def _observe_node(self, change):
79-
if change["new"] != change["old"]:
78+
if not ((node := change["new"]) and node != change["old"]):
79+
return
80+
if node.uuid in self.node_views:
81+
self.children = [self.node_views[node.uuid]]
82+
return
83+
self.children = [self.node_view_loading_message]
84+
node_view = viewer(node)
85+
if isinstance(node_view, ipw.DOMWidget):
86+
self.node_views[node.uuid] = node_view
87+
self.children = [node_view]
88+
else:
8089
with self._output:
8190
clear_output()
8291
if change["new"]:
83-
display(viewer(change["new"]))
92+
display(node_view)
93+
self.children = [self._output]
8494

8595

8696
@register_viewer_widget("data.core.dict.Dict.")

tests/test_loaders.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
22

3+
from aiidalab_widgets_base.loaders import LoadingWidget
34
from aiidalab_widgets_base.utils.loaders import load_css
45

56

@@ -8,3 +9,12 @@ def test_load_css():
89
css_dir = Path("aiidalab_widgets_base/static/styles")
910
load_css(css_path=css_dir)
1011
load_css(css_path=css_dir / "global.css")
12+
13+
14+
def test_loading_widget():
15+
"""Test `LoadingWidget`."""
16+
widget = LoadingWidget(message="Loading some widget")
17+
assert widget.message.value == "Loading some widget"
18+
assert widget.children[0].value == "Loading some widget"
19+
assert widget.children[1].value == "<i class='fa fa-spinner fa-spin fa-2x fa-fw'/>"
20+
assert "loading" in widget._dom_classes

tests/test_viewers.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,31 @@ def __init__(self, node=None):
313313
"Viewer is not an instance of the expected viewer class."
314314
)
315315
assert viewer.node == process, "Viewer's node does not match the test process node."
316+
317+
318+
def test_node_view_for_non_widget_viewer():
319+
"""Test that a node with no registered viewer is displayed in an output widget"""
320+
import sys
321+
from io import StringIO
322+
323+
# Intercepting stdout because `ipw.Output` does not
324+
# store outputs in non-interactive environments.
325+
captured = StringIO()
326+
sys.stdout = captured
327+
328+
node_view = viewers.AiidaNodeViewWidget()
329+
node = orm.Int(1)
330+
node_view.node = node
331+
assert node_view.children[0] is node_view._output
332+
assert str(node) in sys.stdout.getvalue()
333+
334+
335+
def test_node_view_caching():
336+
"""Test that providing a given node a second time returns the cached viewer."""
337+
node_view = viewers.AiidaNodeViewWidget()
338+
node = orm.Int(1)
339+
node_view.node = node
340+
viewer = node_view.children[0]
341+
node_view.node = None
342+
node_view.node = node
343+
assert node_view.children[0] is viewer

0 commit comments

Comments
 (0)