From 2bc1a3f2b6cfec6aaddace219eda292cf1f411d2 Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Sun, 22 Dec 2024 11:14:43 +0000 Subject: [PATCH] Elevate common features up to `PanelModel` and `Panel` root classes --- src/aiidalab_qe/app/configuration/__init__.py | 7 +- src/aiidalab_qe/app/submission/__init__.py | 13 ++- src/aiidalab_qe/common/panel.py | 108 ++++++++---------- .../plugins/bands/result/result.py | 2 - .../electronic_structure/result/result.py | 2 - src/aiidalab_qe/plugins/pdos/result/result.py | 2 - src/aiidalab_qe/plugins/xas/result/result.py | 2 - src/aiidalab_qe/plugins/xps/result/result.py | 2 - 8 files changed, 55 insertions(+), 83 deletions(-) diff --git a/src/aiidalab_qe/app/configuration/__init__.py b/src/aiidalab_qe/app/configuration/__init__.py index 79e3493a9..e47fd9258 100644 --- a/src/aiidalab_qe/app/configuration/__init__.py +++ b/src/aiidalab_qe/app/configuration/__init__.py @@ -238,7 +238,6 @@ def _fetch_plugin_calculation_settings(self): if key not in configuration: raise ValueError(f"Entry {identifier} is missing the '{key}' key") - panel = configuration["panel"] model: ConfigurationSettingsModel = configuration["model"]() self._model.add_model(identifier, model) @@ -280,7 +279,5 @@ def toggle_plugin(change, identifier=identifier, model=model, info=info): ) ) - self.settings[identifier] = panel( - identifier=identifier, - model=model, - ) + panel: ConfigurationSettingsPanel = configuration["panel"](model=model) + self.settings[identifier] = panel diff --git a/src/aiidalab_qe/app/submission/__init__.py b/src/aiidalab_qe/app/submission/__init__.py index 540899647..c67a0bf23 100644 --- a/src/aiidalab_qe/app/submission/__init__.py +++ b/src/aiidalab_qe/app/submission/__init__.py @@ -12,7 +12,11 @@ from aiidalab_qe.app.utils import get_entry_items from aiidalab_qe.common.code import PluginCodes, PwCodeModel from aiidalab_qe.common.infobox import InAppGuide -from aiidalab_qe.common.panel import PluginResourceSettingsModel, ResourceSettingsPanel +from aiidalab_qe.common.panel import ( + PluginResourceSettingsModel, + PluginResourceSettingsPanel, + ResourceSettingsPanel, +) from aiidalab_qe.common.setup_codes import QESetupWidget from aiidalab_qe.common.setup_pseudos import PseudosInstallWidget from aiidalab_widgets_base import WizardAppWidgetStep @@ -338,7 +342,6 @@ def _fetch_plugin_resource_settings(self): if key not in resources: raise ValueError(f"Entry {identifier} is missing the '{key}' key") - panel = resources["panel"] model: PluginResourceSettingsModel = resources["model"]() model.observe( self._on_plugin_overrides_change, @@ -354,10 +357,8 @@ def _fetch_plugin_resource_settings(self): ) self._model.add_model(identifier, model) - self.settings[identifier] = panel( - identifier=identifier, - model=model, - ) + panel: PluginResourceSettingsPanel = resources["panel"](model=model) + self.settings[identifier] = panel codes[identifier] = dict(model.get_models()) diff --git a/src/aiidalab_qe/common/panel.py b/src/aiidalab_qe/common/panel.py index ea0b48c96..f708fab93 100644 --- a/src/aiidalab_qe/common/panel.py +++ b/src/aiidalab_qe/common/panel.py @@ -28,34 +28,39 @@ DEFAULT: dict = DEFAULT_PARAMETERS # type: ignore -class Panel(ipw.VBox): - """Base class for all the panels. +class PanelModel(Model): + """Base class for all panel models. + + Attributes + ---------- + `title` : `str` + The title to be shown in the GUI. + `identifier` : `str` + Which plugin this panel belong to. + """ - The base class has a method to return the value of all the widgets in - the panel as a dictionary. The dictionary is used to construct the - input file for the calculation. The class also has a method to load a dictionary to set the value of the widgets in the panel. + title = "" + identifier = "" - title: the title to be shown in the GUI - identifier: which plugin this panel belong to. - """ +PM = t.TypeVar("PM", bound=PanelModel) - title = "Panel" - # TODO remove `identifier` (and `parent`) from signature - # TODO add `model` parameter - # TODO add `identifier` property to route to model.identifier - def __init__(self, parent=None, identifier=None, **kwargs): - """Initialize the panel. +class Panel(ipw.VBox, t.Generic[PM]): + """Base class for all panels.""" - :param kwargs: keyword arguments to pass to the ipw.VBox constructor. - """ - self.parent = parent - self.identifier = identifier or getattr(self, "identifier", "plugin") - super().__init__( - children=kwargs.pop("children", []), - **kwargs, - ) + rendered = False + loading_message = "Loading {identifier} panel" + + def __init__(self, model: PM, **kwargs): + loading_message = self.loading_message.format(identifier=model.identifier) + loading_message = loading_message.replace("_", " ") + self.loading_message = LoadingWidget(loading_message) + super().__init__(children=[self.loading_message], **kwargs) + self._model = model + + def render(self): + raise NotImplementedError() class PluginOutline(ipw.HBox): @@ -82,15 +87,17 @@ def __init__(self, **kwargs): ) -class SettingsModel(Model): - title = "" - identifier = "" +class SettingsModel(PanelModel): + """Base model for settings models.""" + dependencies: list[str] = [] include = tl.Bool(False) loaded_from_process = tl.Bool(False) - _defaults = {} + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._defaults = {} def update(self): """Updates the model.""" @@ -112,25 +119,15 @@ def reset(self): SM = t.TypeVar("SM", bound=SettingsModel) -class SettingsPanel(Panel, t.Generic[SM]): - def __init__(self, model: SM, **kwargs): - self.loading_message = LoadingWidget(f"Loading {model.identifier} settings") +class SettingsPanel(Panel[SM]): + """Base model for settings panels.""" - super().__init__( - children=[self.loading_message], - **kwargs, - ) - - self._model = model - - self.rendered = False - self.updated = False + updated = False + def __init__(self, model: SM, **kwargs): + super().__init__(model=model, **kwargs) self.links = [] - def render(self): - raise NotImplementedError() - class ConfigurationSettingsModel(SettingsModel, Confirmable): """Base model for configuration settings models.""" @@ -149,7 +146,7 @@ def update(self, specific=""): CSM = t.TypeVar("CSM", bound=ConfigurationSettingsModel) -class ConfigurationSettingsPanel(SettingsPanel[CSM], t.Generic[CSM]): +class ConfigurationSettingsPanel(SettingsPanel[CSM]): """Base class for configuration settings panels.""" def refresh(self, specific=""): @@ -203,8 +200,6 @@ def _reset(self): class ResourceSettingsModel(SettingsModel, HasModels[CodeModel]): """Base model for resource setting models.""" - dependencies = [] - global_codes = tl.Dict( key_trait=tl.Unicode(), value_trait=tl.Dict(), @@ -256,12 +251,11 @@ def _check_submission_blockers(self): RSM = t.TypeVar("RSM", bound=ResourceSettingsModel) -class ResourceSettingsPanel(SettingsPanel[RSM], t.Generic[RSM]): +class ResourceSettingsPanel(SettingsPanel[RSM]): """Base class for resource setting panels.""" def __init__(self, model, **kwargs): super().__init__(model, **kwargs) - self.code_widgets = {} def _on_code_resource_change(self, _): @@ -400,7 +394,7 @@ def _link_model(self, model: CodeModel): PRSM = t.TypeVar("PRSM", bound=PluginResourceSettingsModel) -class PluginResourceSettingsPanel(ResourceSettingsPanel[PRSM], t.Generic[PRSM]): +class PluginResourceSettingsPanel(ResourceSettingsPanel[PRSM]): """Base class for plugin resource setting panels.""" def __init__(self, model, **kwargs): @@ -506,10 +500,7 @@ def _link_override_to_widget_disable(self, code_model, code_widget): ) -class ResultsModel(Model, HasProcess): - title = "Model" - identifier = "model" - +class ResultsModel(PanelModel, HasProcess): process_status_notification = tl.Unicode("") _this_process_label = "" @@ -593,7 +584,7 @@ def _fetch_child_process_node(self, child="this") -> orm.ProcessNode | None: RM = t.TypeVar("RM", bound=ResultsModel) -class ResultsPanel(Panel, t.Generic[RM]): +class ResultsPanel(Panel[RM]): """Base class for all the result panels. The base class has a method to load the result of the calculation. @@ -601,16 +592,12 @@ class ResultsPanel(Panel, t.Generic[RM]): It has a update method to update the result in the panel. """ - # To specify which plugins (outputs) are needed - # for this result panel. - workchain_labels = [] + has_controls = False + loading_message = "Loading {identifier} results" def __init__(self, model: RM, **kwargs): - self.loading_message = LoadingWidget(f"Loading {model.title.lower()} results") + super().__init__(model=model, **kwargs) - super().__init__(**kwargs) - - self._model = model self._model.observe( self._on_process_change, "process_uuid", @@ -620,9 +607,6 @@ def __init__(self, model: RM, **kwargs): "monitor_counter", ) - self.rendered = False - self.has_controls = False - self.links = [] def render(self): diff --git a/src/aiidalab_qe/plugins/bands/result/result.py b/src/aiidalab_qe/plugins/bands/result/result.py index 0115db889..1912463e3 100644 --- a/src/aiidalab_qe/plugins/bands/result/result.py +++ b/src/aiidalab_qe/plugins/bands/result/result.py @@ -9,8 +9,6 @@ class BandsResultsPanel(ResultsPanel[BandsResultsModel]): - workchain_labels = ["bands"] - def _render(self): bands_node = self._model.get_bands_node() model = BandsPdosModel() diff --git a/src/aiidalab_qe/plugins/electronic_structure/result/result.py b/src/aiidalab_qe/plugins/electronic_structure/result/result.py index af2abb808..1d1d55eea 100644 --- a/src/aiidalab_qe/plugins/electronic_structure/result/result.py +++ b/src/aiidalab_qe/plugins/electronic_structure/result/result.py @@ -9,8 +9,6 @@ class ElectronicStructureResultsPanel(ResultsPanel[ElectronicStructureResultsModel]): - workchain_labels = ["bands", "pdos"] - def _render(self): bands_node = self._model.get_bands_node() pdos_node = self._model.get_pdos_node() diff --git a/src/aiidalab_qe/plugins/pdos/result/result.py b/src/aiidalab_qe/plugins/pdos/result/result.py index 34f52a0d0..11e841be9 100644 --- a/src/aiidalab_qe/plugins/pdos/result/result.py +++ b/src/aiidalab_qe/plugins/pdos/result/result.py @@ -9,8 +9,6 @@ class PdosResultsPanel(ResultsPanel[PdosResultsModel]): - workchain_labels = ["pdos"] - def _render(self): pdos_node = self._model.get_pdos_node() model = BandsPdosModel() diff --git a/src/aiidalab_qe/plugins/xas/result/result.py b/src/aiidalab_qe/plugins/xas/result/result.py index c94e216a6..a4edb1772 100644 --- a/src/aiidalab_qe/plugins/xas/result/result.py +++ b/src/aiidalab_qe/plugins/xas/result/result.py @@ -11,8 +11,6 @@ class XasResultsPanel(ResultsPanel[XasResultsModel]): - workchain_labels = ["xas"] - def _render(self): variable_broad_select = ipw.Checkbox( description="Use variable energy broadening.", diff --git a/src/aiidalab_qe/plugins/xps/result/result.py b/src/aiidalab_qe/plugins/xps/result/result.py index abb9c8211..fbd22b89f 100644 --- a/src/aiidalab_qe/plugins/xps/result/result.py +++ b/src/aiidalab_qe/plugins/xps/result/result.py @@ -9,8 +9,6 @@ class XpsResultsPanel(ResultsPanel[XpsResultsModel]): - workchain_labels = ["xps"] - experimental_data = None # Placeholder for experimental data def _on_file_upload(self, change):