Skip to content

Instantly share code, notes, and snippets.

@bsnacks000
Created June 10, 2020 15:08
Show Gist options
  • Select an option

  • Save bsnacks000/b6b7b4d62b1028c7b786b864f6e75969 to your computer and use it in GitHub Desktop.

Select an option

Save bsnacks000/b6b7b4d62b1028c7b786b864f6e75969 to your computer and use it in GitHub Desktop.
ipywidget wrapper
""" 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