Skip to content

Commit

Permalink
Merge 5b3503c into 517d925
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens committed Nov 14, 2018
2 parents 517d925 + 5b3503c commit 418480a
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 55 deletions.
23 changes: 18 additions & 5 deletions holoviews/core/dimension.py
Expand Up @@ -1153,7 +1153,9 @@ def __unicode__(self):
def __call__(self, options=None, **kwargs):
if config.warn_options_call:
self.warning('Use of __call__ to set options will be deprecated '
'in future. Use the equivalent opts method instead.')
'in future. Use the equivalent opts method or use '
'the recommended .options method instead.')

return self.opts(options, **kwargs)

def opts(self, options=None, backend=None, clone=True, **kwargs):
Expand Down Expand Up @@ -1222,7 +1224,7 @@ def opts(self, options=None, backend=None, clone=True, **kwargs):
return obj


def options(self, options=None, backend=None, clone=True, **kwargs):
def options(self, *args, **kwargs):
"""
Applies options on an object or nested group of objects in a
flat format returning a new object with the options
Expand All @@ -1243,15 +1245,26 @@ def options(self, options=None, backend=None, clone=True, **kwargs):
If no options are supplied all options on the object will be reset.
Disabling clone will modify the object inplace.
"""
if isinstance(options, basestring):
options = {options: kwargs}
elif options and kwargs:
backend = kwargs.pop('backend', None)
clone = kwargs.pop('clone', True)

if len(args) == 0 and len(kwargs)==0:
options = None
elif args and isinstance(args[0], basestring):
options = {args[0]: kwargs}
elif args and isinstance(args[0], list):
if kwargs:
raise ValueError('Please specify a list of option objects, or kwargs, but not both')
options = args[0]
elif args and kwargs:
raise ValueError("Options must be defined in one of two formats."
"Either supply keywords defining the options for "
"the current object, e.g. obj.options(cmap='viridis'), "
"or explicitly define the type, e.g."
"obj.options({'Image': {'cmap': 'viridis'}})."
"Supplying both formats is not supported.")
elif args:
options = list(args)
elif kwargs:
options = {type(self).__name__: kwargs}

Expand Down
28 changes: 26 additions & 2 deletions holoviews/core/options.py
Expand Up @@ -392,6 +392,8 @@ class Options(param.Parameterized):
skipping over invalid keywords or not. May only be specified at
the class level.""")

_option_groups = ['style', 'plot', 'norm']

def __init__(self, key=None, allowed_keywords=[], merge_keywords=True, max_cycles=None, **kwargs):

invalid_kws = []
Expand All @@ -402,6 +404,10 @@ def __init__(self, key=None, allowed_keywords=[], merge_keywords=True, max_cycle
else:
raise OptionError(kwarg, allowed_keywords)

if key and key[0].islower() and key not in self._option_groups:
raise Exception('Key %s does not start with a capitalized element class name and is not a group in %s'
% (repr(key), ', '.join(repr(el) for el in self._option_groups)))

for invalid_kw in invalid_kws:
error = OptionError(invalid_kw, allowed_keywords, group_name=key)
StoreOptions.record_skipped_option(error)
Expand Down Expand Up @@ -495,8 +501,14 @@ def options(self):


def __repr__(self):
kws = ', '.join("%s=%r" % (k,v) for (k,v) in self.kwargs.items())
return "%s(%s)" % (self.__class__.__name__, kws)
kws = ', '.join("%s=%r" % (k,self.kwargs[k]) for k in sorted(self.kwargs.keys()))

if self.key and self.key[0].isupper() and kws:
return "%s(%s, %s)" % (self.__class__.__name__, repr(self.key), kws)
elif self.key and self.key[0].isupper():
return "%s(%s)" % (self.__class__.__name__, repr(self.key))
else:
return "%s(%s)" % (self.__class__.__name__, kws)

def __str__(self):
return repr(self)
Expand Down Expand Up @@ -617,6 +629,9 @@ def __setattr__(self, identifier, val):
group_items = val
elif isinstance(val, Options) and val.key is None:
raise AttributeError("Options object needs to have a group name specified.")
elif isinstance(val, Options) and val.key[0].isupper():
raise AttributeError("OptionTree only accepts Options using keys that are one of %s." %
', '.join(repr(el) for el in Options._option_groups))
elif isinstance(val, Options):
group_items = {val.key: val}
elif isinstance(val, OptionTree):
Expand Down Expand Up @@ -1052,6 +1067,15 @@ class Store(object):

current_backend = 'matplotlib'

_backend_switch_hooks = []

@classmethod
def set_current_backend(cls, backend):
"Use this method to set the backend to run the switch hooks"
for hook in cls._backend_switch_hooks:
hook(backend)
cls.current_backend = backend

@classmethod
def options(cls, backend=None, val=None):
backend = cls.current_backend if backend is None else backend
Expand Down
13 changes: 7 additions & 6 deletions holoviews/core/spaces.py
Expand Up @@ -97,7 +97,7 @@ def opts(self, options=None, backend=None, clone=True, **kwargs):
return self.clone(data)


def options(self, options=None, backend=None, clone=True, **kwargs):
def options(self, *args, **kwargs):
"""
Applies options on an object or nested group of objects in a
flat format returning a new object with the options
Expand All @@ -118,7 +118,7 @@ def options(self, options=None, backend=None, clone=True, **kwargs):
If no options are supplied all options on the object will be reset.
Disabling clone will modify the object inplace.
"""
data = OrderedDict([(k, v.options(options, backend, clone, **kwargs))
data = OrderedDict([(k, v.options(*args, **kwargs))
for k, v in self.data.items()])
return self.clone(data)

Expand Down Expand Up @@ -946,7 +946,7 @@ def opts(self, options=None, backend=None, clone=True, **kwargs):
return dmap


def options(self, options=None, backend=None, clone=True, **kwargs):
def options(self, *args, **kwargs):
"""
Applies options on an object or nested group of objects in a
flat format returning a new object with the options
Expand All @@ -968,17 +968,18 @@ def options(self, options=None, backend=None, clone=True, **kwargs):
Disabling clone will modify the object inplace.
"""
from ..util import Dynamic
clone = kwargs.get('clone', True)

obj = self if clone else self.clone()
dmap = Dynamic(obj, operation=lambda obj, **dynkwargs: obj.options(options, backend,
clone, **kwargs),
dmap = Dynamic(obj, operation=lambda obj, **dynkwargs: obj.options(*args, **kwargs),
streams=self.streams, link_inputs=True)
if not clone:
with util.disable_constant(self):
self.callback = dmap.callback
self.callback.inputs[:] = [obj]
obj.callback.inputs[:] = []
dmap = self
dmap.data = OrderedDict([(k, v.options(options, backend, **kwargs))
dmap.data = OrderedDict([(k, v.options(*args, **kwargs))
for k, v in self.data.items()])
return dmap

Expand Down
27 changes: 26 additions & 1 deletion holoviews/core/util.py
Expand Up @@ -84,7 +84,7 @@ class Config(param.ParameterizedFunction):
Switch to the default style options used up to (and including)
the HoloViews 1.7 release.""")

warn_options_call = param.Boolean(default=False, doc="""
warn_options_call = param.Boolean(default=True, doc="""
Whether to warn when the deprecated __call__ options syntax is
used (the opts method should now be used instead). It is
recommended that users switch this on to update any uses of
Expand Down Expand Up @@ -144,6 +144,31 @@ def default(self, obj):
return id(obj)


def merge_option_dicts(old_opts, new_opts):
"""
Update the old_opts option dictionary with the options defined in
new_opts. Instead of a shallow update as would be performed by calling
old_opts.update(new_opts), this updates the dictionaries of all option
types separately.
Given two dictionaries
old_opts = {'a': {'x': 'old', 'y': 'old'}}
and
new_opts = {'a': {'y': 'new', 'z': 'new'}, 'b': {'k': 'new'}}
this returns a dictionary
{'a': {'x': 'old', 'y': 'new', 'z': 'new'}, 'b': {'k': 'new'}}
"""
merged = dict(old_opts)

for option_type, options in new_opts.items():
if option_type not in merged:
merged[option_type] = {}

merged[option_type].update(options)

return merged


class periodic(Thread):
"""
Run a callback count times with a given period without blocking.
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/bokeh/__init__.py
Expand Up @@ -52,7 +52,7 @@
Store.renderers['bokeh'] = BokehRenderer.instance()

if len(Store.renderers) == 1:
Store.current_backend = 'bokeh'
Store.set_current_backend('bokeh')

associations = {Overlay: OverlayPlot,
NdOverlay: OverlayPlot,
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/mpl/__init__.py
Expand Up @@ -97,7 +97,7 @@ def get_color_cycle():
Store.renderers['matplotlib'] = MPLRenderer.instance()

if len(Store.renderers) == 1:
Store.current_backend = 'matplotlib'
Store.set_current_backend('matplotlib')

# Defines a wrapper around GridPlot and RasterGridPlot
# switching to RasterGridPlot if the plot only contains
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/plotly/__init__.py
Expand Up @@ -14,7 +14,7 @@
Store.renderers['plotly'] = PlotlyRenderer.instance()

if len(Store.renderers) == 1:
Store.current_backend = 'plotly'
Store.set_current_backend('plotly')

Store.register({Points: PointPlot,
Scatter: PointPlot,
Expand Down
27 changes: 26 additions & 1 deletion holoviews/tests/core/testoptions.py
Expand Up @@ -26,6 +26,14 @@

class TestOptions(ComparisonTestCase):

def setUp(self):
Options._option_groups = ['test']
super(TestOptions, self).setUp()

def tearDown(self):
Options._option_groups = ['style', 'plot', 'norm']
super(TestOptions, self).tearDown()

def test_options_init(self):
Options('test')

Expand Down Expand Up @@ -114,6 +122,14 @@ def test_options_inherit_invalid_keywords(self):

class TestCycle(ComparisonTestCase):

def setUp(self):
Options._option_groups = ['test']
super(TestCycle, self).setUp()

def tearDown(self):
Options._option_groups = ['style', 'plot', 'norm']
super(TestCycle, self).tearDown()

def test_cycle_init(self):
Cycle(values=['a', 'b', 'c'])
Cycle(values=[1, 2, 3])
Expand Down Expand Up @@ -178,6 +194,14 @@ def test_options_property_disabled(self):

class TestOptionTree(ComparisonTestCase):

def setUp(self):
Options._option_groups = ['group1', 'group2']
super(TestOptionTree, self).setUp()

def tearDown(self):
Options._option_groups = ['style', 'plot', 'norm']
super(TestOptionTree, self).tearDown()

def test_optiontree_init_1(self):
OptionTree(groups=['group1', 'group2'])

Expand Down Expand Up @@ -532,6 +556,7 @@ def test_style_transfer(self):
class TestOptionTreeFind(ComparisonTestCase):

def setUp(self):
Options._option_groups = ['group']
options = OptionTree(groups=['group'])
self.opts1 = Options('group', kw1='value1')
self.opts2 = Options('group', kw2='value2')
Expand All @@ -553,6 +578,7 @@ def setUp(self):


def tearDown(self):
Options._option_groups = ['style', 'plot', 'norm']
Store.options(val=self.original_options)
Store._custom_options = {k:{} for k in Store._custom_options.keys()}

Expand Down Expand Up @@ -732,4 +758,3 @@ def test_pickle_mpl_bokeh(self):
Store.current_backend = 'bokeh'
bokeh_opts = Store.lookup_options('bokeh', img, 'style').options
self.assertEqual(bokeh_opts, {'cmap':'Purple'})

0 comments on commit 418480a

Please sign in to comment.