Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor model and root handling #259

Merged
merged 7 commits into from
Mar 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions panel/holoviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ def _render(self, doc, comm, root):
return plot

def _get_model(self, doc, root=None, parent=None, comm=None):
"""
Should return the Bokeh model to be rendered.
"""
if root is None:
return self._get_root(doc, comm)
ref = root.ref['id']
plot = self._render(doc, comm, root)
child_pane = Pane(plot.state, _temporary=True)
Expand Down
3 changes: 1 addition & 2 deletions panel/interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,7 @@ def find_abbreviations(self, kwargs):
return new_kwargs

def _get_model(self, doc, root=None, parent=None, comm=None):
layout = self._inner_layout._get_model(doc, root, parent, comm)
return layout
return self._inner_layout._get_model(doc, root, parent, comm)

def _link_widgets(self):
if self.manual_update:
Expand Down
16 changes: 6 additions & 10 deletions panel/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ def _get_objects(self, model, old_objects, doc, root, comm=None):

def _get_model(self, doc, root=None, parent=None, comm=None):
model = self._bokeh_model()
root = model if root is None else root
if root is None:
root = model
objects = self._get_objects(model, [], doc, root, comm)
props = dict(self._init_properties(), objects=objects)
model.update(**self._process_param_change(props))
Expand Down Expand Up @@ -326,29 +327,24 @@ class Spacer(Reactive):

_bokeh_model = BkSpacer

def _get_root(self, doc, comm=None):
root = BkRow()
model = self._get_model(doc, root, root, comm=comm)
root.children.append(model)
self._preprocess(root)
return root

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


class VSpacer(Reactive):
class VSpacer(Spacer):
"""
Spacer which automatically fills all available vertical space.
"""

sizing_mode = param.Parameter(default='stretch_height', readonly=True)


class HSpacer(Reactive):
class HSpacer(Spacer):
"""
Spacer which automatically fills all available horizontal space.
"""
Expand Down
40 changes: 21 additions & 19 deletions panel/pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

import param

from bokeh.models import (LayoutDOM, CustomJS, Widget as _BkWidget,
Div as _BkDiv, Column as _BkColumn)
from bokeh.models import LayoutDOM, CustomJS, Div as _BkDiv

from .layout import Panel, Row
from .util import basestring, param_reprs, push, remove_root
Expand Down Expand Up @@ -46,7 +45,7 @@ def panel(obj, **kwargs):
if kwargs.get('name', False) is None:
kwargs.pop('name')
pane = PaneBase.get_pane_type(obj)(obj, **kwargs)
if len(pane.layout) == 1:
if len(pane.layout) == 1 and pane._unpack:
return pane.layout[0]
return pane.layout

Expand Down Expand Up @@ -89,6 +88,9 @@ class PaneBase(Reactive):
# Declares whether Pane supports updates to the Bokeh model
_updates = False

# Whether the Pane layout can be safely unpacked
_unpack = True

__abstract = True

@classmethod
Expand Down Expand Up @@ -124,13 +126,6 @@ def get_pane_type(cls, obj):
return pane_type
raise TypeError('%s type could not be rendered.' % type(obj).__name__)

def __repr__(self, depth=0):
cls = type(self).__name__
params = param_reprs(self, ['object'])
obj = type(self.object).__name__
template = '{cls}({obj}, {params})' if params else '{cls}({obj})'
return template.format(cls=cls, params=', '.join(params), obj=obj)

def __init__(self, object, **params):
applies = self.applies(object)
if isinstance(applies, bool) and not applies:
Expand All @@ -141,14 +136,24 @@ def __init__(self, object, **params):
kwargs = {k: v for k, v in params.items() if k in Layoutable.param}
self.layout = self.default_layout(self, **kwargs)

def __repr__(self, depth=0):
cls = type(self).__name__
params = param_reprs(self, ['object'])
obj = type(self.object).__name__
template = '{cls}({obj}, {params})' if params else '{cls}({obj})'
return template.format(cls=cls, params=', '.join(params), obj=obj)

def __getitem__(self, index):
"""
Allows pane objects to behave like the underlying layout
"""
return self.layout[index]

def _get_root(self, doc, comm=None):
root = self.layout._get_model(doc, comm=comm)
if self._updates:
root = self._get_model(doc, comm=comm)
else:
root = self.layout._get_model(doc, comm=comm)
self._preprocess(root)
return root

Expand Down Expand Up @@ -219,15 +224,10 @@ def applies(cls, obj):
return isinstance(obj, LayoutDOM)

def _get_model(self, doc, root=None, parent=None, comm=None):
"""
Should return the Bokeh model to be rendered.
"""
model = self.object
if isinstance(model, _BkWidget):
box_kws = {k: getattr(model, k) for k in ['width', 'height', 'sizing_mode']
if k in model.properties()}
model = _BkColumn(model, **box_kws)
if root is None:
return self._get_root(doc, comm)

model = self.object
ref = root.ref['id']
for js in model.select({'type': CustomJS}):
js.code = js.code.replace(model.ref['id'], ref)
Expand Down Expand Up @@ -264,6 +264,8 @@ def _get_properties(self):

def _get_model(self, doc, root=None, parent=None, comm=None):
model = _BkDiv(**self._get_properties())
if root is None:
root = model
self._models[root.ref['id']] = model
self._link_object(doc, root, parent, comm)
return model
Expand Down
48 changes: 30 additions & 18 deletions panel/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

from .pane import Pane, PaneBase
from .layout import Row, Panel, Tabs, Column
from .links import Link
from .util import (
abbreviated_repr, basestring, default_label_formatter, full_groupby,
get_method_owner, is_parameterized, param_name)
from .viewable import Layoutable, Reactive
from .widgets import (
LiteralInput, Select, Checkbox, FloatSlider, IntSlider, RangeSlider,
MultiSelect, StaticText, Button, Toggle, TextInput, DiscreteSlider,
Expand Down Expand Up @@ -58,6 +60,9 @@ class Param(PaneBase):
display_threshold = param.Number(default=0, precedence=-10, doc="""
Parameters with precedence below this value are not displayed.""")

default_layout = param.ClassSelector(default=Column, class_=Panel,
is_instance=False)

default_precedence = param.Number(default=1e-8, precedence=-10, doc="""
Precedence value to use for parameters with no declared
precedence. By default, zero predecence is available for
Expand Down Expand Up @@ -105,6 +110,8 @@ class Param(PaneBase):

priority = 0.1

_unpack = True

_mapping = {
param.Action: Button,
param.Parameter: LiteralInput,
Expand All @@ -121,6 +128,10 @@ class Param(PaneBase):
param.Date: DatetimeInput,
}

@classmethod
def applies(cls, obj):
return (is_parameterized(obj) or isinstance(obj, param.parameterized.Parameters))

def __init__(self, object, **params):
if isinstance(object, param.parameterized.Parameters):
object = object.cls if object.self is None else object.self
Expand All @@ -133,24 +144,19 @@ def __init__(self, object, **params):
# Construct widgets
self._widgets = self._get_widgets()
widgets = [widget for widgets in self._widgets.values() for widget in widgets]
self._widget_box = Column(*widgets, height=self.height,
width=self.width, name=self.name)

# Construct Layout
kwargs = {'name': self.name}
if self.expand_layout is Tabs:
kwargs['width'] = self.width
kwargs = {p: v for p, v in self.get_param_values() if p in Layoutable.param}
self._widget_box = self.default_layout(*widgets, **kwargs)

layout = self.expand_layout
if isinstance(layout, Panel):
self._expand_layout = layout
self.layout = self._widget_box
elif isinstance(self._widget_box, layout):
self.layout = self._widget_box
self._expand_layout = self.layout
self.layout = self._expand_layout = self._widget_box
elif isinstance(layout, type) and issubclass(layout, Panel):
self.layout = layout(self._widget_box, **kwargs)
self._expand_layout = self.layout
self.layout = self._expand_layout = layout(self._widget_box, **kwargs)
else:
raise ValueError('expand_layout expected to be a panel.layout.Panel'
'type or instance, found %s type.' %
Expand Down Expand Up @@ -236,10 +242,6 @@ def update_pane(change, parameter=pname):
else:
toggle_pane(namedtuple('Change', 'new')(True))

@classmethod
def applies(cls, obj):
return (is_parameterized(obj) or isinstance(obj, param.parameterized.Parameters))

def widget_type(cls, pobj):
ptype = type(pobj)
for t in classlist(ptype)[::-1]:
Expand Down Expand Up @@ -377,10 +379,14 @@ def _get_widgets(self):
widgets += [(pname, self.widget(pname)) for pname in ordered_params]
return OrderedDict(widgets)

def _get_root(self, doc, comm=None):
root = self.layout._get_root(doc, comm)
self._models[root.ref['id']] = root
return root

def _get_model(self, doc, root=None, parent=None, comm=None):
model = self.layout._get_model(doc, root, parent, comm)
if root:
self._models[root.ref['id']] = model
self._models[root.ref['id']] = model
return model


Expand Down Expand Up @@ -442,8 +448,12 @@ def update_pane(*events):
# Try updating existing pane
new_object = self.object()
pane_type = self.get_pane_type(new_object)
if type(self._pane) is pane_type:
if isinstance(new_object, (Panel, PaneBase)):
try:
links = Link.registry.get(new_object)
except TypeError:
links = []
if type(self._pane) is pane_type and not links:
if isinstance(new_object, Reactive):
pvals = dict(self._pane.get_param_values())
new_params = {k: v for k, v in new_object.get_param_values()
if k != 'name' and v is not pvals[k]}
Expand All @@ -470,8 +480,10 @@ def update_pane(*events):
watcher = pobj.param.watch(update_pane, ps, p.what)
self._callbacks[ref].append(watcher)


def _get_model(self, doc, root=None, parent=None, comm=None):
if root is None:
return self._get_root(doc, comm)

ref = root.ref['id']
if ref in self._callbacks:
self._cleanup(root)
Expand Down
4 changes: 3 additions & 1 deletion panel/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _to_figure(self, obj, layout={}):
fig = go.Figure(data=data, layout=layout)
return fig

def _get_model(self, doc, root, parent=None, comm=None):
def _get_model(self, doc, root=None, parent=None, comm=None):
"""
Should return the bokeh model to be rendered.
"""
Expand All @@ -72,6 +72,8 @@ def _get_model(self, doc, root, parent=None, comm=None):
data[key] = trace.pop(key)
sources.append(ColumnDataSource(data))
model = PlotlyPlot(data=json, data_sources=sources)
if root is None:
root = model
self._models[root.ref['id']] = model
self._link_object(doc, root, parent, comm)
return model
Expand Down
8 changes: 3 additions & 5 deletions panel/tests/test_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,17 +175,15 @@ def test(a):

column = interact_pane.layout._get_model(document, comm=comm)
assert isinstance(column, BkColumn)
box = column.children[1].children[0]
assert isinstance(box, BkColumn)
div = box.children[0]
div = column.children[1].children[0]
assert isinstance(div, BkDiv)
assert div.text == 'Test'
assert len(interact_pane._callbacks['instance']) == 1
assert column.ref['id'] in pane._callbacks
assert pane._models[column.ref['id']] is box
assert pane._models[column.ref['id']] is div

widget.value = True
assert box.ref['id'] not in pane._callbacks
assert column.ref['id'] not in pane._callbacks
assert pane._callbacks == {}
new_pane = interact_pane._pane
assert new_pane is not pane
Expand Down
Loading