Created
June 10, 2020 15:08
-
-
Save bsnacks000/b6b7b4d62b1028c7b786b864f6e75969 to your computer and use it in GitHub Desktop.
ipywidget wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ Defines a generic interface for composable widgets. Panels are the view layer, Datastores are the model. | |
| Downloaders should fill the datastores with data. Panels should register observers to implement parts of the API. This could be | |
| as simple as downloading and displaying data, or calling custom APIs that call things. | |
| """ | |
| import abc | |
| import IPython | |
| import ipywidgets as widgets | |
| import inspect | |
| class PanelDatastore(object): | |
| _registered_fields = [] | |
| _registered_triggers = [] | |
| class _Triggers(object): | |
| """ A namespace used for triggering things between widget panels. | |
| """ | |
| def emit(self, name): | |
| t = getattr(self, name) | |
| t.click() | |
| def register_handler(self, name, handler): | |
| t = getattr(self, name) | |
| t.on_click(handler) | |
| def __init__(self): | |
| self._store = {} | |
| self._triggers = self._Triggers() | |
| # control the keys and types in the datastore | |
| self._registered_keys = [] | |
| self._registered_vals = [] | |
| # get bases... reverse so that object hierarchy is preserved... | |
| bases = reversed(inspect.getmro(self.__class__)) | |
| for b in bases: | |
| if hasattr(b, '_registered_fields'): | |
| for field in b._registered_fields: | |
| self._registered_keys.append(field[0]) | |
| self._registered_vals.append(field[1]) | |
| self._store[ field[0] ] = field[1]() | |
| if hasattr(b, '_registered_triggers'): | |
| for name in b._registered_triggers: | |
| self.register_trigger(name) | |
| def create_or_update(self, key, val, protect=True): | |
| if key not in self._registered_keys: | |
| raise KeyError('This datastore only excepts the keys: {}'.format(self._registered_fields)) | |
| key_idx = self._registered_keys.index(key) | |
| if not isinstance(val, self._registered_vals[key_idx]): | |
| raise ValueError('The value for this key must be an instance of {}'.format(self._registered_fields[key][1])) | |
| self._store[key] = val | |
| def register_trigger(self, name): | |
| trig = widgets.Button() | |
| setattr(self._triggers, name, trig) | |
| @property | |
| def registered_triggers(self): | |
| return self._registered_triggers | |
| @property | |
| def trigger(self): | |
| return self._triggers | |
| @property | |
| def store(self): | |
| return self._store.copy() | |
| class AbstractBasePanel(abc.ABC): | |
| """ We define an interface for a Panel as being composed of a datastore and optional | |
| downloaders. We register observers to interact with the data in some way and we must call | |
| display. Downloaders and other APIs get set on the subclass init | |
| All widget components should be registered within the subclasses init. The _render method | |
| gets overridden with components and must return a class that inherits from widgets.Layout. This is how the GUI will be | |
| displayed in the notebook. | |
| If handlers are used, they should be implemented on the class as private methods and then registered in `_register_observers`. | |
| These are used to interact with the datastore and downloaders, managing state changes in the view layer. | |
| See this https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html | |
| The public display method calls the `_render` and `_register_observers` methods and displays the result. | |
| """ | |
| def __init__(self, datastore): | |
| if not isinstance(datastore, PanelDatastore): | |
| raise ValueError('datastore must be passed set') | |
| self._datastore = datastore | |
| @property | |
| def datastore(self): | |
| return self._datastore | |
| @abc.abstractmethod | |
| def _render(self): | |
| pass | |
| def _register_observers(self): | |
| return | |
| def _initialize(self): | |
| return | |
| def display(self): | |
| """ Builds and displays the widget panel. | |
| """ | |
| self._initialize() | |
| layout = self._render() | |
| if not isinstance(layout, widgets.GridBox): | |
| raise ValueError('_render must return a subclass of widgets.GridBox. See https://ipywidgets.readthedocs.io/en/latest/examples/Layout%20Templates.html') | |
| self._register_observers() | |
| display(layout) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment