Is it possible to create a progress bar that keeps up with background python functions?

So I was trying to create a progress bar or a eventlistener that progresses or prints out what functions where running. For this I would need to be able to use customjs without a object call. Is this possible or do I actually need to work with celery or a websocket?

First things first, Bokeh does not currently have a built-in progress bar (there is an open Pull Request, but it is not merged yet). You could certainly load some third-party progress bar widgert into your page with an HTML template, and then update that from a CustomJS callback in principle (I don’t have any specific example code to share). If you need something immediately, then Panel (which is built on top of Bokeh) does have a built-in progress bar, so you might consider using Panel rather than Bokeh directly.

For the rest, there’s not really enough information to speculate. What does “what functions were running” actually mean? Do you mean in a Bokeh server process? Do you mean in some completely separate unrelated process? More than one process? On a local machine or across a network? You’d need to provide a lot more detail in order to possibly offer guidance, though ultimately it will be up to you to provide a way to:

  1. determine what functions are running
  2. pass that information to Bokeh in some manner

And Bokeh does not really have anything to say directly about those things.

Hey, so this is basically what I am trying to do for the event listener Ig. The problem is that I am unable to call display_event without using js_on_event. I’m trying to use button.on_click() so it calls the functions and from there I want to display an event that happens in the function. So I can see exactly where I am in the function/process after I press the button. This is a dummy code btw, the actual code has some longer processes that takes some time. Thanks in advance.

import numpy as np

from bokeh import events
from bokeh.io import output_file, show
from bokeh.layouts import column, row
from bokeh.models import Button, CustomJS, Div
from bokeh.plotting import figure
from bokeh.io import curdoc

def display_event(div, attributes=[], style = 'float:left;clear:left;font_size=13px'):
    "Build a suitable CustomJS to display the current event in the div model."
    return CustomJS(args=dict(div=div), code="""
        const attrs = %s;
        const args = [];
        for (let i = 0; i<attrs.length; i++) {
            args.push(attrs[i] + '=' + Number(cb_obj[attrs[i]]).toFixed(2));
        }
        
        const line = "<span style=%r><b>xxxx</b>(" + args.join(", ") + ")</span>\\n";
        const text = div.text.concat(line);
        const lines = text.split("\\n")
        
        if (lines.length > 35)
            lines.shift();
        div.text = lines.join("\\n");
    """ % (attributes, style))

def test2():
    button.js_on_event(events.ButtonClick,display_event(div,attributes=["xxxx"]))


def test():
    button.js_on_event(events.ButtonClick,display_event(div,attributes=["start function"]))
    x = 0
    for i in range (50):
        x +=i
    print(x)
    test2()
    button.js_on_event(events.ButtonClick,display_event(div,attributes=["end function"]))
    print(x+5)


div = Div(width=400, height=300, height_policy="fixed")
button = Button(label="Button", button_type="success")
layout = column(button, div)

button.js_on_event(events.ButtonClick, test())

#button.on_click(test)

curdoc().add_root(layout)

There’s no direct way to invoke JS callbacks from Python, e.g. like a Remote Procedure Call (RPC) style system. A common if clunky workaround is to add js_on_change to some arbitrary model property and then set that property to trigger the JS callback indirectly. A DataModel could be useful for that purpose, but any property that is otherwise “unimportant” to the appearance of your plots will do.

I found a workaround with add_next_tick_callback.

2 functions that are being called for each process

def progress_load(Percentage=1):
    Perc = str(Percentage)
    return f"""<div id="myProgress" style="width: 500px; background-color: grey;">
    <div id="myBar" style="width: {Perc}%; height: 30px; background-color: green;"><b>{Perc}%</b></div>
    </div>"""

def progress(Text,Percentage):
    if Text != "COMPLETED":
        progress_text_div.text = f'<b style="background-color: red; padding: 5px;">{Text}</b><br>'
        progress_bar_div.text = progress_load(Percentage)
        videoButton.button_type = 'danger'
        videoButton.label = "PLEASE WAIT..."
    else:
        progress_text_div.text = f'<b style="background-color: green; padding: 5px;">{Text}</b><br>'
        progress_bar_div.text = progress_load(Percentage)
        videoButton.button_type = 'success'
        videoButton.label = "CREATE VIDEO"

The 2 processes that needs to be kept up

def process_images_callback():
    # Define the range of images for the video
    imgRange = [int(rangeslider.value[0]), int(rangeslider.value[1])]
    processImages(rootURL, rootDir, testDir, imageFileList, imgList, imHeight,
                      imWidth, imgRange, roi_x0, roi_y0, roi_x1, roi_y1, timeBetweenFrames,
                      checkbox_group.active)

    # Task 2: Create Video
def create_video_callback():
    #creating a Time stamp
    timeStamp = datetime.now().strftime("%d%b%Y_%H%M%S")
    if ((1 in checkbox_group.active) or (0 in checkbox_group.active)):
        createVideoWithFFmpeg(ffmpegExe, videoFPSinput.value, 'video' + timeStamp + '.mp4', rootURL, testDir, timeStamp)

Lastly the function that calls everything with add_next_tick_callback.

def button_clicked():
    curdoc().add_next_tick_callback(lambda: progress("STARTING", 1))
    time.sleep(1)
    curdoc().add_next_tick_callback(process_images_callback)
    curdoc().add_next_tick_callback(lambda: progress("almost done", 30))
    curdoc().add_next_tick_callback(create_video_callback)
    curdoc().add_next_tick_callback(lambda: updateLayout(rootDir, rootURL,testDir, layout))
    print("completed")

    curdoc().add_next_tick_callback(lambda: progress("COMPLETED",100))

I don’t know if it is the best way to go about it but it works ig. Thanks for the help btw