Skip to content

Commit

Permalink
Started callback refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Mar 9, 2019
1 parent 8e0ef61 commit 09ead8e
Show file tree
Hide file tree
Showing 19 changed files with 466 additions and 513 deletions.
2 changes: 1 addition & 1 deletion panel/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def _cleanup_panel(msg_id):
"""
if msg_id not in state._views:
return
viewable, model = state._views.pop(msg_id)
viewable, model, _, _ = state._views.pop(msg_id)
viewable._cleanup(model)


Expand Down
99 changes: 43 additions & 56 deletions panel/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,61 +36,22 @@ def __init__(self, *objects, **params):
objects = [panel(pane) for pane in objects]
super(Panel, self).__init__(objects=objects, **params)

def _link_params(self, model, params, doc, root, comm=None):
def set_value(*events):
msg = {event.name: event.new for event in events}
events = {event.name: event for event in events}

def update_model():
if 'objects' in msg:
old = events['objects'].old
msg['objects'] = self._get_objects(model, old, doc, root, comm)
for pane in old:
if pane not in self.objects:
pane._cleanup(root)
self._preprocess(root) #preprocess links between new elements
processed = self._process_param_change(msg)
model.update(**processed)

if comm:
update_model()
push(doc, comm)
elif state.curdoc:
update_model()
else:
doc.add_next_tick_callback(update_model)

ref = root.ref['id']
if ref not in self._callbacks:
watcher = self.param.watch(set_value, params)
self._callbacks[ref].append(watcher)
def _update_model(self, events, msg, root, model, doc, comm):
if self._rename['objects'] in msg:
old = events['objects'].old
msg[self._rename['objects']] = self._get_objects(model, old, doc, root, comm)
for pane in old:
if pane not in self.objects:
pane._cleanup(root)
model.update(**msg)
self._preprocess(root) #preprocess links between new elements

def _cleanup(self, root=None, final=False):
super(Panel, self)._cleanup(root, final)
if root is not None:
for p in self.objects:
p._cleanup(root, final)

def select(self, selector=None):
"""
Iterates over the Viewable and any potential children in the
applying the Selector.
Arguments
---------
selector: type or callable or None
The selector allows selecting a subset of Viewables by
declaring a type or callable function to filter by.
Returns
-------
viewables: list(Viewable)
"""
objects = super(Panel, self).select(selector)
for obj in self.objects:
objects += obj.select(selector)
return objects

def _get_objects(self, model, old_objects, doc, root, comm=None):
"""
Returns new child models for the layout while reusing unchanged
Expand All @@ -102,7 +63,7 @@ def _get_objects(self, model, old_objects, doc, root, comm=None):
pane = panel(pane)
self.objects[i] = pane
if pane in old_objects:
child = pane._models[root.ref['id']]
child, _ = pane._models[root.ref['id']]
else:
child = pane._get_model(doc, root, model, comm)
new_models.append(child)
Expand All @@ -115,9 +76,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None):
objects = self._get_objects(model, [], doc, root, comm)
props = dict(self._init_properties(), objects=objects)
model.update(**self._process_param_change(props))
params = [p for p in self.param if p != 'name']
self._models[root.ref['id']] = model
self._link_params(model, params, doc, root, comm)
self._models[root.ref['id']] = (model, parent)
self._link_props(model, self._linked_props, doc, root, comm)
return model

Expand Down Expand Up @@ -190,6 +149,30 @@ def __repr__(self, depth=0, max_depth=10):
objs=('%s' % spacer).join(objs), spacer=spacer
)

#----------------------------------------------------------------
# Public API
#----------------------------------------------------------------

def select(self, selector=None):
"""
Iterates over the Viewable and any potential children in the
applying the Selector.
Arguments
---------
selector: type or callable or None
The selector allows selecting a subset of Viewables by
declaring a type or callable function to filter by.
Returns
-------
viewables: list(Viewable)
"""
objects = super(Panel, self).select(selector)
for obj in self.objects:
objects += obj.select(selector)
return objects

def append(self, pane):
from .pane import panel
new_objects = list(self)
Expand Down Expand Up @@ -318,7 +301,7 @@ def _get_objects(self, model, old_objects, doc, root, comm=None):
pane = panel(pane, name=name)
self.objects[i] = pane
if pane in old_objects:
child = pane._models[root.ref['id']]
child, _ = pane._models[root.ref['id']]
else:
child = pane._get_model(doc, root, model, comm)
child = BkPanel(title=name, name=pane.name, child=child)
Expand Down Expand Up @@ -361,6 +344,10 @@ def __setitem__(self, index, panes):
new_objects[i], self._names[i] = self._to_object_and_name(pane)
self.objects = new_objects

#----------------------------------------------------------------
# Public API
#----------------------------------------------------------------

def append(self, pane):
new_object, new_name = self._to_object_and_name(pane)
new_objects = list(self)
Expand Down Expand Up @@ -415,11 +402,11 @@ class Spacer(Reactive):
_bokeh_model = BkSpacer

def _get_model(self, doc, root=None, parent=None, comm=None):
model = self._bokeh_model(**self._process_param_change(self._init_properties()))
properties = self._process_param_change(self._init_properties())
model = self._bokeh_model(**properties)
if root is None:
root = model
self._models[root.ref['id']] = model
self._link_params(model, ['width', 'height'], doc, root, comm)
self._models[root.ref['id']] = (model, parent)
return model


Expand Down
155 changes: 80 additions & 75 deletions panel/pane/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,39 +57,6 @@ class PaneBase(Reactive):

__abstract = True

@classmethod
def applies(cls, obj):
"""
Given the object return a boolean indicating whether the Pane
can render the object. If the priority of the pane is set to
None, this method may also be used to define a priority
depending on the object being rendered.
"""
return None

@classmethod
def get_pane_type(cls, obj):
if isinstance(obj, Viewable):
return type(obj)
descendents = []
for p in param.concrete_descendents(PaneBase).values():
priority = p.applies(obj) if p.priority is None else p.priority
if isinstance(priority, bool) and priority:
raise ValueError('If a Pane declares no priority '
'the applies method should return a '
'priority value specific to the '
'object type or False, but the %s pane '
'declares no priority.' % p.__name__)
elif priority is None or priority is False:
continue
descendents.append((priority, p))
pane_types = reversed(sorted(descendents, key=lambda x: x[0]))
for _, pane_type in pane_types:
applies = pane_type.applies(obj)
if isinstance(applies, bool) and not applies: continue
return pane_type
raise TypeError('%s type could not be rendered.' % type(obj).__name__)

def __init__(self, object, **params):
applies = self.applies(object)
if isinstance(applies, bool) and not applies:
Expand All @@ -99,6 +66,7 @@ def __init__(self, object, **params):
super(PaneBase, self).__init__(object=object, **params)
kwargs = {k: v for k, v in params.items() if k in Layoutable.param}
self.layout = self.default_layout(self, **kwargs)
self.param.watch(self._update_pane, 'object')

def __repr__(self, depth=0):
cls = type(self).__name__
Expand All @@ -119,57 +87,94 @@ def _get_root(self, doc, comm=None):
else:
root = self.layout._get_model(doc, comm=comm)
self._preprocess(root)
ref = root.ref['id']
state._views[ref] = (self, root, doc, comm)
return root

def _cleanup(self, root=None, final=False):
super(PaneBase, self)._cleanup(root, final)
if final:
self.object = None

def _update(self, model):
"""
If _updates=True this method is used to update an existing Bokeh
model instead of replacing the model entirely. The supplied model
should be updated with the current state.
If _updates=True this method is used to update an existing
Bokeh model instead of replacing the model entirely. The
supplied model should be updated with the current state.
"""
raise NotImplementedError

def _link_object(self, doc, root, parent, comm=None):
def _synced_params(self):
return [p for p in self.param if p not in ['object', 'name']]

def _update_object(self, old_model, doc, root, parent, comm):
if self._updates:
self._update(old_model)
else:
new_model = self._get_model(doc, root, parent, comm)
try:
index = parent.children.index(old_model)
except IndexError:
self.warning('%s pane model %s could not be replaced '
'with new model %s, ensure that the '
'parent is not modified at the same '
'time the panel is being updated.' %
(type(self).__name__, old_model, new_model))
else:
parent.children[index] = new_model

def _update_pane(self, event):
for ref, (model, parent) in self._models.items():
viewable, root, doc, comm = state._views[ref]
if comm or state.curdoc:
self._update_object(model, doc, root, parent, comm)
if comm:
push(doc, comm)
else:
cb = partial(self._update_model, model, doc, root, parent, comm)
doc.add_next_tick_callback(cb)

#----------------------------------------------------------------
# Public API
#----------------------------------------------------------------

@classmethod
def applies(cls, obj):
"""
Links the object parameter to the rendered Bokeh model, triggering
an update when the object changes.
Given the object return a boolean indicating whether the Pane
can render the object. If the priority of the pane is set to
None, this method may also be used to define a priority
depending on the object being rendered.
"""
ref = root.ref['id']
return None

def update_pane(change):
old_model = self._models[ref]
@classmethod
def get_pane_type(cls, obj):
"""
Returns the applicable Pane type given an object by resolving
the precedence of all types whose applies method declares that
the object is supported.
if self._updates:
# Pane supports model updates
def update_models():
self._update(old_model)
else:
# Otherwise replace the whole model
new_model = self._get_model(doc, root, parent, comm)
def update_models():
try:
index = parent.children.index(old_model)
except IndexError:
self.warning('%s pane model %s could not be replaced '
'with new model %s, ensure that the '
'parent is not modified at the same '
'time the panel is being updated.' %
(type(self).__name__, old_model, new_model))
else:
parent.children[index] = new_model

if comm:
update_models()
push(doc, comm)
elif state.curdoc:
update_models()
else:
doc.add_next_tick_callback(update_models)
Parameters
----------
obj (object): The object type to return a Pane for
if ref not in self._callbacks:
self._callbacks[ref].append(self.param.watch(update_pane, 'object'))
Returns
-------
The applicable Pane type with the highest precedence.
"""
if isinstance(obj, Viewable):
return type(obj)
descendents = []
for p in param.concrete_descendents(PaneBase).values():
priority = p.applies(obj) if p.priority is None else p.priority
if isinstance(priority, bool) and priority:
raise ValueError('If a Pane declares no priority '
'the applies method should return a '
'priority value specific to the '
'object type or False, but the %s pane '
'declares no priority.' % p.__name__)
elif priority is None or priority is False:
continue
descendents.append((priority, p))
pane_types = reversed(sorted(descendents, key=lambda x: x[0]))
for _, pane_type in pane_types:
applies = pane_type.applies(obj)
if isinstance(applies, bool) and not applies: continue
return pane_type
raise TypeError('%s type could not be rendered.' % type(obj).__name__)
Loading

0 comments on commit 09ead8e

Please sign in to comment.