Skip to content

Restructure storage of parameters and widgets in ParameterPanel #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions src/scwidgets/code/_widget_parameter_panel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable, Dict, List, Union
from typing import Any, Callable, Dict, List, Union

from ipywidgets import Output, VBox, Widget, fixed, interactive
from traitlets.utils.sentinel import Sentinel
Expand Down Expand Up @@ -39,50 +39,69 @@ def dummy_function(**kwargs):
"Assumed that interactive returns an output as last child. "
"Parameter will be wrongly initialized if this is not True."
)
self._parameters_widget = list(self._interactive_widget.children[:-1])
super().__init__(self._parameters_widget)
# Because interact only keeps a list of the widgets we build a map
# so the params can be changed in arbitrary order.
# Last widget is an output that interact adds to the widgets.
self._param_to_widget_map = {
key: widget
for key, widget in zip(
parameters.keys(), self._interactive_widget.kwargs_widgets
)
}
super().__init__(self.panel_params_widget)

@property
def parameters_widget(self) -> List[Widget]:
return self._parameters_widget
def param_to_widget_map(self) -> dict[str, Widget]:
return self._param_to_widget_map

@property
def parameters_trait(self) -> List[str]:
return ["value"] * len(self._parameters_widget)
def panel_params_trait(self) -> List[str]:
return ["value"] * len(self.panel_params)

@property
def panel_params_widget(self) -> List[Widget]:
"""
:return: Only parameters that are tunable in the parameter panel are returned.
Fixed parameters are ignored.
"""
return [
widget
for widget in self._param_to_widget_map.values()
if not (isinstance(widget, fixed))
]

@property
def params(self) -> dict:
def params(self) -> Dict[str, Any]:
"""
:return: All parameters that were given on initialization are returned,
also including also fixed parameters.
"""
return self._interactive_widget.kwargs.copy()

@params.setter
def params(self, parameters: dict):
for i, key in enumerate(self._interactive_widget.kwargs.keys()):
self._interactive_widget.kwargs_widgets[i].value = parameters[key]
return {key: widget.value for key, widget in self._param_to_widget_map.items()}

@property
def panel_parameters(self) -> dict:
def panel_params(self) -> Dict[str, Any]:
"""
:return: Only parameters that are tunable in the parameter panel are returned.
Fixed parameters are ignored.
"""
return {
key: self._interactive_widget.kwargs_widgets[i].value
for i, key in enumerate(self._interactive_widget.kwargs.keys())
if not (isinstance(self._interactive_widget.kwargs_widgets[i], fixed))
key: widget.value
for key, widget in self._param_to_widget_map.items()
if not (isinstance(widget, fixed))
}

def update_params(self, new_params: Dict[str, Any]):
for key, value in new_params.items():
self.param_to_widget_map[key].value = value

def observe_parameters(
self,
handler: Callable[[dict], None],
trait_name: Union[str, Sentinel, List[str]],
notification_type: Union[None, str, Sentinel] = "change",
):
""" """
for widget in self._parameters_widget:
for widget in self.panel_params_widget:
widget.observe(handler, trait_name, notification_type)

def unobserve_parameters(
Expand All @@ -91,10 +110,10 @@ def unobserve_parameters(
trait_name: Union[str, Sentinel, List[str]],
notification_type: Union[None, str, Sentinel] = "change",
):
for widget in self._parameters_widget:
for widget in self.panel_params_widget:
widget.unobserve(handler, trait_name, notification_type)

def set_parameters_widget_attr(self, name: str, value):
for widget in self._parameters_widget:
for widget in self.panel_params_widget:
if hasattr(widget, name):
setattr(widget, name, value)
34 changes: 17 additions & 17 deletions src/scwidgets/exercise/_widget_code_exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ def __init__(
update_button_disable_during_action = True

self._cue_parameter_panel = UpdateCueBox(
self._parameter_panel.parameters_widget,
self._parameter_panel.parameters_trait, # type: ignore
self._parameter_panel.panel_params_widget,
self._parameter_panel.panel_params_trait, # type: ignore
self._parameter_panel,
)

Expand All @@ -311,25 +311,25 @@ def __init__(
# TODO this has to be made public
cue_output._widgets_to_observe = [
self._code
] + self._parameter_panel.parameters_widget
] + self._parameter_panel.panel_params_widget
# fmt: off
cue_output._traits_to_observe = (

[ # type: ignore[assignment]
"function_body"
]
+ self._parameter_panel.parameters_trait
+ self._parameter_panel.panel_params_trait
)
# fmt: on

cue_output.observe_widgets()
else:
# TODO this has to be made public
cue_output._widgets_to_observe = (
self._parameter_panel.parameters_widget
self._parameter_panel.panel_params_widget
)
cue_output._traits_to_observe = (
self._parameter_panel.parameters_trait # type: ignore[assignment] # noqa: E501
self._parameter_panel.panel_params_trait # type: ignore[assignment] # noqa: E501
)
cue_output.observe_widgets()
elif self._code is not None:
Expand Down Expand Up @@ -396,8 +396,10 @@ def __init__(
save_traits_to_observe.append("function_body")

if self._parameter_panel is not None:
save_widgets_to_observe.extend(self._parameter_panel.parameters_widget)
save_traits_to_observe.extend(self._parameter_panel.parameters_trait)
save_widgets_to_observe.extend(
self._parameter_panel.panel_params_widget
)
save_traits_to_observe.extend(self._parameter_panel.panel_params_trait)

if self._cue_code is not None:
self._cue_code = SaveCueBox(
Expand Down Expand Up @@ -535,7 +537,7 @@ def answer(self, answer: dict):
if answer["code"] is not None and self._code is not None:
self._code.function_body = answer["code"]
if answer["parameter_panel"] is not None and self._parameter_panel is not None:
self._parameter_panel.params = answer["parameter_panel"]
self._parameter_panel.update_params(answer["parameter_panel"])

if self._save_cue_box is not None:
self._save_cue_box.observe_widgets()
Expand All @@ -550,21 +552,19 @@ def panel_parameters(self) -> Dict[str, Check.FunInParamT]:
:return: Only parameters that are tunable in the parameter panel are returned.
Fixed parameters are ignored.
"""
if self._parameter_panel is not None:
parameter_panel = self._parameter_panel
return parameter_panel.panel_parameters
return {}
return (
{}
if self._parameter_panel is None
else self._parameter_panel.panel_parameters
)

@property
def params(self) -> Dict[str, Check.FunInParamT]:
"""
:return: All parameters that were given on initialization are returned,
also including also fixed parameters.
"""
if self._parameter_panel is not None:
parameter_panel = self._parameter_panel
return parameter_panel.params
return {}
return {} if self._parameter_panel is None else self._parameter_panel.params

@property
def exercise_title(self) -> Union[str, None]:
Expand Down
12 changes: 11 additions & 1 deletion tests/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,23 @@
from widget_code_input.utils import CodeValidationError

from scwidgets.check import Check, CheckRegistry, CheckResult
from scwidgets.code import CodeInput
from scwidgets.code import CodeInput, ParameterPanel
from scwidgets.cue import CueObject
from scwidgets.exercise import CodeExercise, ExerciseRegistry

from .test_check import multi_param_check, single_param_check


class TestParameterPanel:

def test_params(self):
from ipywidgets import fixed

panel = ParameterPanel(**{"x": (0, 1, 0.5), "y": (2, 3, 1), "z": fixed(5)})
assert panel.params == {"x": 0.0, "y": 2, "z": 5}
assert panel.panel_params == {"x": 0.0, "y": 2}


class TestCodeInput:
# fmt: off
@staticmethod
Expand Down
Loading