Source code for scwidgets.check._widget_check_registry

# postpones evaluation of annotations
# see https://stackoverflow.com/a/33533514
from __future__ import annotations

from collections import OrderedDict
from typing import Callable, List, Optional, Union

from ipywidgets import Button, HBox, Layout, Output, VBox, Widget

from .._utils import Formatter
from ..css_style import CssStyle
from ._check import Check, CheckResult


[docs] class CheckableWidget: """ A base class for any widget to inherit from to be compatible with the :py:class:`CheckRegistry`. The logic is th :param check_registry: the check registry that registers the checks for this widget :param name: Optional name of the widget that is shown in the messages of the checks """ def __init__( self, check_registry: Optional[CheckRegistry], name: Optional[str] = None ): self._check_registry = check_registry if self._check_registry is not None: self._check_registry.register_widget(self, name)
[docs] def compute_output_to_check( self, *input_args: Check.FunInParamT ) -> Check.FunOutParamsT: """ The widget returns the output that will verified by the added checks. """ raise NotImplementedError("compute_output_to_check has not been implemented")
[docs] def handle_checks_result( self, results: List[Union[CheckResult, Exception]] ) -> None: """ Function that controls how results of the checks are handled. """ raise NotImplementedError("handle_checks_result has not been implemented")
def add_check(self, *args, **kwargs): # simple dispatch logic if len(args) + len(kwargs) == 1: self._add_check_from_check(*args, **kwargs) else: self._add_check_from_check_parameters(*args, **kwargs) def _add_check_from_check(self, checks: Union[List[Check], Check]): if self._check_registry is None: raise ValueError( "No check registry given on initialization, no checks can be added" ) if isinstance(checks, Check): checks = [checks] for check in checks: self._check_registry.add_check( self, check.asserts, check.inputs_parameters, check.outputs_references, check.fingerprint, ) def _add_check_from_check_parameters( self, asserts: Union[List[Check.AssertFunT], Check.AssertFunT], inputs_parameters: Optional[Union[List[dict], dict]] = None, outputs_references: Optional[ Union[List[Check.FunOutParamsT], Check.FunOutParamsT] ] = None, fingerprint: Optional[ Callable[[Check.FunOutParamsT], Check.FingerprintT] ] = None, suppress_fingerprint_asserts: bool = True, ): if self._check_registry is None: raise ValueError( "No check registry given on initialization, no checks can be added" ) self._check_registry.add_check( self, asserts, inputs_parameters, outputs_references, fingerprint, suppress_fingerprint_asserts, ) def compute_and_set_references(self): if self._check_registry is None: raise ValueError( "No check registry given on initialization, " "compute_and_set_references cannot be used" ) self._check_registry.compute_and_set_references(self) def check(self) -> List[Union[CheckResult, Exception]]: if self._check_registry is None: raise ValueError( "No check registry given on initialization, " "check cannot be used" ) return self._check_registry.check_widget(self) @property def checks(self): if self._check_registry is None: raise ValueError( "No check registry given on initialization, " "no checks to access" ) return self._check_registry._checks[self] @property def check_registry(self): return self._check_registry @property def nb_conducted_asserts(self): if self._check_registry is None: raise ValueError( "No check registry given on initialization, " "no checks to access" ) return self._check_registry.nb_conducted_asserts(self)
[docs] class CheckRegistry(VBox): """ Manages the assignment of checks to widgets and the execution of checks. It allows to run the checks of all widgets and properly pipes the result to the corresponding function of the widget. """ def __init__(self, *args, **kwargs): self._checks = OrderedDict() self._names = OrderedDict() self._set_all_references_button = Button(description="Set all references") self._check_all_widgets_button = Button(description="Check all widgets") self._output = Output() kwargs["layout"] = kwargs.pop("layout", Layout(width="100%")) self._buttons_hbox = HBox() # needs to be after the _buttons_hbox already was created self.display_set_all_references_button = kwargs.pop( "display_set_all_references_button", False ) VBox.__init__( self, [ CssStyle(), self._buttons_hbox, self._output, ], *args, **kwargs, ) self._set_all_references_button.on_click( self._on_click_set_all_references_button ) self._check_all_widgets_button.on_click(self._on_click_check_all_widgets_button) @property def checks(self): """ all registerd checks from widgets to checks """ return self._checks @property def display_set_all_references_button(self) -> bool: return self._display_set_all_references_button @display_set_all_references_button.setter def display_set_all_references_button(self, value: bool): if value: self._display_set_all_references_button = True self._buttons_hbox.children = ( self._check_all_widgets_button, self._set_all_references_button, ) else: self._display_set_all_references_button = False self._buttons_hbox.children = (self._check_all_widgets_button,) @property def registered_widgets(self): return self._widgets.copy()
[docs] def nb_conducted_asserts(self, widget: CheckableWidget): """ The total number of asserts that will be conducted for the widget :param widget: the checks of the widget are targeted """ return sum([check.nb_conducted_asserts for check in self._checks[widget]])
def register_widget(self, widget: CheckableWidget, name: Optional[str] = None): self._checks[widget] = [] if name is None: self._names[widget] = len(self._names) + 1 else: self._names[widget] = name def add_check( self, widget: CheckableWidget, asserts: Union[List[Check.AssertFunT], Check.AssertFunT], inputs_parameters: Optional[Union[List[dict], dict]] = None, outputs_references: Optional[ Union[List[Check.FunOutParamsT], Check.FunOutParamsT] ] = None, fingerprint: Optional[ Callable[[Check.FunOutParamsT], Check.FingerprintT] ] = None, suppress_fingerprint_asserts: bool = True, ): if not (issubclass(type(widget), CheckableWidget)): raise ValueError("Argument widget must be subclass of CheckableWidget") if widget not in self._checks.keys(): raise ValueError( "Argument widget must be first registered before checks can be added." ) check = Check( widget.compute_output_to_check, asserts, inputs_parameters, outputs_references, fingerprint, suppress_fingerprint_asserts, ) self._checks[widget].append(check) def compute_and_set_references(self, widget: Widget): for check in self._checks[widget]: try: check.compute_and_set_references() except Exception as exception: widget.handle_checks_result([exception]) raise exception def compute_outputs(self, widget: CheckableWidget): for check in self._checks[widget]: try: return check.compute_outputs() except Exception as exception: widget.handle_checks_result([exception]) raise exception def compute_and_set_all_references(self): for widget in self._checks.keys(): self.compute_and_set_references(widget) def check_widget( self, widget: CheckableWidget ) -> List[Union[CheckResult, Exception]]: checks_result = [] try: for check in self._checks[widget]: result = check.check_function() checks_result.append(result) widget.handle_checks_result(checks_result) return checks_result except Exception as exception: checks_result.append(exception) widget.handle_checks_result(checks_result) return checks_result def check_all_widgets( self, ) -> OrderedDict[CheckableWidget, List[Union[CheckResult, Exception]]]: messages: OrderedDict[CheckableWidget, List[Union[CheckResult, Exception]]] = ( OrderedDict() ) for widget in self._checks.keys(): try: messages[widget] = self.check_widget(widget) except Exception as exception: messages[widget] = [exception] return messages def _on_click_set_all_references_button(self, change: dict): self._output.clear_output(wait=True) with self._output: self.compute_and_set_all_references() print(Formatter.color_success_message("Successfully set all references.")) def _on_click_check_all_widgets_button(self, change: dict): self._output.clear_output(wait=True) try: # we raise the error within in the output so we can iterate through all # widget results even when an exception is raised # to prevent silent exceptions raised before the output in check_all_widgets # we wrap it in a try-catch block widgets_results = self.check_all_widgets() for widget, widget_results in widgets_results.items(): with self._output: if wrong_types := [ result for result in widget_results if not ( isinstance(result, Exception) or isinstance(result, CheckResult) ) ]: raise ValueError( f"Not supported result type {type(wrong_types[0])}. " "Only results of type `Exception` and `CheckResult` " "are supported." ) elif [ result for result in widget_results if isinstance(result, Exception) ]: print( Formatter.color_error_message( f"Widget {self._names[widget]}: ‼ (error)" ) ) elif not [ result for result in widget_results if isinstance(result, CheckResult) and not result.successful ]: print( Formatter.color_success_message( f"Widget {self._names[widget]}: ✓ (success)" ) ) else: print( Formatter.color_error_message( f"Widget {self._names[widget]}: 𐄂 (failed)" ) ) except Exception as exception: with self._output: print( Formatter.color_error_message( "Error raised while checking widgets:" ), exception, ) raise exception