Skip to content

Commit

Permalink
Ensure that ReplacementPane does not modify user objects
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Jan 19, 2020
1 parent 860ed2c commit afdbaac
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 4 deletions.
32 changes: 29 additions & 3 deletions panel/pane/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ def __init__(self, object=None, **params):
if p not in self.param}
super(ReplacementPane, self).__init__(object, **params)
self._pane = Pane(None)
self._internal = True
self._inner_layout = Row(self._pane, **{k: v for k, v in params.items() if k in Row.param})

def _update_pane(self, *events):
Expand All @@ -326,7 +327,20 @@ def _update_inner(self, new_object):
links = Link.registry.get(new_object)
except TypeError:
links = []
if type(self._pane) is pane_type and not links:
custom_watchers = False
if isinstance(new_object, Reactive):
watch_fns = [
str(w.fn) for pwatchers in new_object._param_watchers.values()
for awatchers in pwatchers.values() for w in awatchers
]
custom_watchers = not all(
'Reactive._link_params' in wfn or 'PaneBase._update_pane' in wfn
for wfn in watch_fns
)

if type(self._pane) is pane_type and not links and not custom_watchers and self._internal:
# If the object has not external referrers we can update
# it inplace instead of replacing it
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()
Expand All @@ -338,8 +352,20 @@ def _update_inner(self, new_object):
# Replace pane entirely
kwargs = dict(self.get_param_values(), **self._kwargs)
del kwargs['object']
self._pane = Pane(new_object, **{k: v for k, v in kwargs.items()
if k in pane_type.param})
self._pane = panel(new_object, **{k: v for k, v in kwargs.items()
if k in pane_type.param})
if new_object is self._pane:
# If all watchers on the object are internal watchers
# we can make a clone of the object and update this
# clone going forward, otherwise we have replace the
# model entirely which is more expensive.
if not (custom_watchers or links):
self._pane = self._pane.clone()
self._internal = True
else:
self._internal = False
else:
self._internal = new_object is not self._pane
self._inner_layout[0] = self._pane

def _get_model(self, doc, root=None, parent=None, comm=None):
Expand Down
33 changes: 32 additions & 1 deletion panel/tests/test_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Div, Slider, Select, RangeSlider, MultiSelect, Row as BkRow,
CheckboxGroup, Toggle, Button, TextInput as BkTextInput,
Tabs as BkTabs, Column as BkColumn, TextInput)
from panel.pane import Pane, PaneBase, Matplotlib, Bokeh
from panel.pane import Pane, PaneBase, Matplotlib, Bokeh, HTML
from panel.layout import Tabs, Row
from panel.param import Param, ParamMethod, ParamFunction, JSONInit
from panel.widgets import LiteralInput
Expand Down Expand Up @@ -842,6 +842,37 @@ def view(a):
assert inner_pane._models == {}


def test_param_function_pane_update(document, comm):
test = View()

objs = {
0: HTML("012"),
1: HTML("123")
}

@param.depends(test.param.a)
def view(a):
return objs[a]

pane = Pane(view)
inner_pane = pane._pane
assert inner_pane is not objs[0]
assert inner_pane.object is objs[0].object
assert pane._internal

test.a = 1

assert pane._pane is inner_pane
assert pane._internal

objs[0].param.watch(print, ['object'])

test.a = 0

assert pane._pane is not inner_pane
assert not pane._internal


def test_get_param_method_pane_type():
assert PaneBase.get_pane_type(View().view) is ParamMethod

Expand Down

0 comments on commit afdbaac

Please sign in to comment.