☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

Adding callbacks within callbacks

Community Support
nick-hou
nick-hou

I’m trying to add a button that, when clicked, does the following:

  • Make some UI updates to show the button has been clicked
  • Asynchronously execute a python function that takes several minutes to run and returns some data
  • When complete, stream the data into a ColumnDataSource

Running 3.3.0, and my code is based on the example in the docs:

    def on_button_click():
        run_current_button.disabled=True

        if(look_for_run()):
            info_text.text = 'Run exists'
            run_current_button.disabled = False
            return

        info_text.text = 'Run submitted'
        run_current_button.disabled = False #Allows user to queue more runs while waiting

        curdoc().add_next_tick_callback(run_current)
    
    @without_document_lock
    async def run_current():
        # Call run and await results
        new_run_data = await submitRun(model_location, run_name, inputs)

        curdoc().add_next_tick_callback(partial(after_run, data=new_run_data))

    async def after_run(data):
        table_data_source.stream(data)
        info_text.text = 'Run complete, see table'

....

    button = Button(label='Run current values')
    button.on_click(on_button_click)

With this code, submitRun successfully executes without locking the document, however:

  • after_run is never called and table_data_source does not get updated
  • The UI updates in on_button_click do not appear until after submitRun finishes
⌁   Link to post
Bryan
Bryan

The main motivating use-case behind Bokeh was just simple one-off callbacks for a retained-mode document, ala Shiny. So while this kind of usage is possible in principle, in practice it definitely pushes the boundaries of what is typical for Bokeh, and what is regularly exercised and tested. I could try to make a very quick investigation to see if any specific observations or suggestions come to mind. But I would absolutely need something I could just copy and paste and run, i.e. a complete Minimal Reproducible Example

⌁   Link to post
nick-hou
nick-hou

FWIW I was able to find a workaround by adding both callbacks to the original event. The drawback is I can’t pass data from the first callback to the second, requiring a global variable:

run_data = []
idx = 0

def on_button_click():
    run_current_button.disabled=True

    if(look_for_run()):
        info_text.text = 'Run exists'
        run_current_button.disabled = False
        return

    info_text.text = 'Run submitted'
    run_current_button.disabled = False #Allows user to queue more runs while waiting

    idx = idx + 1
    curdoc().add_next_tick_callback(partial(run_current, index=idx))
    curdoc().add_next_tick_callback(partial(after_run, index=idx))
    
@without_document_lock
async def run_current(index):
    # Call run and await results
    run_data[index] = await submitRun(model_location, run_name, inputs)

async def after_run(index):
    data = run_data[index]
    table_data_source.stream(data)
    info_text.text = 'Run complete, see table'

....

button = Button(label='Run current values')
button.on_click(on_button_click)
⌁   Link to post

End of archived discussion · Replies are disabled