From 6acbe0c52f1314ff1f5e66bf61fba2333d69427b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 29 Nov 2021 17:01:58 +0100 Subject: [PATCH 01/13] Always load imported bokeh extensions (#2957) --- panel/io/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/io/resources.py b/panel/io/resources.py index e281734276..27affc8147 100644 --- a/panel/io/resources.py +++ b/panel/io/resources.py @@ -123,7 +123,7 @@ def bundle_resources(roots, resources): css_files.extend(css_resources.css_files) css_raw.extend(css_resources.css_raw) - extensions = _bundle_extensions(roots, js_resources) + extensions = _bundle_extensions(None, js_resources) if mode == "inline": js_raw.extend([ Resources._inline(bundle.artifact_path) for bundle in extensions ]) elif mode == "server": From 94002adfc7398c7ecce09cf6452dd70049845cb5 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 29 Nov 2021 18:07:30 +0100 Subject: [PATCH 02/13] Do not run ace import on initialization (#2959) --- panel/models/ace.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/panel/models/ace.ts b/panel/models/ace.ts index b45552a9d5..1253d5b749 100644 --- a/panel/models/ace.ts +++ b/panel/models/ace.ts @@ -14,7 +14,6 @@ function ID() { export class AcePlotView extends PanelHTMLBoxView { model: AcePlot - protected _ace: any protected _editor: any protected _langTools: any protected _modelist: any @@ -22,7 +21,6 @@ export class AcePlotView extends PanelHTMLBoxView { initialize(): void { super.initialize() - this._ace = (window as any).ace this._container = div({ id: ID(), style: { @@ -51,9 +49,9 @@ export class AcePlotView extends PanelHTMLBoxView { if (!(this._container === this.el.childNodes[0])) this.el.appendChild(this._container) this._container.textContent = this.model.code - this._editor = this._ace.edit(this._container.id) - this._langTools = this._ace.require('ace/ext/language_tools') - this._modelist = this._ace.require("ace/ext/modelist") + this._editor = (window as any).ace.edit(this._container.id) + this._langTools = (window as any).ace.require('ace/ext/language_tools') + this._modelist = (window as any).ace.require("ace/ext/modelist") this._editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true, From b3926b78530335d8c828abd11a8490cc9cdbb00f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 30 Nov 2021 12:11:58 +0100 Subject: [PATCH 03/13] Fix regression rendering HoloViews plotly backend (#2961) * Fix regression rendering HoloViews plotly backend * Fix flake --- panel/pane/holoviews.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 38eb00636b..939e896a53 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -259,10 +259,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): kwargs = {p: v for p, v in self.param.get_param_values() if p in Layoutable.param and p != 'name'} - pane_type = self._panes.get(backend, Pane) - if 'tight' in pane_type.param: - kwargs['tight'] = True - child_pane = pane_type(state, **kwargs) + child_pane = self._get_pane(backend, state, **kwargs) self._update_plot(plot, child_pane) model = child_pane._get_model(doc, root, parent, comm) if ref in self._plots: @@ -273,6 +270,12 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[ref] = (model, parent) return model + def _get_pane(self, backend, state, **kwargs): + pane_type = self._panes.get(backend, Pane) + if isinstance(pane_type, type) and issubclass(pane_type, Matplotlib): + kwargs['tight'] = True + return pane_type(state, **kwargs) + def _render(self, doc, comm, root): import holoviews as hv from holoviews import Store, renderer as load_renderer From df6514f21b90cfb36180fbd3c1ead1545d786332 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 3 Dec 2021 17:21:15 +0100 Subject: [PATCH 04/13] Improve handling of ReactiveHTML cleanup (#2974) --- panel/reactive.py | 45 +++++++++++------------------------- panel/tests/test_reactive.py | 45 ++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/panel/reactive.py b/panel/reactive.py index fed3bb7d02..12b249a687 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -1262,19 +1262,12 @@ def __init__(self, **params): else: params[children_param] = panel(child_value) super().__init__(**params) + self._panes = {} self._event_callbacks = defaultdict(lambda: defaultdict(list)) def _cleanup(self, root): - for children_param in self._parser.children.values(): - children = getattr(self, children_param) - mode = self._child_config.get(children_param) - if mode != 'model': - continue - if isinstance(children, dict): - children = children.values() - elif not isinstance(children, list): - children = [children] - for child in children: + for child, panes in self._panes.items(): + for pane in panes: child._cleanup(root) super()._cleanup(root) @@ -1341,12 +1334,11 @@ def _get_events(self): node_events[e] = False return events - def _get_children(self, doc, root, model, comm, old_children=None): + def _get_children(self, doc, root, model, comm): from .pane import panel - old_children = old_children or {} old_models = model.children new_models = {parent: [] for parent in self._parser.children} - new_panes = {} + new_panes, internal_panes = {}, {} for parent, children_param in self._parser.children.items(): mode = self._child_config.get(children_param, 'model') @@ -1363,20 +1355,13 @@ def _get_children(self, doc, root, model, comm, old_children=None): else: panes = [panel(panes)] new_panes[parent] = panes + if isinstance(panes, dict): + panes = list(panes.values()) + internal_panes[children_param] = panes - for children_param, old_panes in old_children.items(): - mode = self._child_config.get(children_param, 'model') - if mode == 'literal': - continue - panes = getattr(self, children_param) - if not isinstance(panes, (list, dict)): - panes = [panes] - old_panes = [old_panes] - elif isinstance(panes, dict): - panes = panes.values() - old_panes = old_panes.values() + for children_param, old_panes in self._panes.items(): for old_pane in old_panes: - if old_pane not in panes and hasattr(old_pane, '_cleanup'): + if old_pane not in internal_panes.get(children_param, []): old_pane._cleanup(root) for parent, child_panes in new_panes.items(): @@ -1386,11 +1371,9 @@ def _get_children(self, doc, root, model, comm, old_children=None): mode = self._child_config.get(children_param, 'model') if mode == 'literal': new_models[parent] = children_param - elif children_param in old_children: + elif children_param in self._panes: # Find existing models - old_panes = old_children[children_param] - if not isinstance(old_panes, (list, dict)): - old_panes = [old_panes] + old_panes = self._panes[children_param] for i, pane in enumerate(child_panes): if pane in old_panes and root.ref['id'] in pane._models: child, _ = pane._models[root.ref['id']] @@ -1405,6 +1388,7 @@ def _get_children(self, doc, root, model, comm, old_children=None): pane._get_model(doc, root, model, comm) for pane in child_panes ] + self._panes = internal_panes return self._process_children(doc, root, model, comm, new_models) def _get_template(self): @@ -1556,10 +1540,9 @@ def _update_model(self, events, msg, root, model, doc, comm): else: data_msg[prop] = v if new_children: - old_children = {key: events[key].old for key in new_children} if self._parser.looped: model_msg['html'] = escape(self._get_template()) - children = self._get_children(doc, root, model, comm, old_children) + children = self._get_children(doc, root, model, comm) else: children = None if children is not None: diff --git a/panel/tests/test_reactive.py b/panel/tests/test_reactive.py index 964dd88fb2..a43ad0d17e 100644 --- a/panel/tests/test_reactive.py +++ b/panel/tests/test_reactive.py @@ -291,12 +291,14 @@ class TestChildren(ReactiveHTML): test = TestChildren(children=[widget]) root = test.get_root() assert root.children == {'div': [widget._models[root.ref['id']][0]]} + assert len(widget._models) == 1 + assert test._panes == {'children': [widget]} widget_new = TextInput() test.children = [widget_new] assert len(widget._models) == 0 assert root.children == {'div': [widget_new._models[root.ref['id']][0]]} - + assert test._panes == {'children': [widget_new]} def test_reactive_html_templated_children(): @@ -323,12 +325,52 @@ class TestTemplatedChildren(ReactiveHTML): root = test.get_root() assert root.looped == ['option'] assert root.children == {'option': [widget._models[root.ref['id']][0]]} + assert test._panes == {'children': [widget]} widget_new = TextInput() test.children = [widget_new] assert len(widget._models) == 0 assert root.children == {'option': [widget_new._models[root.ref['id']][0]]} + assert test._panes == {'children': [widget_new]} + + +def test_reactive_html_templated_dict_children(): + + class TestTemplatedChildren(ReactiveHTML): + + children = param.Dict(default={}) + + _template = """ +