Skip to content

Commit

Permalink
Enable syncing dataframe parameters (#6745)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcSkovMadsen committed Apr 17, 2024
1 parent d7cbc08 commit 5f7136d
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 3 deletions.
11 changes: 11 additions & 0 deletions panel/io/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ def _pandas_hash(obj):
if len(obj) >= _PANDAS_ROWS_LARGE:
obj = obj.sample(n=_PANDAS_SAMPLE_SIZE, random_state=0)
try:
if isinstance(obj, pd.DataFrame):
return ((b"%s" % pd.util.hash_pandas_object(obj).sum())
+ (b"%s" % pd.util.hash_pandas_object(obj.columns).sum())
)
return b"%s" % pd.util.hash_pandas_object(obj).sum()
except TypeError:
# Use pickle if pandas cannot hash the object for example if
Expand Down Expand Up @@ -463,3 +467,10 @@ def server_clear(session_context):
pass

return wrapped_func

def is_equal(value, other)->bool:
"""Returns True if value and other are equal
Supports complex values like DataFrames
"""
return value is other or _generate_hash(value)==_generate_hash(other)
5 changes: 3 additions & 2 deletions panel/io/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..models.location import Location as _BkLocation
from ..reactive import Syncable
from ..util import edit_readonly, parse_query
from .cache import is_equal
from .document import create_doc_if_none_exists
from .state import state

Expand All @@ -24,7 +25,6 @@
from bokeh.server.contexts import BokehSessionContext
from pyviz_comms import Comm


class Location(Syncable):
"""
The Location component can be made available in a server context
Expand Down Expand Up @@ -164,9 +164,10 @@ def _update_synced(self, event: param.parameterized.Event = None) -> None:
except Exception:
pass
try:
equal = v == getattr(p, pname)
equal = is_equal(v, getattr(p, pname))
except Exception:
equal = False

if not equal:
mapped[pname] = v
try:
Expand Down
21 changes: 20 additions & 1 deletion panel/tests/io/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
diskcache = None
diskcache_available = pytest.mark.skipif(diskcache is None, reason="requires diskcache")

from panel.io.cache import _find_hash_func, cache
from panel.io.cache import (
_find_hash_func, _generate_hash, cache, is_equal,
)
from panel.io.state import set_curdoc, state
from panel.tests.util import serve_and_wait

Expand Down Expand Up @@ -339,3 +341,20 @@ def expensive_calculation(self, value):
assert model.expensive_calculation(2) == 4

assert model.executions == 2

DF1 = pd.DataFrame({"x": [1]})
DF2 = pd.DataFrame({"y": [1]})

def test_hash_on_simple_dataframes():
assert _generate_hash(DF1)!=_generate_hash(DF2)

@pytest.mark.parametrize(["value", "other", "expected"], [
(None, None, True),
(True, False, False), (False, True, False), (False, False, True), (True, True, True),
(None, 1, False), (1, None, False), (1, 1, True), (1,2,False),
(None, "a", False), ("a", None, False), ("a", "a", True), ("a","b",False),
(1,"1", False),
(None, DF1, False), (DF1, None, False), (DF1, DF1, True), (DF1, DF1.copy(), True), (DF1,DF2,False),
])
def test_is_equal(value, other, expected):
assert is_equal(value, other)==expected
24 changes: 24 additions & 0 deletions panel/tests/io/test_location.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pandas as pd
import param
import pytest

Expand Down Expand Up @@ -26,6 +27,8 @@ class SyncParameterized(param.Parameterized):

string = param.String(default=None)

dataframe = param.DataFrame(default=None)


def test_location_update_query(location):
location.update_query(a=1)
Expand Down Expand Up @@ -164,3 +167,24 @@ def app():

def test_iframe_srcdoc_location():
Location(pathname="srcdoc")

@pytest.fixture
def dataframe():
return pd.DataFrame({"x": [1]})

def test_location_sync_from_dataframe(location, dataframe):
p = SyncParameterized(dataframe=dataframe)
location.sync(p)
assert location.search == "?dataframe=%5B%7B%22x%22%3A+1%7D%5D"

def test_location_sync_to_dataframe(location, dataframe):
p = SyncParameterized()
location.search = "?dataframe=%5B%7B%22x%22%3A+1%7D%5D"
location.sync(p)
pd.testing.assert_frame_equal(p.dataframe, dataframe)

def test_location_sync_to_dataframe_with_initial_value(location, dataframe):
p = SyncParameterized(dataframe=pd.DataFrame({"y": [2]}))
location.search = "?dataframe=%5B%7B%22x%22%3A+1%7D%5D"
location.sync(p)
pd.testing.assert_frame_equal(p.dataframe, dataframe)

0 comments on commit 5f7136d

Please sign in to comment.