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

Preprocessor refactor #1232

Merged
merged 15 commits into from Mar 29, 2017
Merged
Changes from 14 commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+119 −83
Diff settings

Always

Just for now

Copy path View file
@@ -10,60 +10,6 @@
from .core import util


class Preprocessor(param.Parameterized):
"""
A Preprocessor is a callable that takes a dictionary as an argument
and returns a dictionary. Where possible, Preprocessors should have
valid reprs that can be evaluated.
Preprocessors are used to set the contents of a stream based on the
parameter values. They may be used for debugging purposes or to
remap or repack parameter values before they are passed onto to the
subscribers.
"""

def __call__(self, params):
return params



class Rename(Preprocessor):
"""
A preprocessor used to rename parameter values.
"""

mapping = param.Dict(default={}, doc="""
The mapping from the parameter names to the designated names""")

def __init__(self, **mapping):
super(Rename, self).__init__(mapping=mapping)

def __call__(self, params):
return {self.mapping.get(k,k):v for (k,v) in params.items()}

def __repr__(self):
keywords = ','.join('%s=%r' % (k,v) for (k,v) in sorted(self.mapping.items()))
return 'Rename(%s)' % keywords



class Group(Preprocessor):
"""
A preprocessor that keeps the parameter dictionary together,
supplying it as a value associated with the given key.
"""

def __init__(self, key):
super(Group, self).__init__(key=key)

def __call__(self, params):
return {self.key:params}

def __repr__(self):
return 'Group(%r)' % self.key



class Stream(param.Parameterized):
"""
A Stream is simply a parameterized object with parameters that
@@ -115,7 +61,7 @@ def trigger(cls, streams):
stream.deactivate()


def __init__(self, preprocessors=[], source=None, subscribers=[],
def __init__(self, rename={}, source=None, subscribers=[],

This comment has been minimized.

Copy link
@philippjfr

philippjfr Mar 29, 2017

Contributor

Did none of the docstrings mention preprocessors? Either way docstrings should now mention the rename argument.

This comment has been minimized.

Copy link
@jlstevens

jlstevens Mar 29, 2017

Author Contributor

Fixed in 65131a2

linked=True, **params):
"""
Mapping allows multiple streams with similar event state to be
@@ -130,9 +76,9 @@ def __init__(self, preprocessors=[], source=None, subscribers=[],
"""
self._source = source
self.subscribers = subscribers
self.preprocessors = preprocessors
self._hidden_subscribers = []
self.linked = linked
self._rename = self._validate_rename(rename)

# The metadata may provide information about the currently
# active event, i.e. the source of the stream values may
@@ -143,6 +89,23 @@ def __init__(self, preprocessors=[], source=None, subscribers=[],
if source:
self.registry[id(source)].append(self)

def _validate_rename(self, mapping):
param_names = [k for k in self.params().keys() if k != 'name']
for k,v in mapping.items():
if k not in param_names:
raise KeyError('Cannot rename %r as it is not a stream parameter' % k)
if v in param_names:
raise KeyError('Cannot rename to %r as it clashes with a '
'stream parameter of the same name' % v)
return mapping

def rename(self, **mapping):

This comment has been minimized.

Copy link
@philippjfr

philippjfr Mar 29, 2017

Contributor

Add docstring.

This comment has been minimized.

Copy link
@jlstevens

jlstevens Mar 29, 2017

Author Contributor

Fixed in 65131a2

params = {k:v for k,v in self.get_param_values() if k != 'name'}
return self.__class__(rename=mapping,
source=self._source,
subscribers=self.subscribers,
linked=self.linked, **params)


def deactivate(self):
"""
@@ -164,21 +127,24 @@ def source(self, source):
self.registry[id(source)].append(self)


def transform(self):
"""
Method that can be overwritten by subclasses to process the
parameter values before renaming is applied. Returns a
dictionary of transformed parameters.
"""
return {}

@property
def contents(self):
remapped = {k:v for k,v in self.get_param_values() if k!= 'name' }
for preprocessor in self.preprocessors:
remapped = preprocessor(remapped)
return remapped
filtered = {k:v for k,v in self.get_param_values() if k!= 'name' }
return {self._rename.get(k,k):v for (k,v) in filtered.items()}


def update(self, trigger=True, **kwargs):
def _set_stream_parameters(self, **kwargs):
"""
The update method updates the stream parameters in response to
some event.
If trigger is enabled, the trigger classmethod is invoked on
this particular Stream instance.
Sets the stream parameters which are expected to be declared
constant.
"""
params = self.params().values()
constants = [p.constant for p in params]
@@ -188,6 +154,19 @@ def update(self, trigger=True, **kwargs):
for (param, const) in zip(params, constants):
param.constant = const

def update(self, trigger=True, **kwargs):
"""
The update method updates the stream parameters in response to
some event. If the stream has a custom transform method, this
is applied to transform the parameter values accordingly.
If trigger is enabled, the trigger classmethod is invoked on
this particular Stream instance.
"""
self._set_stream_parameters(**kwargs)
transformed = self.transform()
if transformed:
self._set_stream_parameters(**transformed)
if trigger:
self.trigger([self])

@@ -196,10 +175,11 @@ def __repr__(self):
cls_name = self.__class__.__name__
kwargs = ','.join('%s=%r' % (k,v)
for (k,v) in self.get_param_values() if k != 'name')
if not self.preprocessors:
if not self._rename:
return '%s(%s)' % (cls_name, kwargs)
else:
return '%s(%r, %s)' % (cls_name, self.preprocessors, kwargs)
return '%s(%r, %s)' % (cls_name, self._rename, kwargs)



def __str__(self):
@@ -276,9 +256,16 @@ class PlotSize(Stream):
Returns the dimensions of a plot once it has been displayed.
"""

width = param.Integer(300, doc="The width of the plot in pixels")
width = param.Integer(300, constant=True, doc="The width of the plot in pixels")

height = param.Integer(300, constant=True, doc="The height of the plot in pixels")

height = param.Integer(300, doc="The height of the plot in pixels")
scale = param.Number(default=1.0, constant=True, doc="""
Scale factor to scale width and height values reported by the stream""")

def transform(self):
return {'width': int(self.width * self.scale),
'height': int(self.height * self.scale)}


class RangeXY(Stream):
@@ -327,7 +314,7 @@ class Selection1D(Stream):
A stream representing a 1D selection of objects by their index.
"""

index = param.List(default=[], doc="""
index = param.List(default=[], constant=True, doc="""
Indices into a 1D datastructure.""")


@@ -353,9 +340,6 @@ def contents(self):
for k in self._obj.params().keys() if k!= 'name'}
else:
remapped={k:v for k,v in self._obj.get_param_values() if k!= 'name'}

for preprocessor in self.preprocessors:
remapped = preprocessor(remapped)
return remapped


Copy path View file
@@ -3,9 +3,16 @@
"""
import param
from holoviews.element.comparison import ComparisonTestCase
from holoviews.streams import Stream, PositionX, PositionY, PositionXY, ParamValues
from holoviews.streams import Rename, Group
from holoviews.streams import * # noqa (Test all available streams)

def test_all_stream_parameters_constant():
all_stream_cls = [v for v in globals().values() if
isinstance(v, type) and issubclass(v, Stream)]
for stream_cls in all_stream_cls:
for name, param in stream_cls.params().items():
if param.constant != True:
raise TypeError('Parameter %s of stream %s not declared constant'
% (name, stream_cls.__name__))

class TestSubscriber(object):

@@ -138,12 +145,57 @@ def test_batch_subscribers(self):
self.assertEqual(subscriber2.call_count, 1)


class TestPreprocessors(ComparisonTestCase):
class TestParameterRenaming(ComparisonTestCase):

def test_rename_preprocessor(self):
position = PositionXY([Rename(x='x1',y='y1')], x=1, y=3)
self.assertEqual(position.contents, dict(x1=1, y1=3))
def test_simple_rename_constructor(self):
xy = PositionXY(rename={'x':'xtest', 'y':'ytest'}, x=0, y=4)
self.assertEqual(xy.contents, {'xtest':0, 'ytest':4})

def test_invalid_rename_constructor(self):
with self.assertRaises(KeyError) as cm:
PositionXY(rename={'x':'xtest', 'z':'ytest'}, x=0, y=4)
self.assertEqual(str(cm).endswith('is not a stream parameter'), True)

def test_clashing_rename_constructor(self):
with self.assertRaises(KeyError) as cm:
PositionXY(rename={'x':'xtest', 'y':'x'}, x=0, y=4)
self.assertEqual(str(cm).endswith('parameter of the same name'), True)

def test_simple_rename_method(self):
xy = PositionXY(x=0, y=4)
renamed = xy.rename(x='xtest', y='ytest')
self.assertEqual(renamed.contents, {'xtest':0, 'ytest':4})

def test_invalid_rename_method(self):
xy = PositionXY(x=0, y=4)
with self.assertRaises(KeyError) as cm:
renamed = xy.rename(x='xtest', z='ytest')
self.assertEqual(str(cm).endswith('is not a stream parameter'), True)

def test_clashing_rename_method(self):
xy = PositionXY(x=0, y=4)
with self.assertRaises(KeyError) as cm:
renamed = xy.rename(x='xtest', y='x')
self.assertEqual(str(cm).endswith('parameter of the same name'), True)


class TestPlotSizeTransform(ComparisonTestCase):

def test_plotsize_initial_contents_1(self):
plotsize = PlotSize(width=300, height=400, scale=0.5)
self.assertEqual(plotsize.contents, {'width':300, 'height':400, 'scale':0.5})

def test_plotsize_update_1(self):
plotsize = PlotSize(scale=0.5)
plotsize.update(width=300, height=400)
self.assertEqual(plotsize.contents, {'width':150, 'height':200, 'scale':0.5})

def test_plotsize_initial_contents_2(self):
plotsize = PlotSize(width=600, height=100, scale=2)
self.assertEqual(plotsize.contents, {'width':600, 'height':100, 'scale':2})

def test_plotsize_update_2(self):
plotsize = PlotSize(scale=2)
plotsize.update(width=600, height=100)
self.assertEqual(plotsize.contents, {'width':1200, 'height':200, 'scale':2})

def test_group_preprocessor(self):
position = PositionXY([Group('mygroup')], x=1, y=3)
self.assertEqual(position.contents, dict(mygroup={'x':1,'y':3}))
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.