Skip to content

Commit 0acd017

Browse files
Avoid storing process nodes - use UUID
1 parent d544524 commit 0acd017

File tree

11 files changed

+77
-86
lines changed

11 files changed

+77
-86
lines changed

src/aiidalab_qe/app/result/__init__.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def _post_render(self):
121121

122122
self.toggle_controls.value = (
123123
"Results"
124-
if (process := self._model.fetch_process_node()) and process.is_finished_ok
124+
if self._model.has_process and self._model.process.is_finished_ok
125125
else "Status"
126126
)
127127

@@ -152,10 +152,9 @@ def _on_state_change(self, change):
152152

153153
def _on_previous_step_state_change(self, _):
154154
if self.previous_step_state is WizardAppWidgetStep.State.SUCCESS:
155-
process_node = self._model.fetch_process_node()
156155
message = (
157156
"Loading results"
158-
if process_node and process_node.is_finished
157+
if self._model.has_process and self._model.process.is_finished
159158
else "Submitting calculation"
160159
)
161160
self.children = [LoadingWidget(message)]
@@ -203,11 +202,10 @@ def _toggle_view(self, panel: ResultsComponent):
203202
def _update_kill_button_layout(self):
204203
if not self.rendered:
205204
return
206-
process_node = self._model.fetch_process_node()
207205
if (
208-
not process_node
209-
or process_node.is_finished
210-
or process_node.is_excepted
206+
not self._model.has_process
207+
or self._model.process.is_finished
208+
or self._model.process.is_excepted
211209
or self.state
212210
in (
213211
self.State.SUCCESS,
@@ -221,8 +219,7 @@ def _update_kill_button_layout(self):
221219
def _update_clean_scratch_button_layout(self):
222220
if not self.rendered:
223221
return
224-
process_node = self._model.fetch_process_node()
225-
if process_node and process_node.is_terminated:
222+
if self._model.has_process and self._model.process.is_terminated:
226223
self.clean_scratch_button.layout.display = "block"
227224
else:
228225
self.clean_scratch_button.layout.display = "none"
@@ -231,12 +228,12 @@ def _update_status(self):
231228
self._model.monitor_counter += 1
232229

233230
def _update_state(self):
234-
if not (process_node := self._model.fetch_process_node()):
231+
if not self._model.has_process:
235232
self.state = self.State.INIT
236233
self._update_controls()
237234
return
238235

239-
if process_state := process_node.process_state:
236+
if process_state := self._model.process.process_state:
240237
status = self._get_process_status(process_state.value)
241238
else:
242239
status = "Unknown"
@@ -254,9 +251,9 @@ def _update_state(self):
254251
ProcessState.KILLED,
255252
):
256253
self.state = self.State.FAIL
257-
elif process_node.is_failed:
254+
elif self._model.process.is_failed:
258255
self.state = self.State.FAIL
259-
elif process_node.is_finished_ok:
256+
elif self._model.process.is_finished_ok:
260257
self.state = self.State.SUCCESS
261258

262259
self._model.process_info = self.STATUS_TEMPLATE.format(status)

src/aiidalab_qe/app/result/components/summary/model.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,15 @@ def generate_report_text(self, report_dict):
124124

125125
def generate_failure_report(self):
126126
"""Generate a html for reporting the failure of the `QeAppWorkChain`."""
127-
process_node = self.fetch_process_node()
128-
if not (process_node and process_node.exit_status):
127+
if not (self.has_process and self.process.exit_status):
129128
return
130-
final_calcjob = self._get_final_calcjob(process_node)
129+
final_calcjob = self._get_final_calcjob(self.process)
131130
env = Environment()
132131
template = files(templates).joinpath("workflow_failure.jinja").read_text()
133132
style = files(styles).joinpath("style.css").read_text()
134133
self.failed_calculation_report = env.from_string(template).render(
135134
style=style,
136-
process_report=get_workchain_report(process_node, "REPORT"),
135+
process_report=get_workchain_report(self.process, "REPORT"),
137136
calcjob_exit_message=final_calcjob.exit_message,
138137
)
139138
self.has_failure_report = True
@@ -151,29 +150,28 @@ def _generate_report_parameters(self):
151150
"""
152151
from aiida.orm.utils.serialize import deserialize_unsafe
153152

154-
qeapp_wc = self.fetch_process_node()
155-
156-
ui_parameters = qeapp_wc.base.extras.get("ui_parameters", {})
153+
ui_parameters = self.process.base.extras.get("ui_parameters", {})
157154
if isinstance(ui_parameters, str):
158155
ui_parameters = deserialize_unsafe(ui_parameters)
159156
# Construct the report parameters needed for the report
160157
# drop support for old ui parameters
161158
if "workchain" not in ui_parameters:
162159
return {}
163160

164-
inputs = qeapp_wc.inputs
161+
inputs = self.inputs
162+
assert inputs.structure, "BUG! Missing structure input" # shouldn't happen!
165163
structure: orm.StructureData = inputs.structure
166164
basic = ui_parameters["workchain"]
167165
advanced = ui_parameters["advanced"]
168-
ctime = qeapp_wc.ctime
169-
mtime = qeapp_wc.mtime
166+
ctime = self.process.ctime
167+
mtime = self.process.mtime
170168

171169
report = {
172170
"workflow_properties": {
173-
"pk": qeapp_wc.pk,
174-
"uuid": str(qeapp_wc.uuid),
175-
"label": qeapp_wc.label,
176-
"description": qeapp_wc.description,
171+
"pk": self.process.pk,
172+
"uuid": str(self.process.uuid),
173+
"label": self.process.label,
174+
"description": self.process.description,
177175
"creation_time": f"{format_time(ctime)} ({relative_time(ctime)})",
178176
"modification_time": f"{format_time(mtime)} ({relative_time(mtime)})",
179177
},

src/aiidalab_qe/app/result/components/summary/summary.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ def _render_summary(self):
8181
self.has_settings_report = True
8282

8383
def _render_download_widget(self):
84-
process_node = self._model.fetch_process_node()
85-
if process_node and process_node.is_terminated:
86-
output_download_widget = WorkChainOutputs(node=process_node)
84+
if self._model.has_process and self._model.process.is_terminated:
85+
output_download_widget = WorkChainOutputs(node=self._model.process)
8786
output_download_widget.layout.width = "100%"
8887
self.output_download_container.children = [
8988
self.output_download_help,

src/aiidalab_qe/app/result/model.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ def update(self):
2626
self._update_process_remote_folder_state()
2727

2828
def kill_process(self):
29-
if process_node := self.fetch_process_node():
30-
control.kill_processes([process_node])
29+
if self.has_process:
30+
control.kill_processes([self.process])
3131

3232
def clean_remote_data(self):
33-
if not (process_node := self.fetch_process_node()):
33+
if not self.has_process:
3434
return
35-
for called_descendant in process_node.called_descendants:
35+
for called_descendant in self.process.called_descendants:
3636
if isinstance(called_descendant, orm.CalcJobNode):
3737
with contextlib.suppress(Exception):
3838
called_descendant.outputs.remote_folder._clean()
@@ -43,11 +43,10 @@ def reset(self):
4343
self.process_info = ""
4444

4545
def _update_process_remote_folder_state(self):
46-
process_node = self.fetch_process_node()
47-
if not (process_node and process_node.called_descendants):
46+
if not (self.has_process and self.process.called_descendants):
4847
return
4948
cleaned = []
50-
for called_descendant in process_node.called_descendants:
49+
for called_descendant in self.process.called_descendants:
5150
if isinstance(called_descendant, orm.CalcJobNode):
5251
with contextlib.suppress(Exception):
5352
cleaned.append(called_descendant.outputs.remote_folder.is_empty)

src/aiidalab_qe/app/submission/model.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from aiida.engine import ProcessBuilderNamespace, submit
99
from aiida.orm.utils.serialize import serialize
1010
from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
11-
from aiidalab_qe.common.mixins import HasInputStructure, HasModels
11+
from aiidalab_qe.common.mixins import HasInputStructure, HasModels, HasProcess
1212
from aiidalab_qe.common.panel import PluginResourceSettingsModel, ResourceSettingsModel
1313
from aiidalab_qe.common.wizard import QeConfirmableWizardStepModel
1414
from aiidalab_qe.utils import shallow_copy_nested_dict
@@ -21,12 +21,12 @@ class SubmissionStepModel(
2121
QeConfirmableWizardStepModel,
2222
HasModels[ResourceSettingsModel],
2323
HasInputStructure,
24+
HasProcess,
2425
):
2526
identifier = "submission"
2627

2728
input_parameters = tl.Dict()
2829

29-
process_node = tl.Instance(orm.WorkChainNode, allow_none=True)
3030
process_label = tl.Unicode("")
3131
process_description = tl.Unicode("")
3232

@@ -60,7 +60,7 @@ def __init__(self, *args, **kwargs):
6060

6161
def confirm(self):
6262
super().confirm()
63-
if not self.process_node:
63+
if not self.has_process:
6464
self._submit()
6565

6666
def update(self):
@@ -163,15 +163,19 @@ def set_model_state(self, state: dict):
163163
model.set_model_state(codes[identifier])
164164
model.locked = True
165165

166-
if self.process_node:
167-
self.process_label = self.process_node.label
168-
self.process_description = self.process_node.description
169-
self.locked = True
166+
self._update_process_metadata()
167+
168+
def _update_process_metadata(self):
169+
if not self.has_process:
170+
return
171+
self.process_label = self.process.label
172+
self.process_description = self.process.description
173+
self.locked = True
170174

171175
def reset(self):
172176
with self.hold_trait_notifications():
173177
self.input_parameters = {}
174-
self.process_node = None
178+
self.process_uuid = None
175179
for identifier, model in self.get_models():
176180
if identifier not in self._default_models:
177181
model.include = False
@@ -194,13 +198,10 @@ def _submit(self):
194198
"structure",
195199
self.input_structure.get_formula(),
196200
)
197-
self.process_node = process_node
198-
199-
self._update_url()
201+
self.process_uuid = process_node.uuid
200202

201-
def _update_url(self):
202-
pk = self.process_node.pk
203-
display(Javascript(f"window.history.pushState(null, '', '?pk={pk}');"))
203+
pk = process_node.pk
204+
display(Javascript(f"window.history.pushState(null, '', '?pk={pk}');"))
204205

205206
def _link_model(self, model: ResourceSettingsModel):
206207
for dependency in model.dependencies:

src/aiidalab_qe/app/wizard_app.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,8 @@ def _update_submission_step(self):
159159

160160
def _update_results_step(self):
161161
ipw.dlink(
162-
(self.submit_model, "process_node"),
162+
(self.submit_model, "process_uuid"),
163163
(self.results_model, "process_uuid"),
164-
lambda node: node.uuid if node is not None else None,
165164
)
166165

167166
def _lock_app(self):
@@ -186,7 +185,7 @@ def _update_from_process(self, pk):
186185
parameters = deserialize_unsafe(parameters)
187186
self.configure_model.set_model_state(parameters)
188187
self.configure_model.confirm()
189-
self.submit_model.process_node = process_node
188+
self.submit_model.process_uuid = process_node.uuid
190189
self.submit_model.set_model_state(parameters)
191190
self.submit_model.confirm()
192191
self._wizard_app_widget.selected_index = 3

src/aiidalab_qe/common/mixins.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,36 +106,37 @@ class HasProcess(tl.HasTraits):
106106
process_uuid = tl.Unicode(None, allow_none=True)
107107
monitor_counter = tl.Int(0) # used for continuous updates
108108

109+
@property
110+
@cache_per_thread(invalidator="process_uuid")
111+
def process(self) -> orm.WorkChainNode | None:
112+
if not self.process_uuid:
113+
return None
114+
try:
115+
return t.cast(orm.WorkChainNode, orm.load_node(self.process_uuid))
116+
except NotExistent:
117+
return None
118+
109119
@property
110120
def has_process(self):
111-
return self.fetch_process_node() is not None
121+
return self.process is not None
112122

113123
@property
114-
def inputs(self):
115-
process_node = self.fetch_process_node()
116-
return process_node.inputs if process_node else []
124+
def inputs(self) -> orm.NodeLinksManager | list:
125+
return self.process.inputs if self.has_process else []
117126

118127
@property
119-
def properties(self):
120-
process_node = self.fetch_process_node()
128+
def properties(self) -> list:
121129
# read the attributes directly instead of using the `get_list` method
122130
# to avoid error in case of the orm.List object being converted to a orm.Data object
123131
return (
124-
process_node.inputs.properties.base.attributes.get("list")
125-
if process_node
132+
self.inputs.properties.base.attributes.get("list")
133+
if self.has_process
126134
else []
127135
)
128136

129137
@property
130138
def outputs(self):
131-
process_node = self.fetch_process_node()
132-
return process_node.outputs if process_node else []
133-
134-
def fetch_process_node(self) -> orm.ProcessNode | None:
135-
try:
136-
return orm.load_node(self.process_uuid) if self.process_uuid else None # type: ignore
137-
except NotExistent:
138-
return None
139+
return self.process.outputs if self.has_process else []
139140

140141

141142
class Confirmable(tl.HasTraits):

src/aiidalab_qe/common/panel.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -529,22 +529,20 @@ def fetch_child_process_node(self, which="this") -> orm.ProcessNode | None:
529529
uuid = getattr(self, f"_{which}_process_uuid")
530530
label = getattr(self, f"_{which}_process_label")
531531
if not uuid:
532-
root = self.fetch_process_node()
532+
root = self.process
533533
child = next((c for c in root.called if c.process_label == label), None)
534534
uuid = child.uuid if child else None
535535
return orm.load_node(uuid) if uuid else None # type: ignore
536536

537537
def save_state(self):
538538
"""Saves the current state of the model to the AiiDA database."""
539-
node = self.fetch_process_node()
540-
results = node.base.extras.get("results", {})
539+
results = self.process.base.extras.get("results", {})
541540
results[self.identifier] = self.get_model_state()
542-
node.base.extras.set("results", results)
541+
self.process.base.extras.set("results", results)
543542

544543
def load_state(self):
545544
"""Loads the state of the model from the AiiDA database."""
546-
node = self.fetch_process_node()
547-
results = node.base.extras.get("results", {})
545+
results = self.process.base.extras.get("results", {})
548546
if self.identifier in results:
549547
self.set_model_state(results[self.identifier])
550548

src/aiidalab_qe/common/process/tree.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ def _render(self):
102102
)
103103
self.collapse_button.on_click(self._collapse_all)
104104

105-
root = self._model.fetch_process_node()
106-
107-
self.trunk = WorkChainTreeNode(node=root, on_inspect=self._on_inspect)
105+
self.trunk = WorkChainTreeNode(
106+
node=self._model.process,
107+
on_inspect=self._on_inspect,
108+
)
108109
self.trunk.add_class("tree-trunk")
109110
self.trunk.initialize()
110111
self.trunk.expand()

src/aiidalab_qe/plugins/electronic_structure/result/result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _render_bands_pdos_widget(self, node_identifiers):
100100
identifier: self._model.fetch_child_process_node(identifier)
101101
for identifier in node_identifiers
102102
},
103-
"root": self._model.fetch_process_node(),
103+
"root": self._model.process,
104104
}
105105
model = BandsPdosModel.from_nodes(**nodes)
106106
widget = BandsPdosWidget(model=model)

0 commit comments

Comments
 (0)