Skip to content

Commit

Permalink
quicksetup with template work version
Browse files Browse the repository at this point in the history
Now the problem is the computer_setup is set for aiida_computer_setup widget,
but when create the computer, the parameter values are from widget.
It should from computer_setup with updated from the inputs.
  • Loading branch information
unkcpz committed Sep 13, 2023
1 parent d8f8c19 commit 9e0deff
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 31 deletions.
132 changes: 110 additions & 22 deletions aiidalab_widgets_base/computational_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import subprocess
import threading
from collections import namedtuple
from copy import copy
from copy import copy, deepcopy
from pathlib import Path
from uuid import UUID

Expand Down Expand Up @@ -436,7 +436,7 @@ def _on_setup_ssh_button_pressed(self, _=None):

# If hostname & username are not provided - do not do anything.
if self.hostname.value == "": # check hostname
self.message = "Please specify the computer hostname."
self.message = "Please specify the computer hostname (for SSH)"
return False

if self.username.value == "": # check username
Expand Down Expand Up @@ -1328,13 +1328,14 @@ def _render(self):
w = ipw.Text(
description=f"{var}:",
value=default_value.get(var, ""),
# delay notifying the observers until the user stops typing
continuous_update=False,
layout=LAYOUT,
style=STYLE,
)
# Every time the value of the widget changes, we update the filled template.
# This migth be too much to sync the final filled template every time.
w.observe(self._on_template_variable_filled, names="value")
# self._mapping_variables[var] = (key, temp_str, w)
self._mapping_variables[var] = namedtuple(
"MappingVariable", ["key", "temp_str", "widget", "variables"]
)(key, temp_str, w, variables)
Expand All @@ -1351,13 +1352,17 @@ def _on_template_variable_filled(self, change):
for var, mapping_variable in self._mapping_variables.items():
if mapping_variable.widget is change["owner"]:
# See if all variables are set in widget and ready from the mapping
# If not continue to wait for the inputs.
variables = mapping_variable.variables
for v in variables:
if self._mapping_variables[v][2].value == "":
# Be careful that here we cannot conditional on whether still there is un-filled template, because the template with default is valid to pass as output filled template.
if self._mapping_variables[v].widget.value == "":
return

# If all variables are ready, update the filled template.
inp_dict = {v: self._mapping_variables[v][2].value for v in variables}
inp_dict = {
v: self._mapping_variables[v].widget.value for v in variables
}

# re-render the template
env = Environment()
Expand All @@ -1366,13 +1371,16 @@ def _on_template_variable_filled(self, change):
)

# Update the filled template.
self.filled_templates[mapping_variable.key] = filled_str
# use deepcopy to assure the trait change is triggered.
filled_templates = deepcopy(self.filled_templates)
filled_templates[mapping_variable.key] = filled_str
self.filled_templates = filled_templates

# Update the template partially in the filled template and filled with other variables in the same template string.
# The template string can contain multiple variables.
self._mapping_variables[var] = namedtuple(
"MappingVariable", ["key", "temp_str", "widget"]
)(mapping_variable.key, filled_str, mapping_variable.widget)
"MappingVariable", ["key", "temp_str", "widget", "variables"]
)(mapping_variable.key, filled_str, mapping_variable.widget, variables)

break

Expand All @@ -1384,7 +1392,9 @@ class QuickSetupWidget(ipw.VBox):
message = traitlets.Unicode()

ssh_config = traitlets.Dict(allow_none=True)
computer_setup = traitlets.Dict(allow_none=True)
computer_setup = traitlets.Dict(
allow_none=True
) # In the format of {setup: {}, configure: {}}
code_setup = traitlets.Dict(allow_none=True)

def __init__(self, default_calc_job_plugin=None, **kwargs):
Expand All @@ -1401,30 +1411,51 @@ def __init__(self, default_calc_job_plugin=None, **kwargs):
self.comp_resources_database.observe(self._on_select_code, names="code_setup")

self.ssh_computer_setup = SshComputerSetup()
self.ssh_computer_setup.observe(self._on_ssh_computer_setup, names="ssh_config")
ipw.dlink(
(self.ssh_computer_setup, "message"),
(self, "message"),
)
ipw.dlink(
(self, "ssh_config"),
(self.ssh_computer_setup, "ssh_config"),
)

self.aiida_computer_setup = AiidaComputerSetup()
self.aiida_computer_setup.on_setup_computer_success(
self._on_setup_computer_success
)
# link two traits so only one of them needs to be set (in the widget only manipulate with `self.computer_setup``)
ipw.dlink(
(self, "computer_setup"),
(self.aiida_computer_setup, "computer_setup"),
)
ipw.dlink(
(self.aiida_computer_setup, "message"),
(self, "message"),
)

self.aiida_code_setup = AiidaCodeSetup()
self.aiida_code_setup.on_setup_code_success(self._on_setup_code_success)
# link two traits so only one of them needs to be set (in the widget only manipulate with `self.code_setup``)
ipw.dlink(
(self, "code_setup"),
(self.aiida_code_setup, "code_setup"),
)
ipw.dlink(
(self.aiida_code_setup, "message"),
(self, "message"),
)

# The placeholder widget for the template variable of config.
self.template_variables_computer = TemplateVariablesWidget()
self.template_variables_computer.observe(
self._on_template_variables_computer_filled, names="filled_templates"
)
self.template_variables_code = TemplateVariablesWidget()
self.template_variables_code.observe(
self._on_template_variables_code_filled, names="filled_templates"
)

super().__init__(
children=[
Expand All @@ -1443,9 +1474,28 @@ def __init__(self, default_calc_job_plugin=None, **kwargs):
**kwargs,
)

def _on_ssh_computer_setup(self, change=None):
"""Callback when the ssh config is set."""
# Update the ssh config.
self.ssh_config = change["new"]

def _on_template_variables_computer_filled(self, change):
"""Callback when the template variables of computer are filled."""
# Update the filled template.
computer_setup = deepcopy(self.computer_setup)
computer_setup["setup"] = change["new"]
self.computer_setup = computer_setup

def _on_template_variables_code_filled(self, change):
"""Callback when the template variables of code are filled."""
# Update the filled template.
self.code_setup = change["new"]

def _on_select_computer(self, change):
"""Update the computer trait"""
if change["new"] is None:
if not change["new"]:
# for both `None` and `{}`
self.reset()
return

# reset the widget first to clean all the input fields (ssh_config, computer_setup, code_setup).
Expand All @@ -1454,18 +1504,14 @@ def _on_select_computer(self, change):
new_setup = change["new"]

# Read from template and prepare the widgets for the template variables.
self.template_variables_computer.template = new_setup
self.template_variables_computer.templates = new_setup["setup"]

# pre-set the input fields no matter if the template variables are set.
self.aiida_computer_setup.computer_setup = new_setup
self.computer_setup = new_setup

# ssh config need to sync hostname etc to resource database.
self.ssh_computer_setup.ssh_config = self.comp_resources_database.ssh_config
self.ssh_config = self.comp_resources_database.ssh_config

# for temp_str in change["new"].values():

def _on_select_code(self, change):
"""Update the code trait"""
if change["new"] is None:
Expand All @@ -1474,25 +1520,67 @@ def _on_select_code(self, change):
# XXX check if code needs to be reset or not

new_setup = change["new"]
self.template_variables_code.template = new_setup
self.template_variables_code.templates = new_setup

self.aiida_code_setup.code_setup = new_setup
self.code_setup = new_setup

def _on_quick_setup(self, _=None):
"""Go through all the setup steps automatically."""
# Fill text fields with template variables. (computer setup)
# XXX
# Use default values for the template variables if not set.
# and the same time check if all templates are filled.
# Becareful there are same key in both template_variables_computer and template_variables_code, e.g. label.
# So can not combine them by {**a, **b}
for w_tmp in [self.template_variables_computer, self.template_variables_code]:
filled_templates = deepcopy(w_tmp.filled_templates)
for k, v in w_tmp.filled_templates.items():
env = Environment()
parsed_content = env.parse(v)
vs = meta.find_undeclared_variables(parsed_content)
filters = list(parsed_content.find_all(Filter))

# No variables in the template, all filled.
if len(vs) == 0:
continue

# We only has default filter for now, so when filter number is less than variable number, it means some variables are not filled.
if len(filters) < len(vs):
self.message = f"Please fill all the variables. Missing: {vs}"
return
else:
# Update filled template with default value.

# self.ssh_computer_setup._on_setup_ssh_button_pressed()
# if self.aiida_computer_setup.on_setup_computer():
# self.aiida_code_setup.on_setup_code()
default_value = {
fltr.node.name: fltr.args[0].value
for fltr in filters
if fltr.name == "default"
}
filled_templates[k] = env.from_string(v).render(**default_value)

# Update the filled template to trigger the trait change.
w_tmp.filled_templates = filled_templates

# Fill text fields with template variables.
with self.hold_trait_notifications():
# XXX should trigger
self.code_setup = self.template_variables_code.filled_templates
computer_setup = deepcopy(self.computer_setup)
computer_setup["setup"] = self.template_variables_computer.filled_templates
self.computer_setup = computer_setup

if self.aiida_computer_setup.on_setup_computer():
self.aiida_code_setup.on_setup_code()
self.ssh_computer_setup._on_setup_ssh_button_pressed()

def _on_setup_computer_success(self):
"""Callback that is called when the computer is successfully set up."""
# update the computer dropdown list of code setup
self.aiida_code_setup.refresh()

# and set the computer in the code_setup
code_setup = deepcopy(self.code_setup)
code_setup["computer"] = self.computer_setup["setup"]["label"]
self.code_setup = code_setup

def _on_setup_code_success(self):
"""Callback that is called when the code is successfully set up."""
self.success = True
Expand Down
49 changes: 40 additions & 9 deletions tests/test_computational_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def test_template_variables_widget():
# Fill the template variables
for key, value in w._mapping_variables.items():
if key == "label":
sub_widget = value[2]
sub_widget = value.widget
sub_widget.value = "daint-mc-test"

# check the filled value is updated in the filled template
Expand All @@ -247,11 +247,15 @@ def test_template_variables_widget():
# Fill two template variables in one template line
for key, value in w._mapping_variables.items():
if key == "slurm_partition":
sub_widget = value[2]
sub_widget = value.widget
sub_widget.value = "normal-test"

# check the value can be re-input
# this test somehow has unexpected behavior, disable it for the moment.
# sub_widget.value = "another-test"

elif key == "slurm_account":
sub_widget = value[2]
sub_widget = value.widget
sub_widget.value = "newuser"

# check the filled value is updated in the filled template
Expand All @@ -271,13 +275,13 @@ def test_quick_setup_widget():
# Test message is update correctly. By click setup button without filling in any information.
w._on_quick_setup()

assert "Please specify the computer name" in w.message
assert "Please specify the computer hostname (for SSH)" in w.message

# Test select a new resource setup will update the output interface (e.g. ssh_config, computer_setup, code_setup)
# and the computer/code setup widget will be updated accordingly.
w.comp_resources_database.inp_domain.value = "daint.cscs.ch"
w.comp_resources_database.inp_computer.value = "multicore"
w.comp_resources_database.inp_code.value = "pw-7.0-multicore"
w.comp_resources_database.domain_selector.value = "daint.cscs.ch"
w.comp_resources_database.computer_selector.value = "mc"
w.comp_resources_database.code_selector.value = "pw-7.0"

assert w.ssh_config == w.comp_resources_database.ssh_config
assert w.computer_setup == w.comp_resources_database.computer_setup
Expand All @@ -287,11 +291,38 @@ def test_quick_setup_widget():
# the message should be updated.
w._on_quick_setup()

assert "created" in w.message
assert "Please fill all the variables" in w.message

# Fill in the computer name and trigger the setup button again, the message should be updated.
for (
key,
mapping_variable,
) in w.template_variables_computer._mapping_variables.items():
if key == "label":
sub_widget = mapping_variable.widget

# Test the default value is filled in correctly.
assert sub_widget.value == "daint-mc"
if key == "slurm_partition":
sub_widget = mapping_variable.widget
sub_widget.value = "normal-test"
if key == "slurm_account":
sub_widget = mapping_variable.widget
sub_widget.value = "newuser"

w._on_quick_setup()

assert "Please specify your SSH username" in w.message

w.ssh_computer_setup.username.value = "aiida"

w._on_quick_setup()

assert "Generating SSH key pair" in w.message
assert w.success

# test select new resource reset the widget, success trait, and message trait.
w.comp_resources_database._reset()
w.comp_resources_database.reset()

assert w.ssh_config == {}
assert w.computer_setup == {}
Expand Down

0 comments on commit 9e0deff

Please sign in to comment.