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 110efa9
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 2 deletions.
38 changes: 36 additions & 2 deletions holoviews/core/spaces.py
Expand Up @@ -4,6 +4,7 @@
from itertools import groupby
from functools import partial
from contextlib import contextmanager
from inspect import ArgSpec

import numpy as np
import param
Expand Down Expand Up @@ -478,6 +479,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 +516,24 @@ 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):
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 @@ -595,6 +619,14 @@ def __init__(self, callback, initial_items=None, **params):
'are not Stream instances: {objs}')
raise TypeError(msg.format(objs = ', '.join('%r' % el for el in invalid)))

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')

self._posarg_keys = util.validate_dynamic_argspec(self.callback.argspec,
self.kdims,
self.streams)
Expand Down Expand Up @@ -654,6 +686,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 +889,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 110efa9

Please sign in to comment.