How to add and remove periodic callbacks, from on_click button events?

First of all I would like to extend my congratulations to the developers, as of recent I checked on the different plotting options with Python, and Bokeh has delivered! I recently published some nice XPS spectrum deconvolution for scientific publication via Bokeh! I may display these pictures here once editorial publishes the document.

Now I am heading towards server applications, I would like to develop a GUI for electrochemical readouts. But I am currently struggling with the add_periodic_callback and remove_periodic_callback methods.

I require two buttons, one for data acquisition and another one to stop acquisition. So far, I am able to add periodic callbacks at the global scope, and then remove these via on_click function.

However, I am not able to add periodic callbacks from a button event and then delete these from a second one. Evidently, this problem has to do with the scope of variables/classes, but I haven’t found my way around it. I provide an example code, which is a modified version of the “Updating from Unlocked Callbacks” example.

from functools import partial

import time

from concurrent.futures import ThreadPoolExecutor

from tornado import gen

from bokeh.document import without_document_lock

from bokeh.models import ColumnDataSource

from bokeh.plotting import curdoc, figure

from bokeh.models.widgets import Button

from bokeh.layouts import column, row

source = ColumnDataSource(data=dict(x=[0], y=[0], color=["blue"]))

i = 0

doc = curdoc()

executor = ThreadPoolExecutor(max_workers=2)

def blocking_task(i):

    time.sleep(1.5)

    return i

@gen.coroutine

def locked_update(i):

    source.stream(dict(x=[source.data['x'][-1]+1], y=[i], color=["blue"]))

@gen.coroutine

@without_document_lock

def unlocked_task():

    global i

    i += 1

    res = yield executor.submit(blocking_task, i)

    doc.add_next_tick_callback(partial(locked_update, i=res))

@gen.coroutine

def update():

    source.stream(dict(x=[source.data['x'][-1]+1], y=[i], color=["red"]))

    #print("Just streamed")

p = figure(x_range=[0, 100], y_range=[0, 20])

l = p.circle(x='x', y='y', color='color', source=source)

#---------------------------#

#          Buttons          #

#---------------------------#

Add_Callbacks = Button(label='Add Callbacks', button_type='success', width=320)

Remove_Callbacks = Button(label='Remove Callbacks',

                          button_type='warning', width=320)

def callback_Add_threads():

    #NOTE: Adding callbacks from here, can't be removed

    nonlocal callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)

    callback_update = doc.add_periodic_callback(update, 200)

def callback_Remove_threads():

    #The following functions are not able to acces these threads

    doc.remove_periodic_callback(callback_unlocked_task)

    doc.remove_periodic_callback(callback_update)

Add_Callbacks.on_click(callback_Add_threads)

Remove_Callbacks.on_click(callback_Remove_threads)

#NOTE: If I add the periodic callbacks from here, these can be removed by my "remove" button.

#UNCOMMENT callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)

#UNCOMMENT callback_update = doc.add_periodic_callback(update, 200)

doc.add_root(row(p, column(Add_Callbacks, Remove_Callbacks)))

Python has global and nonlocal keywords for that. Also, instead of storing the required objects in variables, you can store them as fields of some object. With just a = 1, a cannot be a global object (unless you use one of the aforementioned keywords), but with x.a = 1 the x object can come from anywhere since you don’t assign directly to it.

@mfjimenez also please note you have quoted the code, not applied code formatting. To apply code formatting, enclose the block in triple backtick ``` fences, or use the </> button on the editor GUI.

Will do that, I was wondering how to format it properly.

Have tried the global and nonlocal approaches, but as far as my understanding goes, we use nonlocal when we are nesting functions and you want to access to a variable that its on higher scope (hopefully my lexicon is correct).

Thing is, the callback id is on a different function, like this:

def callback_Add_threads():
    #NOTE: Adding callbacks from here, can't be removed
    callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)
    callback_update = doc.add_periodic_callback(update, 200)

def callback_Remove_threads():
    #The following functions are not able to acces these threads
    doc.remove_periodic_callback(callback_unlocked_task)
    doc.remove_periodic_callback(callback_update)

Which didn’t work. I will try your second proposal.

I am sorry if this is more fundamental, I am learning python on the go and still don’t know what would be the best coding philosophy when it somes to this kind of aaplications.

So if you have code like

def change_a():
    a = 1

def use_a():
    print(a)

it will not work.
To make it work, you can change it so it looks like this:

a = None

def change_a():
    global a
    a = 1

def use_a():
    print(a)

There are other options, but this one is the most straightforward.

1 Like

This worked! Really appreciate your support. I didn’t try to assign a None in the global scope (didn’t know until now about it, I come from a C background, so it is still weird to me not to assign a type to variables).

callback_unlocked_task = None
callback_update = None

def callback_Add_threads():
    #NOTE: Adding callbacks from here, can't be removed
    global callback_unlocked_task
    global callback_update
    callback_unlocked_task = doc.add_periodic_callback(unlocked_task, 1000)
    callback_update = doc.add_periodic_callback(update, 200)

def callback_Remove_threads():
    #The following functions are not able to acces these threads
    doc.remove_periodic_callback(callback_unlocked_task)
    doc.remove_periodic_callback(callback_update)

I will use this for my application. Just one more question, is this the only way how I could had implemented adding and removing threads with different buttons? Usually people discourage the use of global variables.

I am very thankful! :smile:

Of course it’s not the only way - the amount of ways is virtually infinite. Although, only a few of them are practical.

Another way would be wrap everything in a function. Or a class.