From 44978bd51438fcf35275f5ccc309c1266780fc41 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 10 Apr 2019 12:48:39 +0100 Subject: [PATCH 1/2] Add support for passing in parameter instances as streams --- holoviews/streams.py | 41 +++++++++++++++++++++------------- holoviews/tests/teststreams.py | 38 ++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/holoviews/streams.py b/holoviews/streams.py index 0b439cc1d1..4543df8cb6 100644 --- a/holoviews/streams.py +++ b/holoviews/streams.py @@ -166,30 +166,41 @@ def trigger(cls, streams): def _on_trigger(self): """Called when a stream has been triggered""" + @classmethod def _process_streams(cls, streams): """ Processes a list of streams promoting Parameterized objects and methods to Param based streams. """ - param_watch_support = util.param_version >= '1.8.0' - parameterizeds = [s.parameterized for s in streams if isinstance(s, Params)] + parameterizeds = defaultdict(list) valid, invalid = [], [] for s in streams: - if not isinstance(s, Stream): - if isinstance(s, param.Parameterized) and param_watch_support: - if s not in parameterizeds: - s = Params(s) - else: - continue - elif util.is_param_method(s) and param_watch_support: - if not hasattr(s, "_dinfo") or util.get_method_owner(s) in parameterizeds: - continue - else: - s = ParamMethod(s) - else: - invalid.append(s) + if isinstance(s, Stream): + pass + elif isinstance(s, param.Parameter): + s = Params(s.owner, [s.name]) + elif isinstance(s, param.Parameterized): + s = Params(s) + elif util.is_param_method(s): + if not hasattr(s, "_dinfo"): continue + s = ParamMethod(s) + else: + invalid.append(s) + continue + if isinstance(s, Params): + pid = id(s.parameterized) + if pid in parameterizeds: + overlap = (set(s.parameters) & set(parameterizeds[pid])) + if overlap: + pname = type(s.parameterized).__name__ + raise ValueError('Found multiple Params streams ' + 'subscribing to the %s parameter(s) ' + 'on the %s object. Ensure that ' + 'you only subscribe to a parameter ' + 'once.' % (list(overlap), pname)) + parameterizeds[pid] += s.parameters valid.append(s) return valid, invalid diff --git a/holoviews/tests/teststreams.py b/holoviews/tests/teststreams.py index f0c2dd1869..dda36dc609 100644 --- a/holoviews/tests/teststreams.py +++ b/holoviews/tests/teststreams.py @@ -181,9 +181,6 @@ def test_class_value_update(self): class TestParamsStream(ComparisonTestCase): def setUp(self): - if LooseVersion(param.__version__) < '1.8.0': - raise SkipTest('Params stream requires param >= 1.8.0') - class Inner(param.Parameterized): x = param.Number(default = 0) @@ -223,6 +220,41 @@ def subscriber(**kwargs): inner.y = 2 self.assertEqual(values, [{'x': 2, 'y': 2}]) + def test_param_stream_instance_separate_parameters(self): + inner = self.inner() + + xparam = Params(inner, ['x']) + yparam = Params(inner, ['y']) + + valid, invalid = Stream._process_streams([xparam, yparam]) + self.assertEqual(len(valid), 2) + self.assertEqual(len(invalid), 0) + + def test_param_stream_instance_overlapping_parameters(self): + inner = self.inner() + + params1 = Params(inner) + params2 = Params(inner) + + with self.assertRaises(ValueError): + Stream._process_streams([params1, params2]) + + def test_param_parameter_instance_separate_parameters(self): + inner = self.inner() + + valid, invalid = Stream._process_streams([inner.param.x, inner.param.y]) + xparam, yparam = valid + + self.assertIs(xparam.parameterized, inner) + self.assertEqual(xparam.parameters, ['x']) + self.assertIs(yparam.parameterized, inner) + self.assertEqual(yparam.parameters, ['y']) + + def test_param_parameter_instance_overlapping_parameters(self): + inner = self.inner() + with self.assertRaises(ValueError): + Stream._process_streams([inner.param.x, inner.param.x]) + def test_param_stream_parameter_override(self): inner = self.inner(x=2) stream = Params(inner, parameters=['x']) From d97a9e4de19fb0d6990ace9c43ffbea331990fab Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 10 Apr 2019 18:18:07 +0100 Subject: [PATCH 2/2] Only warn on duplicated Params streams --- holoviews/streams.py | 23 ++++++++++++----------- holoviews/tests/teststreams.py | 13 ++++++++----- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/holoviews/streams.py b/holoviews/streams.py index 4543df8cb6..a3444d01e5 100644 --- a/holoviews/streams.py +++ b/holoviews/streams.py @@ -173,7 +173,7 @@ def _process_streams(cls, streams): Processes a list of streams promoting Parameterized objects and methods to Param based streams. """ - parameterizeds = defaultdict(list) + parameterizeds = defaultdict(set) valid, invalid = [], [] for s in streams: if isinstance(s, Stream): @@ -191,16 +191,17 @@ def _process_streams(cls, streams): continue if isinstance(s, Params): pid = id(s.parameterized) - if pid in parameterizeds: - overlap = (set(s.parameters) & set(parameterizeds[pid])) - if overlap: - pname = type(s.parameterized).__name__ - raise ValueError('Found multiple Params streams ' - 'subscribing to the %s parameter(s) ' - 'on the %s object. Ensure that ' - 'you only subscribe to a parameter ' - 'once.' % (list(overlap), pname)) - parameterizeds[pid] += s.parameters + overlap = (set(s.parameters) & parameterizeds[pid]) + if overlap: + pname = type(s.parameterized).__name__ + param.main.param.warning( + 'The %s parameter(s) on the %s object have ' + 'already been supplied in another stream. ' + 'Ensure that the supplied streams only specify ' + 'each parameter once, otherwise multiple ' + 'events will be triggered when the parameter ' + 'changes.' % (sorted(overlap), pname)) + parameterizeds[pid] |= set(s.parameters) valid.append(s) return valid, invalid diff --git a/holoviews/tests/teststreams.py b/holoviews/tests/teststreams.py index dda36dc609..0476b615d6 100644 --- a/holoviews/tests/teststreams.py +++ b/holoviews/tests/teststreams.py @@ -12,6 +12,9 @@ from holoviews.streams import * # noqa (Test all available streams) from holoviews.util import Dynamic +from .utils import LoggingComparisonTestCase + + def test_all_stream_parameters_constant(): all_stream_cls = [v for v in globals().values() if isinstance(v, type) and issubclass(v, Stream)] @@ -178,7 +181,7 @@ def test_class_value_update(self): -class TestParamsStream(ComparisonTestCase): +class TestParamsStream(LoggingComparisonTestCase): def setUp(self): class Inner(param.Parameterized): @@ -236,8 +239,8 @@ def test_param_stream_instance_overlapping_parameters(self): params1 = Params(inner) params2 = Params(inner) - with self.assertRaises(ValueError): - Stream._process_streams([params1, params2]) + Stream._process_streams([params1, params2]) + self.log_handler.assertContains('WARNING', "['x', 'y']") def test_param_parameter_instance_separate_parameters(self): inner = self.inner() @@ -252,8 +255,8 @@ def test_param_parameter_instance_separate_parameters(self): def test_param_parameter_instance_overlapping_parameters(self): inner = self.inner() - with self.assertRaises(ValueError): - Stream._process_streams([inner.param.x, inner.param.x]) + Stream._process_streams([inner.param.x, inner.param.x]) + self.log_handler.assertContains('WARNING', "['x']") def test_param_stream_parameter_override(self): inner = self.inner(x=2)