Skip to content

Commit

Permalink
Restore generator and argument-free callable support to DynamicMap
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens committed Apr 20, 2017
1 parent 211eb1c commit 10a8cb0
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 2 deletions.
39 changes: 37 additions & 2 deletions holoviews/core/spaces.py
Expand Up @@ -478,6 +478,8 @@ def clone(self, callable=None, **overrides):


def __call__(self, *args, **kwargs):
# Nothing to do for callbacks that accept no arguments
if not args and not kwargs: return self.callable()
inputs = [i for i in self.inputs if isinstance(i, DynamicMap)]
streams = []
for stream in [s for i in inputs for s in get_nested_streams(i)]:
Expand Down Expand Up @@ -513,6 +515,25 @@ def __call__(self, *args, **kwargs):
return ret



class Generator(Callable):
"""
Generators are considered a special case of Callable that accept no
arguments and never memoize.
"""

callable = param.Parameter(default=None, constant=True, doc="""
The generator function being wrapped.""")

@property
def argspec(self):
from inspect import ArgSpec
return ArgSpec(args=[], varargs=None, keywords=None, defaults=None)

def __call__(self):
return next(self.callable)


def get_nested_streams(dmap):
"""
Get all (potentially nested) streams from DynamicMap with Callable
Expand Down Expand Up @@ -580,7 +601,10 @@ class DynamicMap(HoloMap):
the cache is full.""")

def __init__(self, callback, initial_items=None, **params):
if not isinstance(callback, Callable):

if isinstance(callback, types.GeneratorType):
callback = Generator(callback)
elif not isinstance(callback, Callable):
callback = Callable(callback)

if 'sampled' in params:
Expand All @@ -590,6 +614,15 @@ def __init__(self, callback, initial_items=None, **params):

super(DynamicMap, self).__init__(initial_items, callback=callback, **params)
invalid = [s for s in self.streams if not isinstance(s, Stream)]

if isinstance(self.callback, Generator):
if self.kdims:
raise Exception('Generators can only be used without key dimensions')
if self.streams == []:
self.warning('Empty streams required to trigger generator')
if util.stream_parameters(self.streams):
raise Exception('Generators can only be used with empty streams')

if invalid:
msg = ('The supplied streams list contains objects that '
'are not Stream instances: {objs}')
Expand Down Expand Up @@ -654,6 +687,7 @@ def _validate_key(self, key):
Make sure the supplied key values are within the bounds
specified by the corresponding dimension range and soft_range.
"""
if key == () and len(self.kdims) == 0: return ()
key = util.wrap_tuple(key)
assert len(key) == len(self.kdims)
for ind, val in enumerate(key):
Expand Down Expand Up @@ -856,7 +890,8 @@ def __getitem__(self, key):
try:
dimensionless = util.dimensionless_contents(get_nested_streams(self),
self.kdims, no_duplicates=False)
if dimensionless:
empty = util.stream_parameters(self.streams) == [] and self.kdims==[]
if dimensionless or empty:
raise KeyError('Using dimensionless streams disables DynamicMap cache')
cache = super(DynamicMap,self).__getitem__(key)
except KeyError as e:
Expand Down
8 changes: 8 additions & 0 deletions holoviews/streams.py
Expand Up @@ -269,6 +269,14 @@ def __str__(self):
return repr(self)


class Next(Stream):
"""
Example of an empty stream that can be used to trigger generators or
callbacks that take no arguments.
"""
pass


class Counter(Stream):
"""
Simple stream that automatically increments an integer counter
Expand Down

0 comments on commit 10a8cb0

Please sign in to comment.