Source code for shyft.dashboard.base.selector_presenter

from abc import abstractmethod
from enum import Enum
from typing import Optional, Union, List, Tuple
import logging

from shyft.dashboard.base.ports import (States, Sender, StatePorts, Receiver, connect_ports,
                                        connect_state_ports)
from shyft.dashboard.base.selector_views import SelectorViewBase
from shyft.dashboard.widgets.logger_box import LoggerBox

Option = Union[str, Tuple[str, str]]
Options = List[Option]


[docs] class SelectorPresenterError(RuntimeError): pass
[docs] class SelectorPresenter:
[docs] def __init__(self, name: str, view: SelectorViewBase, default: Option = '', logger=None) -> None: """ The presenter is used as connection between Model and View. The presenter is meant to be composed with a Model class as its attribute. Whereas the view is added to the presenter as attribute. E.i.: model.presenter.view The Model interacts with the class methods: - set_selector_value - set_selector_options - selector_options and the port function: - receive_state: to receive an the state of the model ACTIVE or DEACTIVE The selection is passed back to the view with the send_selection_to_model port. This needs to be connected to a receiver in the model class. Parameters ---------- name: name of the presenter - used in debug msg view: the view-instance the presenter should connect to default: default selection primarily used to be able to deselect the selector view, defaults to '' if you do not want an empty select option set default=None logger: logger instance """ self.logger = logger or logging.getLogger() if not isinstance(view, SelectorViewBase): raise SelectorPresenterError(f"SelectorPresenter {name}: View {view} is not of type SelectViewBase!") self.selector_view = view self.init_default = default or '' self.selector_values = [self.init_default] self.selected_values = [self.init_default] self.default = default self.options = [self.init_default] self.options_map = {} self.inverse_options_map = {} self.state = States.ACTIVE self.name = name # interface to model self.send_selection_to_model = Sender(parent=self, name='send selection to model', signal_type=List[str]) # interface to view self.send_view_selection = Sender(parent=self, name='set the selection of the view', signal_type=List[str]) self.send_view_options = Sender(parent=self, name='set the selection of the view', signal_type=List[str]) self.receive_view_selection = Receiver(parent=self, name='receive the selection of a view', func=self._receive_view_selection, signal_type=List[str]) self.state_ports = StatePorts(parent=self, _receive_state=self._receive_state) connect_ports(self.send_view_options, view.receive_options) connect_ports(self.send_view_selection, view.receive_selection) connect_ports(view.send_selection, self.receive_view_selection) connect_state_ports(self.state_ports, view.state_ports) # initialize self.set_selector_options(self._unpack_options(options=self.options), callback=False) self.set_selector_value(self.init_default)
@staticmethod def _unpack_options(*, options: Options, tuple_index: int = 1) -> List[str]: return [o[tuple_index] if isinstance(o, tuple) else o for o in options] def __dir__(self) -> List[str]: return ["selector_view", 'selector_values', 'set_selector_value', 'set_selector_options', 'selector_options', 'selected_values'] def __repr__(self) -> str: return f"SelectorPresenter '{self.name}' with view: {self.selector_view}" def _receive_state(self, state: States) -> None: if state != self.state: self.state = state # TODO: REMOVE THIS IF BOKEH VERSION IS UPGRADED AND DISABLE PROPERTY WORKS AGAIN if state == States.DEACTIVE: self.send_view_options([self.init_default]) # TODO: Commented this because of an error in the self.default variable being a tuple instead of a string, works now # self.set_view_selection([self.default]) if state == States.ACTIVE: self.send_view_options(self._unpack_options(options=self.options, tuple_index=1)) self.state_ports.send_state(state) def _receive_view_selection(self, new_selection: List[str]) -> None: self.selected_values = [self.options_map[s] for s in new_selection if s in self.options_map] self.send_selection_to_model(self.selected_values)
[docs] def set_selector_value(self, value: Union[str, List[str]], callback: bool = False) -> None: """ Method to set the value of the view if options are defined with (key, label), value here should be the key of the option Parameters ---------- value: value the presenter should set the view callback: if True, the callback is triggered and the selected value is turned back via the send_selection_to_model Port """ if isinstance(value, str): value = [value] options = self._unpack_options(options=self.selector_options, tuple_index=0) value = self._unpack_options(options=value, tuple_index=0) if not all(v in options for v in value): self.logger.debug(f"{self}: '{value}' is not in options of selector") return self.selector_values = [self.inverse_options_map[s] for s in value if s in self.inverse_options_map] self.selected_values = value self.send_view_selection(self.selector_values) if callback: self.send_selection_to_model(self.selected_values)
[docs] def set_selector_options(self, options: Optional[Options] = None, sort: bool = True, selected_value: Optional[Union[str, List[str]]] = None, sort_reverse: bool = False, callback: bool = True) -> None: """ Method to set the selector options Options is either List[str] or a List[Tuple[str, str]]. The Tuple[str, str] is equivalent to (key, label) where the label is shown in the selector, but the key is return to the model. Parameters ---------- options: options for the selector sort: if True sort the selector options alphabetically selected_value: value to set the selector, needs to be in the options sort_reverse: if True sort the selector options in reverse order callback: if True the selected value or default is set with triggering the callback """ if isinstance(options, list): options = options.copy() if sort: options = sorted(options, reverse=sort_reverse, key=lambda x: x[1] if isinstance(x, tuple) else x) if self.default in options: options.remove(self.default) if self.default is not None: options.insert(0, self.default) self.options = options self.options_map = {o[1] if isinstance(o, tuple) else o: o[0] if isinstance(o, tuple) else o for o in options} self.inverse_options_map = {o[0] if isinstance(o, tuple) else o: o[1] if isinstance(o, tuple) else o for o in options} self.send_view_options(self._unpack_options(options=options, tuple_index=1)) if selected_value: self.set_selector_value(selected_value, callback=callback) elif self.default is not None: self.set_selector_value(self.default, callback=callback) else: self.state_ports.receive_state(States.INVALID) self.logger.error(f'Presenter {self} received invalid options') self.set_selector_value(self.init_default, callback=False)
@property def selector_options(self) -> Options: if self.state == States.DEACTIVE: return [self.init_default] else: return [o for o in self.options]
# self._options = {o[0] if isinstance(o, tuple) else o: o for o in opts}
[docs] class TextInputPresenter: """ The TextInputPresenter connects a Model with a TextInput View. The presenter receives a string from the View, which then will be passed to the Model with the send_text_input_to_model port. """
[docs] def __init__(self, name: str, view, logger=None) -> None: self.logger = logger or logging.getLogger() self.text_input_view = view self.state = States.ACTIVE self.name = name self.view = view # interface to model self.send_text_input_to_model = Sender(parent=self, name='send text input to model', signal_type=str) # interface to view self.receive_view_text_input = Receiver(parent=self, name='receive the text input of a view', func=self._receive_view_text_input, signal_type=str) self.state_ports = StatePorts(parent=self, _receive_state=self._receive_state) connect_ports(view.send_text_input, self.receive_view_text_input) connect_state_ports(self.state_ports, view.state_port)
def _receive_state(self, state: States) -> None: """ Receives a state. :param state: a State object """ if state != self.state: self.state = state self.state_ports.send_state(state) def _receive_view_text_input(self, new_text: str) -> None: """ Method to respond to an active or deactive state :param state: state :return: None """ self.input_value = new_text self.send_text_input_to_model(self.input_value)