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

Enable syncing dataframe parameters #6745

Merged
merged 2 commits into from
Apr 17, 2024
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
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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See pandas-dev/pandas#46705 for more context.

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)
Loading