From 4d4cd6b6f6648535a4bbff7af23489e8e2a179ee Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 23 Oct 2024 08:11:32 -0400 Subject: [PATCH] Check whether property value has changed after transform (#7432) * Check whether property value has changed after transform * Add test --- panel/reactive.py | 30 +++++++++++++++++++++++------- panel/tests/test_custom.py | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/panel/reactive.py b/panel/reactive.py index 81b0a076ba..6d1e96a780 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -1582,16 +1582,32 @@ def _process_param_change(self, params): def _set_on_model(self, msg: Mapping[str, Any], root: Model, model: Model) -> None: if not msg: return - old = self._changing.get(root.ref['id'], []) - self._changing[root.ref['id']] = [ - attr for attr, value in msg.items() - if not model.lookup(attr).property.matches(getattr(model, attr), value) - ] + prev_changing = self._changing.get(root.ref['id'], []) + changing = [] + transformed = {} + for attr, value in msg.items(): + prop = model.lookup(attr).property + old = getattr(model, attr) + try: + matches = bool(prop.matches(old, value)) + except Exception: + for tp, converter in prop.alternatives: + if tp.is_valid(value): + value = converter(value) + break + try: + matches = bool(prop.matches(old, value)) + except Exception: + matches = False + if not matches: + transformed[attr] = value + changing.append(attr) + self._changing[root.ref['id']] = changing try: - model.update(**msg) + model.update(**transformed) finally: if old: - self._changing[root.ref['id']] = old + self._changing[root.ref['id']] = prev_changing else: del self._changing[root.ref['id']] diff --git a/panel/tests/test_custom.py b/panel/tests/test_custom.py index 45ef95e0ae..97e32a1ea3 100644 --- a/panel/tests/test_custom.py +++ b/panel/tests/test_custom.py @@ -1,3 +1,5 @@ +import numpy as np +import pandas as pd import param from panel.custom import PyComponent, ReactiveESM @@ -44,6 +46,25 @@ def test_py_component_cleanup(document, comm): assert not spy._view__._models +class ESMDataFrame(ReactiveESM): + + df = param.DataFrame() + + +def test_reactive_esm_sync_dataframe(document, comm): + esm_df = ESMDataFrame() + + model = esm_df.get_root(document, comm) + + esm_df.df = pd.DataFrame({"1": [2]}) + + assert isinstance(model.data.df, dict) + assert len(model.data.df) == 2 + expected = {"index": np.array([0]), "1": np.array([2])} + for col, values in model.data.df.items(): + np.testing.assert_array_equal(values, expected.get(col)) + + class ESMWithChildren(ReactiveESM): child = param.ClassSelector(class_=Viewable)