From 00e3227c124fae0dca518a2f7b151f905c335365 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 2 Apr 2019 11:44:20 -0500 Subject: [PATCH] Implement PeriodicCallback (#348) --- panel/callbacks.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++ panel/viewable.py | 31 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 panel/callbacks.py diff --git a/panel/callbacks.py b/panel/callbacks.py new file mode 100644 index 0000000000..fb46161f3d --- /dev/null +++ b/panel/callbacks.py @@ -0,0 +1,76 @@ +""" +Defines callbacks to be executed on a thread or by scheduling it +on a running bokeh server. +""" +from __future__ import absolute_import, division, unicode_literals + + +import time +import param + +from bokeh.io import curdoc as _curdoc + + +class PeriodicCallback(param.Parameterized): + """ + Periodic encapsulates a periodic callback which will run both + in tornado based notebook environments and on bokeh server. By + default the callback will run until the stop method is called, + but count and timeout values can be set to limit the number of + executions or the maximum length of time for which the callback + will run. + """ + + callback = param.Callable(doc=""" + The callback to execute periodically.""") + + count = param.Integer(default=None, doc=""" + Number of times the callback will be executed, by default + this is unlimited.""") + + period = param.Integer(default=500, doc=""" + Period in milliseconds at which the callback is executed.""") + + timeout = param.Integer(default=None, doc=""" + Timeout in seconds from the start time at which the callback + expires""") + + def __init__(self, **params): + super(PeriodicCallback, self).__init__(**params) + self._counter = 0 + self._start_time = None + self._timeout = None + self._cb = None + self._doc = None + + def start(self): + if self._cb is not None: + raise RuntimeError('Periodic callback has already started.') + self._start_time = time.time() + if _curdoc().session_context: + self._doc = _curdoc() + self._cb = self._doc.add_periodic_callback(self._periodic_callback, self.period) + else: + from tornado.ioloop import PeriodicCallback + self._cb = PeriodicCallback(self._periodic_callback, self.period) + self._cb.start() + + def _periodic_callback(self): + self.callback() + self._counter += 1 + if self._timeout is not None: + dt = (time.time() - self._start_time) + if dt > self._timeout: + self.stop() + if self._counter == self.count: + self.stop() + + def stop(self): + self._counter = 0 + self._timeout = None + if self._doc: + self._doc.remove_periodic_callback(self._cb) + else: + self._cb.stop() + self._cb = None + diff --git a/panel/viewable.py b/panel/viewable.py index 80cca41aa7..a1c388b941 100644 --- a/panel/viewable.py +++ b/panel/viewable.py @@ -18,6 +18,7 @@ from bokeh.models import CustomJS from pyviz_comms import JupyterCommManager +from .callbacks import PeriodicCallback from .config import config, panel_extension from .io.embed import embed_state from .io.model import add_to_doc @@ -753,6 +754,36 @@ def link(*events): self._callbacks.append(cb) return cb + def add_periodic_callback(self, callback, period=500, count=None, + timeout=None, start=True): + """ + Schedules a periodic callback to be run at an interval set by + the period. Returns a PeriodicCallback object with the option + to stop and start the callback. + + Arguments + --------- + callback: callable + Callable function to be executed at periodic interval. + period: int + Interval in milliseconds at which callback will be executed. + count: int + Maximum number of times callback will be invoked. + timeout: int + Timeout in seconds when the callback should be stopped. + start: boolean (default=True) + Whether to start callback immediately. + + Returns + ------- + Return a PeriodicCallback object with start and stop methods. + """ + cb = PeriodicCallback(callback=callback, period=period, + count=count, timeout=timeout) + if start: + cb.start() + return cb + def jslink(self, target, code=None, **links): """ Links properties on the source object to those on the target