Is there a recent example of embedding bokeh/panel onto a flask app and linking with gunicorn. Or anyone who can help be figure out what needs changing in the below example? I’m trying out the embed_flask_gunicorn example but i get a err connection refused.
my code is as follows:
import asyncio
from threading import Thread
from flask import Flask, render_template
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
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 BaseServer
from bokeh.server.tornado import BokehTornado
from bokeh.server.util import bind_sockets
from bokeh.themes import Theme
if __name__ == '__main__':
print('This script is intended to be run with gunicorn. e.g.')
print()
print(' gunicorn -w 4 flask_gunicorn_embed:app')
print()
print('will start the app on four processes')
import sys
sys.exit()
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")
# can't use shortcuts here, since we are passing to low level BokehTornado
bkapp = Application(FunctionHandler(bkapp))
# This is so that if this app is run using something like "gunicorn -w 4" then
# each process will listen on its own port
sockets, port = bind_sockets("localhost", 0)
@app.route('/', methods=['GET'])
def bkapp_page():
script = server_document('http://localhost:%d/bkapp' % port)
return render_template("embed.html", script=script, template="Flask")
def bk_worker():
asyncio.set_event_loop(asyncio.new_event_loop())
bokeh_tornado = BokehTornado({'/bkapp': bkapp}, extra_websocket_origins=["localhost:8000"])
bokeh_http = HTTPServer(bokeh_tornado)
bokeh_http.add_sockets(sockets)
server = BaseServer(IOLoop.current(), bokeh_tornado, bokeh_http)
server.start()
server.io_loop.start()
t = Thread(target=bk_worker)
t.daemon = True
t.start()
My dockerfile is as follows:
FROM python:3.8
WORKDIR /app
COPY . .
EXPOSE 8000
RUN pip install -r requirements.txt
CMD gunicorn --bind 0.0.0.0:8000 app:app
My docker compose is as follows:
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000"
I try to compose the container as follows:
docker-compose up --scale app=3 --build
My embed.html has a blank body plus the script tag as per jinja templating.
The container builds but localhost: returns a blank page and javascript console returns net::ERR_CONNECTION_REFUSED. This is on my local windows machine. This is the official example so i expected it to work but it’s not. I’m getting the same result when i substitute the panel part with my own code (i.e. unitl the ‘plot’ is created).
I’m trying to implement my panel app on AWS but i want to make sure i don’t get lags in processing if a few people are visiting the app (as warned by a few questions on this forum and stack overflow), hence my opting for the gunicorn approach to route users to ‘instances’ on the fly. Any advice will be appreciated.
My versions are as follows:
bokeh==2.4.3
holoviews==1.15.0
panel==0.13.1
If i can get past this, Panel/Bokeh are just awesome!