Server side throttled slider

Hello,

I wanted to make a class that could be treated pretty much the same as a RangeSlider but handle throttling on the server side.

This was my attempt:

class ThrottledRangeSlider(RangeSlider):
    """
    Extends the bokeh RangeSlider to provide throttling of callbacks provided to on_change
    """
    def __init__(self, doc, base_delay, *args, **kwargs):
        super(ThrottledRangeSlider, self).__init__(*args, **kwargs)

        self.cb_set = False
        self.base_delay = base_delay
        self.delay = base_delay
        self.doc = doc

    def on_change(self, attrname, callback):
        """
        Register a server side callback to be executed when slider attributes change
        """
        update_function = self._callback_wrapper(callback)
        super(ThrottledRangeSlider, self).on_change(attrname, partial(self._callback_setter, update_function))

    def _callback_setter(self, update_function, attrname, old, new):
        if not self.cb_set:
            self.doc.add_timeout_callback(partial(update_function, attrname, old), self.delay)
            self.cb_set = True

    def _callback_wrapper(self, callback):
        def throttled_cb(*args):
            start_time = perf_counter()
            if len(args) == 2:
                attr, old = args
                callback(attr, old, self.value)
            else:
                cb_self, attr, old = args
                callback(cb_self, attr, old, self.value)
            # Modify delay to account for time taken executing callback
            self.delay = max(self.base_delay - 1000 * (perf_counter() - start_time), 50)
            self.cb_set = False
        return throttled_cb

I’m sure there’s other bugs in there, but right now the one that’s causing the code to break is setting my own attributes in init. This was the error message:

AttributeError: unexpected attribute ‘doc’ to ThrottledRangeSlider, possible attributes are bar_color, callback, callback_policy, callback_throttle, css_classes, direction, disabled, end, format, height, js_event_callbacks, js_property_callbacks, name, orientation, show_value, sizing_mode, start, step, subscribed_events, tags, title, tooltips, value or width

Is there some way I can attach my own server side only attributes to the slider or should I just try a different approach?

Thanks,

Matt

Hi,

Bokeh Models are wrappers around a purely declarative system for exchanging typed data between Python and JS runtimes. The classes themselves are heavily instrumented and locked down in order to afford all the automatic synchronization features. If you are subclassing a Bokeh Model then you can only add new Bokeh Properties as public class attributes (i.e. "foo=Int()", etc). If you want to add "regular" instance attributes that are not Bokeh properties, they must be private, i.e. start with an underscore.

Just as an FYI, an alternate, if somewhat clunky, approach to throttling python callbacks is given here:

  https://stackoverflow.com/questions/38375961/throttling-in-bokeh-application/38379136#38379136

Thanks,

Bryan

···

On Oct 2, 2018, at 17:43, Matt Agee <[email protected]> wrote:

Hello,

I wanted to make a class that could be treated pretty much the same as a RangeSlider but handle throttling on the server side.

This was my attempt:

class ThrottledRangeSlider(RangeSlider):
    """
    Extends the bokeh RangeSlider to provide throttling of callbacks provided to on_change
    """
    def __init__(self, doc, base_delay, *args, **kwargs):
        super(ThrottledRangeSlider, self).__init__(*args, **kwargs)

        self.cb_set = False
        self.base_delay = base_delay
        self.delay = base_delay
        self.doc = doc

    def on_change(self, attrname, callback):
        """
        Register a server side callback to be executed when slider attributes change
        """
        update_function = self._callback_wrapper(callback)
        super(ThrottledRangeSlider, self).on_change(attrname, partial(self._callback_setter, update_function))

    def _callback_setter(self, update_function, attrname, old, new):
        if not self.cb_set:
            self.doc.add_timeout_callback(partial(update_function, attrname, old), self.delay)
            self.cb_set = True

    def _callback_wrapper(self, callback):
        def throttled_cb(*args):
            start_time = perf_counter()
            if len(args) == 2:
                attr, old = args
                callback(attr, old, self.value)
            else:
                cb_self, attr, old = args
                callback(cb_self, attr, old, self.value)
            # Modify delay to account for time taken executing callback
            self.delay = max(self.base_delay - 1000 * (perf_counter() - start_time), 50)
            self.cb_set = False
        return throttled_cb

I'm sure there's other bugs in there, but right now the one that's causing the code to break is setting my own attributes in __init__. This was the error message:

AttributeError: unexpected attribute 'doc' to ThrottledRangeSlider, possible attributes are bar_color, callback, callback_policy, callback_throttle, css_classes, direction, disabled, end, format, height, js_event_callbacks, js_property_callbacks, name, orientation, show_value, sizing_mode, start, step, subscribed_events, tags, title, tooltips, value or width

Is there some way I can attach my own server side only attributes to the slider or should I just try a different approach?

Thanks,

Matt

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/27077f54-3a27-4cc4-a554-cc31997f3782%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Hi Bryan,

Thanks for the quick reply. Making my variables private and specifying subtype and view_model as done here got the code working.

This is the corrected version:

from bokeh.models.widgets import RangeSlider

from time import perf_counter

from functools import partial

class ThrottledRangeSlider(RangeSlider):
“”"
Extends the bokeh RangeSlider to provide throttling of callbacks provided to on_update
“”"
view_model = “RangeSlider”
subtype = “”

def __init__(self, doc, base_delay, *args, **kwargs):
    super(ThrottledRangeSlider, self).__init__(*args, **kwargs)

    self._cb_set = False
    self._base_delay = base_delay
    self._delay = base_delay
    self._doc = doc

def on_change(self, attrname, callback):
    """
    Register a server side callback to be executed when slider attributes change
    """
    update_function = self._callback_wrapper(callback)
    super(ThrottledRangeSlider, self).on_change(attrname, partial(self._callback_setter, update_function))

def _callback_setter(self, update_function, attrname, old, new):
    if not self._cb_set:
        self._doc.add_timeout_callback(partial(update_function, attrname, old), self._delay)
        self._cb_set = True

def _callback_wrapper(self, callback):
    """
    Wraps callback to use self.value instead of new.
    This ensures the callback has the most recent slider value at execution.

    Also modifies the delay between callback execution to account for time taken
    performing the callback's work.
    """
    def throttled_cb(*args):
        start_time = perf_counter()
        attr, old = args
        callback(attr, old, self.value)

        # Modify delay to account for time taken executing callback
        self._delay = max(self._base_delay - 1000 * (perf_counter() - start_time), 50)
        self._cb_set = False
    return throttled_cb

<details class='elided'>
<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>

On Tuesday, October 2, 2018 at 6:35:52 PM UTC-7, Bryan Van de ven wrote:
> Hi,
> 
> 
> 
> Bokeh Models are wrappers around a purely declarative system for exchanging typed data between Python and JS runtimes. The classes themselves are heavily instrumented and locked down in order to afford all the automatic synchronization features. If you are subclassing a Bokeh Model then you can only add new Bokeh Properties as public class attributes (i.e. "foo=Int()", etc). If you want to add "regular" instance attributes that are not Bokeh properties, they must be private, i.e. start with an underscore.
> 
> 
> Just as an FYI, an alternate, if somewhat clunky, approach to throttling python callbacks is given here:
> 
> 
> 
>         [https://stackoverflow.com/questions/38375961/throttling-in-bokeh-application/38379136#38379136](https://stackoverflow.com/questions/38375961/throttling-in-bokeh-application/38379136#38379136)
> 
> 
> 
> Thanks,
> 
> 
> 
> Bryan
> 
> 
> > On Oct 2, 2018, at 17:43, Matt Agee <[email protected]> wrote:
> 
> >
> > Hello,
> >
> > I wanted to make a class that could be treated pretty much the same as a RangeSlider but handle throttling on the server side.
> >
> > This was my attempt:
> 
> >
> > class ThrottledRangeSlider(RangeSlider):
> 
> >     """
> 
> >     Extends the bokeh RangeSlider to provide throttling of callbacks provided to on_change
> 
> >     """
> 
> >     def __init__(self, doc, base_delay, *args, **kwargs):
> 
> >         super(ThrottledRangeSlider, self).__init__(*args, **kwargs)
> 
> >
> >         self.cb_set = False
> 
> >         self.base_delay = base_delay
> 
> >         self.delay = base_delay
> 
> >         self.doc = doc
> 
> >
> >     def on_change(self, attrname, callback):
> 
> >         """
> 
> >         Register a server side callback to be executed when slider attributes change
> 
> >         """
> 
> >         update_function = self._callback_wrapper(callback)
> 
> >         super(ThrottledRangeSlider, self).on_change(attrname, partial(self._callback_setter, update_function))
> 
> >
> >     def _callback_setter(self, update_function, attrname, old, new):
> 
> >         if not self.cb_set:
> 
> >             self.doc.add_timeout_callback(partial(update_function, attrname, old), self.delay)
> 
> >             self.cb_set = True
> 
> >
> >     def _callback_wrapper(self, callback):
> 
> >         def throttled_cb(*args):
> 
> >             start_time = perf_counter()
> 
> >             if len(args) == 2:
> 
> >                 attr, old = args
> 
> >                 callback(attr, old, self.value)
> 
> >             else:
> 
> >                 cb_self, attr, old = args
> 
> >                 callback(cb_self, attr, old, self.value)
> 
> >             # Modify delay to account for time taken executing callback
> 
> >             self.delay = max(self.base_delay - 1000 * (perf_counter() - start_time), 50)
> 
> >             self.cb_set = False
> 
> >         return throttled_cb
> 
> >
> >
> > I'm sure there's other bugs in there, but right now the one that's causing the code to break is setting my own attributes in __init__. This was the error message:
> 
> >
> > AttributeError: unexpected attribute 'doc' to ThrottledRangeSlider, possible attributes are bar_color, callback, callback_policy, callback_throttle, css_classes, direction, disabled, end, format, height, js_event_callbacks, js_property_callbacks, name, orientation, show_value, sizing_mode, start, step, subscribed_events, tags, title, tooltips, value or width
> 
> >
> > Is there some way I can attach my own server side only attributes to the slider or should I just try a different approach?
> >
> > Thanks,
> >
> > Matt
> 
> >
> > --
> > You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
> 
> > To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
> 
> > To post to this group, send email to [email protected].
> 
> > To view this discussion on the web visit [https://groups.google.com/a/continuum.io/d/msgid/bokeh/27077f54-3a27-4cc4-a554-cc31997f3782%40continuum.io](https://groups.google.com/a/continuum.io/d/msgid/bokeh/27077f54-3a27-4cc4-a554-cc31997f3782%40continuum.io).
> 
> > For more options, visit [https://groups.google.com/a/continuum.io/d/optout](https://groups.google.com/a/continuum.io/d/optout).
> 
> 
>

</details>