Embed Bokeh Server App in a Flask App

Hi,

I am using the below code flask_embed.py to embed a bokeh server app in a flask app.

    from threading import Thread

    from flask import Flask, render_template
    from tornado.ioloop import IOLoop

    from bokeh.embed import server_document
    from bokeh.layouts import column
    from bokeh.models import ColumnDataSource, Slider
    from bokeh.plotting import figure
    from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
    from bokeh.server.server import Server
    from bokeh.themes import Theme

    app = Flask(__name__)


    def bkapp(doc):
        df = sea_surface_temperature.copy()
        source = ColumnDataSource(data=df)

        plot = figure(x_axis_type='datetime', y_range=(0, 25), y_axis_label='Temperature (Celsius)',
                      title="Sea Surface Temperature at 43.18, -70.43")
        plot.line('time', 'temperature', source=source)

        def callback(attr, old, new):
            if new == 0:
                data = df
            else:
                data = df.rolling(f"{new}D").mean()
            source.data = ColumnDataSource.from_df(data)

        slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
        slider.on_change('value', callback)

        doc.add_root(column(slider, plot))

        doc.theme = Theme(filename="theme.yaml")


    @app.route('/', methods=['GET'])
    def bkapp_page():
        script = server_document('10.XX.XXX.100:5000/bkapp')
        return render_template("embed.html", script=script, template="Flask")


    def bk_worker():
        # Can't pass num_procs > 1 in this configuration. If you need to run multiple
        # processes, see e.g. flask_gunicorn_embed.py
        server = Server({'/bkapp': bkapp}, io_loop=IOLoop(), allow_websocket_origin=["10.XX.XXX.100:5000"])
        server.start()
        server.io_loop.start()

    Thread(target=bk_worker).start()

    if __name__ == '__main__':
        print('Opening single process Flask app with embedded Bokeh application')
        print()
        print('Multiple connections may block the Bokeh app in this configuration!')
        print('See "flask_gunicorn_embed.py" for one way to run multi-process')
        app.run(host = 10.XX.XXX.100,port=6000)

Link to the embed.html file

When I run python flask_embed.py in the terminal, I see the page loaded with the text ‘Bokeh app’ that is present in embed.html but the bokeh plot is not rendering. Below is a message that I see in the terminal after loading the page in the browser 10.XX.XXX.100:5000.

I would really appreciate any feedback/solution to fix this issue. Thank you.

@Tauseef The bokeh server part listens on a different port than the Flask server. The error message is Flask telling you it has no idea about any of the Bokeh server endpoints. The default Bokeh server port is 5006 so assuming you don’t change that, the server_embed line would need to change to

script = server_document('10.XX.XXX.100:5006/bkapp')

But I have to prompt, what if someone set bokeh app to port other than 5006, in case of multiple instances of bokeh development, is there solution exist to circumvent such situation ?

You have to know what the port is, one way or another. If your example code is any indication that should not be a problem since you are configuring the Bokeh Server instance right there yourself, and would have to set a different port yourself for it to be different from the default. The server instance also has a port attribute.

Hi Bryan - Thanks for your reply. I changed the port to default Bokeh server port to 5006 but still getting the same error.

from threading import Thread

from flask import Flask, render_template
from tornado.ioloop import IOLoop

from bokeh.embed import server_document
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
from bokeh.server.server import Server
from bokeh.themes import Theme

app = Flask(__name__)


def bkapp(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data=df)

    plot = figure(x_axis_type='datetime', y_range=(0, 25), y_axis_label='Temperature (Celsius)',
                  title="Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source=source)

    def callback(attr, old, new):
        if new == 0:
            data = df
        else:
            data = df.rolling(f"{new}D").mean()
        source.data = ColumnDataSource.from_df(data)

    slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
    slider.on_change('value', callback)

    doc.add_root(column(slider, plot))

    doc.theme = Theme(filename="theme.yaml")


@app.route('/', methods=['GET'])
def bkapp_page():
    script = server_document('10.XX.XXX.100:5006/bkapp')
    return render_template("embed.html", script=script, template="Flask")


def bk_worker():
    # Can't pass num_procs > 1 in this configuration. If you need to run multiple
    # processes, see e.g. flask_gunicorn_embed.py
    server = Server({'/bkapp': bkapp}, io_loop=IOLoop(), allow_websocket_origin=["10.XX.XXX.100:6000"])
    server.start()
    server.io_loop.start()

Thread(target=bk_worker).start()

if __name__ == '__main__':
    print('Opening single process Flask app with embedded Bokeh application')
    print()
    print('Multiple connections may block the Bokeh app in this configuration!')
    print('See "flask_gunicorn_embed.py" for one way to run multi-process')
    app.run(host = 10.XX.XXX.100,port=6000)

Are you sure it is the same error? E.g the code above specifies to allow a websocket origin on port 5000 but then actually calls app.run with port 6000. If your example code is accurate, that would certainly generate a 403 permissions error (not 404).

Yes still getting the same error. There was a typo in replying, I am using the same port 6000 both in allow websocket origin and in the app.run.

Is there a firewall or proxy that would block access to the Bokeh server at port 5006, or redirect all traffic to Django? If you are still getting 404s from Django then that means the pages requests back to the Bokeh server never even making it to the Bokeh server. They are going to Django instead, and that is certainly a problem. That would need to be resolved. Unfortunately I can’t really offer much assistance without quite a bit of knowledge about your network configuration.

TLDR; The page that django serves will contain JS code that attempts to connect to the Bokeh server. That network address will have to be reachable by the page.

I have the exact same problem. I have embedded Bokeh server app in flask. All the ports and code is correct. The same code works on my work laptop but doesn’t work on my private laptop. Please have a look on the messages shown in my browser and the terminal messages are same as shown by @Tauseef .

@Shaheer_Asghar

The screen capture of your console logs look like you are using Holoviz panel to serve your application. Is that correct?

If so, what versions of panel and bokeh are you using? And worth confirming you have identical versions of everything in your two setups (working and non-working).

Would also suggest starting a separate topic for your specific issue…

See this topic on the Holoviz panel discourse. I am the author of that topic; there was a regression in panel that caused panel+Flask to stop working. Things worked in panel 0.9.7, stopped working in panel 0.10.2, and were fixed in panel 0.10.3

@_jm , Yes you are right. Thanks for sending the link of your topic. It gave me a good hint. However, my problem was solved by installing latest version of panel by simply using “conda install -c anaconda panel”. The Bokeh version on both of my setups is 2.2.3.

1 Like

Yes. That link was an interim solution. Apologies if my comment in this thread was not clear; panel 0.10.3 and newer (currently 0.11.1) fixes the regression so those additional statements are no longer required.

1 Like