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

Supporting streams parameter as a dictionary #4787

Merged
merged 14 commits into from
Jan 14, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ jobs:
run: |
eval "$(conda shell.bash hook)"
conda activate test-environment
coveralls
coveralls --service=github
16 changes: 13 additions & 3 deletions holoviews/core/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from .ndmapping import UniformNdMapping, NdMapping, item_check
from .overlay import Overlay, CompositeOverlay, NdOverlay, Overlayable
from .options import Store, StoreOptions
from ..streams import Stream
from ..streams import Stream, Params, streams_list_from_dict



Expand Down Expand Up @@ -885,7 +885,10 @@ class DynamicMap(HoloMap):
List of Stream instances to associate with the DynamicMap. The
set of parameter values across these streams will be supplied as
keyword arguments to the callback when the events are received,
updating the streams.""" )
updating the streams. Can also be supplied as a dictionary that
maps parameters or panel widgets to callback argument names that
will then be automatically converted to the equivalent list
format.""")

cache_size = param.Integer(default=500, doc="""
The number of entries to cache for fast access. This is an LRU
Expand All @@ -897,11 +900,13 @@ class DynamicMap(HoloMap):
If True, stream parameters are passed to callback as positional arguments.
Each positional argument is a dict containing the contents of a stream.
The positional stream arguments follow the positional arguments for each kdim,
and they are ordered to match the order of the DynamicMap's streams list.
and they are ordered to match the order of the DynamicMap's streams list.
""")

def __init__(self, callback, initial_items=None, streams=None, **params):
streams = (streams or [])
if isinstance(streams, dict):
streams = streams_list_from_dict(streams)

# If callback is a parameterized method and watch is disabled add as stream
if (params.get('watch', True) and (util.is_param_method(callback, has_deps=True) or
Expand Down Expand Up @@ -942,6 +947,11 @@ def __init__(self, callback, initial_items=None, streams=None, **params):
for stream in self.streams:
if stream.source is None:
stream.source = self
if isinstance(stream, Params):
for p in stream.parameters:
if isinstance(p.owner, Stream) and p.owner.source is None:
p.owner.source = self

self.periodic = periodic(self)

@property
Expand Down
17 changes: 16 additions & 1 deletion holoviews/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
server-side or in Javascript in the Jupyter notebook (client-side).
"""

import sys
import weakref
from numbers import Number
from collections import defaultdict
Expand Down Expand Up @@ -44,6 +45,20 @@ def triggering_streams(streams):
stream._triggering = False


def streams_list_from_dict(streams):
"Converts a streams dictionary into a streams list"
params = {}
for k, v in streams.items():
if 'panel' in sys.modules:
from panel.depends import param_value_if_widget
v = param_value_if_widget(v)
if isinstance(v, param.Parameter) and v.owner is not None:
params[k] = v
else:
raise TypeError('Cannot handle value %r in streams dictionary' % v)
return Params.from_params(params)


class Stream(param.Parameterized):
"""
A Stream is simply a parameterized object with parameters that
Expand Down Expand Up @@ -711,7 +726,7 @@ def from_params(cls, params, **kwargs):
for _, group in groupby(sorted(params.items(), key=key_fn), key_fn):
group = list(group)
inst = [p.owner for _, p in group][0]
if not isinstance(inst, param.Parameterized):
if inst is None:
continue
names = [p.name for _, p in group]
rename = {p.name: n for n, p in group}
Expand Down
27 changes: 26 additions & 1 deletion holoviews/tests/core/testdynamic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import uuid
import time
import sys
from collections import deque
from unittest import SkipTest

import param
import numpy as np
Expand Down Expand Up @@ -28,7 +30,8 @@
def sine_array(phase, freq):
return np.sin(phase + (freq*x**2+freq*y**2))


class TestParameters(param.Parameterized):
example = param.Number(default=1)

class DynamicMapConstructor(ComparisonTestCase):

Expand All @@ -50,6 +53,28 @@ def test_simple_constructor_invalid(self):
def test_simple_constructor_streams(self):
DynamicMap(lambda x: x, streams=[PointerX()])

def test_simple_constructor_streams_dict(self):
pointerx = PointerX()
DynamicMap(lambda x: x, streams=dict(x=pointerx.param.x))

def test_simple_constructor_streams_dict_panel_widget(self):
if 'panel' not in sys.modules:
raise SkipTest('Panel not available')
import panel
DynamicMap(lambda x: x, streams=dict(x=panel.widgets.FloatSlider()))

def test_simple_constructor_streams_dict_parameter(self):
test = TestParameters()
DynamicMap(lambda x: x, streams=dict(x=test.param.example))

def test_simple_constructor_streams_dict_class_parameter(self):
DynamicMap(lambda x: x, streams=dict(x=TestParameters.param.example))

def test_simple_constructor_streams_dict_invalid(self):
regexp = "Cannot handle value 3 in streams dictionary"
with self.assertRaisesRegexp(TypeError, regexp):
DynamicMap(lambda x: x, streams=dict(x=3))

def test_simple_constructor_streams_invalid_uninstantiated(self):
regexp = ("The supplied streams list contains objects "
"that are not Stream instances:(.+?)")
Expand Down
44 changes: 44 additions & 0 deletions holoviews/tests/teststreams.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,46 @@ def test(x):
inner.x = 10
self.assertEqual(dmap[()], Points([10]))


def test_param_instance_steams_dict(self):
inner = self.inner()

def test(x):
return Points([x])

dmap = DynamicMap(test, streams=dict(x=inner.param.x))

inner.x = 10
self.assertEqual(dmap[()], Points([10]))

def test_param_class_steams_dict(self):
class ClassParamExample(param.Parameterized):
x = param.Number(default=1)

def test(x):
return Points([x])

dmap = DynamicMap(test, streams=dict(x=ClassParamExample.param.x))

ClassParamExample.x = 10
self.assertEqual(dmap[()], Points([10]))

def test_panel_param_steams_dict(self):
try:
import panel
except:
raise SkipTest('Panel required for widget support in streams dict')
widget = panel.widgets.FloatSlider(value=1)

def test(x):
return Points([x])

dmap = DynamicMap(test, streams=dict(x=widget))

widget.value = 10
self.assertEqual(dmap[()], Points([10]))


def test_param_method_depends_no_deps(self):
inner = self.inner()
stream = ParamMethod(inner.method_no_deps)
Expand Down Expand Up @@ -1230,6 +1270,10 @@ def test_selection_expr_stream_hist_invert_xaxis_yaxis(self):

def test_selection_expr_stream_polygon_index_cols(self):
# Create SelectionExpr on element
try: import shapely # noqa
except:
try: import spatialpandas # noqa
except: raise SkipTest('Shapely required for polygon selection')
poly = Polygons([
[(0, 0, 'a'), (2, 0, 'a'), (1, 1, 'a')],
[(2, 0, 'b'), (4, 0, 'b'), (3, 1, 'b')],
Expand Down