diff --git a/src/scwidgets/code/_widget_code_demo.py b/src/scwidgets/code/_widget_code_demo.py index d254b03..6af2b4a 100644 --- a/src/scwidgets/code/_widget_code_demo.py +++ b/src/scwidgets/code/_widget_code_demo.py @@ -3,7 +3,6 @@ from __future__ import annotations import types -from copy import deepcopy from platform import python_version from typing import Any, Callable, Dict, List, Optional, Union @@ -49,21 +48,22 @@ class CodeDemo(VBox, CheckableWidget): def __init__( self, - code: Union[CodeInput, types.FunctionType] = None, + code: Union[None, CodeInput, types.FunctionType] = None, check_registry: Optional[CheckRegistry] = None, - parameters: Optional[Union[Dict[str, Union[Check.FunInParamT, Widget]], - ParameterPanel]] = None, + parameters: Optional[ + Union[Dict[str, Union[Check.FunInParamT, Widget]], ParameterPanel] + ] = None, update_mode: str = "release", cue_outputs: Union[None, CueOutput, List[CueOutput]] = None, update_process: Optional[ - Callable[CodeDemo, Union[Any, Check.FunOutParamsT]] + Callable[[CodeDemo], Union[Any, Check.FunOutParamsT]] ] = None, *args, **kwargs, ): allowed_update_modes = ["manual", "continuous", "release"] if update_mode not in allowed_update_modes: - raise ValueError( + raise TypeError( f"Got update mode {update_mode!r} but only " f"{allowed_update_modes} are allowed." ) @@ -71,11 +71,41 @@ def __init__( self._update_process = update_process + # verify if input argument `parameter` is valid + if parameters is not None: + allowed_parameter_types = [dict, ParameterPanel] + parameter_type_allowed = False + for allowed_parameter_type in allowed_parameter_types: + if isinstance(parameters, allowed_parameter_type): + parameter_type_allowed = True + if not (parameter_type_allowed): + raise TypeError( + f"Got parameter {type(parameters)!r} but only " + f"{allowed_parameter_types} are allowed." + ) + + # verify if input argument `parameter` is valid if isinstance(code, types.FunctionType): code = CodeInput(function=code) + + # check compability between code and parameters + if code is not None and parameters is not None: + if isinstance(parameters, dict): + compatibility_result = code.compatible_with_signature( + list(parameters.keys()) + ) + elif isinstance(parameters, ParameterPanel): + compatibility_result = code.compatible_with_signature( + list(parameters.parameters.keys()) + ) + if compatibility_result != "": + raise ValueError( + "Code and parameters do no match: " + compatibility_result + ) + if cue_outputs is None: cue_outputs = [] - elif isinstance(cue_outputs, CueOutput): + elif not(isinstance(cue_outputs, list)): cue_outputs = [cue_outputs] CheckableWidget.__init__(self, check_registry) @@ -83,6 +113,11 @@ def __init__( self._code = code # TODO this needs to be settable self._output = CueOutput() + if isinstance(parameters, dict): + self._parameter_panel = ParameterPanel(**parameters) + elif isinstance(parameters, ParameterPanel): + self._parameter_panel = parameters + parameters = self._parameter_panel.parameters self._parameters = parameters self._cue_code = self._code self._cue_outputs = cue_outputs @@ -110,24 +145,6 @@ def __init__( self._update_button = None self._parameter_panel = VBox([]) else: - allowed_parameter_types = [dict, ParameterPanel] - parameter_type_allowed = False - for allowed_parameter_type in allowed_parameter_types: - if isinstance(ParameterPanel, allowed_parameter_type): - parameter_type_allowed = True - if parameter_type_allowed: - raise ValueError( - f"Got parameter {type(ParameterPanel)!r} but only " - f"{allowed_parameter_types} are allowed." - ) - if isinstance(self._parameters, dict): - if self._code is not None: - compatibility_result = self._code.compatible_with_signature( - list(self._parameters.keys()) - ) - if compatibility_result != "": - raise ValueError(compatibility_result) - # set up update button and cueing # ------------------------------- @@ -143,10 +160,6 @@ def __init__( # set up parameter panel # ---------------------- - if isinstance(self._parameters, ParameterPanel): - self._parameter_panel = self._parameters - else: - self._parameter_panel = ParameterPanel(**self._parameters) if self._update_mode == "continuous": self._parameter_panel.set_parameters_widget_attr( "continuous_update", True @@ -178,7 +191,7 @@ def __init__( cue_output._traits_to_observe = ["function_body"] cue_output.observe_widgets() - # TODO set this + # TODO set this self._output._widgets_to_observe = [self._code] self._output._traits_to_observe = ["function_body"] self._output.observe_widgets() @@ -211,8 +224,12 @@ def __init__( cue_output.observe_widgets() else: # TODO this has to be made public - cue_output._widgets_to_observe = self._parameter_panel.parameters_widget - cue_output._traits_to_observe = self._parameter_panel.parameters_trait + cue_output._widgets_to_observe = ( + self._parameter_panel.parameters_widget + ) + cue_output._traits_to_observe = ( + self._parameter_panel.parameters_trait + ) cue_output.observe_widgets() reset_update_cue_widgets = [] @@ -222,10 +239,12 @@ def __init__( reset_update_cue_widgets.extend(self._cue_outputs) if self._code is not None: - description="Run Code" - button_tooltip = "Runs the code and updates outputs with the specified parameters" + description = "Run Code" + button_tooltip = ( + "Runs the code and updates outputs with the specified parameters" + ) else: - description="Update" + description = "Update" button_tooltip = "Updates outputs with the specified parameters" self._update_button = UpdateResetCueButton( @@ -255,11 +274,13 @@ def __init__( demo_children = [] if self._code is not None: demo_children.append(self._cue_code) - demo_children.extend([ - self._cue_parameter_panel, - self._buttons_panel, - self._output, - ]) + demo_children.extend( + [ + self._cue_parameter_panel, + self._buttons_panel, + self._output, + ] + ) demo_children.extend(self._cue_outputs) VBox.__init__( @@ -335,7 +356,8 @@ def _on_click_update_action(self) -> bool: with self._output: try: for cue_output in self.cue_outputs: - cue_output.clear_display(wait=True) + if hasattr(cue_output, "clear_display"): + cue_output.clear_display(wait=True) if self._update_process is not None: self._update_process(self) @@ -343,7 +365,8 @@ def _on_click_update_action(self) -> bool: self.run_code(**self.panel_parameters) for cue_output in self.cue_outputs: - cue_output.draw_display() + if hasattr(cue_output, "draw_display"): + cue_output.draw_display() except CodeValidationError as e: raised_error = True @@ -355,7 +378,15 @@ def _on_click_update_action(self) -> bool: return not (raised_error) def run_code(self, *args, **kwargs) -> Check.FunOutParamsT: + """ + Runs the `code` with the given (keyword) arguments and returns the output of the + `code`. If no `code` was given on intialization, then a `ValueError` is raised. + """ try: + if self._code is None: + raise ValueError( + "run_code was invoked, but no code was given on initializaion" + ) return self._code.run(*args, **kwargs) except CodeValidationError as e: raise e @@ -363,5 +394,5 @@ def run_code(self, *args, **kwargs) -> Check.FunOutParamsT: # we give the student the additional information that this is most likely # not because of his code if python_version() >= "3.11": - e.add_note("This might might be not related to your code input.") + e.add_note("This might be not related to your code input.") raise e