I'm in the process of parameterizing my bokeh apps by having my Flask app expose model data via a route dedicated to jsonifying the requested data passed via query string arguments. I know the data sending route works since when I use it as a url to AjaxDataSource I get the expected data plotted. However when I attempt the equivalent operation using the requests.get api I get a 503 response code which makes me think I'm violating something fundamental here I can't quite grasp with my limited webdev experience. What am I doing wrong and or violating?
I actually need a bit more data retrieving flexibility than the AjaxDataSource provides with its columnar limitations. I was hoping to lean on the requests module to pass arbitrary class instances and what not around by serializing and deserializing Json.
Here's the minimal example I have demonstrating the failure derived from flask_embed.html...
import requests
from flask import Flask, jsonify, render_template
import pandas
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 AjaxDataSource,ColumnDataSource
from bokeh.plotting import figure
from bokeh.server.server import Server
flask_app = Flask(__name__)
# Populate some model maintained by the flask application
modelDf = pandas.DataFrame()
nData = 100
modelDf[ 'c1_x' ] = range(nData)
modelDf[ 'c1_y' ] = [ x*x for x in range(nData) ]
modelDf[ 'c2_x' ] = range(nData)
modelDf[ 'c2_y' ] = [ 2*x for x in range(nData) ]
def modify_doc1(doc):
# get colum name from query string
args = doc.session_context.request.arguments
paramName = str( args['colName'][0].decode('utf-8') )
# get model data from Flask
url = "http://localhost:8080/sendModelData/%s" % paramName
source = AjaxDataSource( data = dict( x=[] , y=[] ) ,
data_url = url ,
polling_interval = 5000 ,
mode = 'replace' ,
method = 'GET' )
# plot the model data
plot = figure( )
plot.circle( 'x' , 'y' , source=source , size=2 )
doc.add_root(column(plot))
def modify_doc2(doc):
# get column name from query string
args = doc.session_context.request.arguments
colName = str( args['colName'][0].decode('utf-8') )
# get model data from Flask
url = "http://localhost:8080/sendModelData/%s" % colName
#pdb.set_trace()
res = requests.get( url , timeout=None , verify=False )
print( "CODE %s" % res.status_code )
print( "ENCODING %s" % res.encoding )
print( "TEXT %s" % res.text )
data = res.json()
# plot the model data
plot = figure()
plot.circle( 'x' , 'y' , source=data , size=2 )
doc.add_root(column(plot))
bokeh_app1 = Application(FunctionHandler(modify_doc1))
bokeh_app2 = Application(FunctionHandler(modify_doc2))
io_loop = IOLoop.current()
server = Server({'/bkapp1': bokeh_app1 , '/bkapp2' : bokeh_app2 }, io_loop=io_loop, allow_websocket_origin=["localhost:8080"])
server.start()
@flask_app.route('/', methods=['GET'] )
def index():
res = "<table>"
res += "<tr><td><a href=\"http://localhost:8080/app1/c1\">APP1 C1</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/app1/c2\">APP1 C2</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/app2/c1\">APP2 C1</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/app2/c2\">APP2 C2</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/sendModelData/c1\">DATA C1</a></td></tr>"
res += "<tr><td><a href=\"http://localhost:8080/sendModelData/c2\">DATA C2</a></td></tr>"
res += "</table>"
return res
@flask_app.route( '/app1/<colName>' , methods=['GET'] )
def bkapp1_page( colName ) :
script = server_document( url='http://localhost:5006/bkapp1' , arguments={'colName' : colName } )
return render_template("embed.html", script=script)
@flask_app.route( '/app2/<colName>' , methods=['GET'] )
def bkapp2_page( colName ) :
script = server_document( url='http://localhost:5006/bkapp2', arguments={'colName' : colName } )
return render_template("embed.html", script=script)
@flask_app.route('/sendModelData/<colName>' , methods=['GET'] )
def sendModelData( colName ) :
x = modelDf[ colName + "_x" ].tolist()
y = modelDf[ colName + "_y" ].tolist()
return jsonify( x=x , y=y )
if __name__ == '__main__':
from tornado.httpserver import HTTPServer
from tornado.wsgi import WSGIContainer
from bokeh.util.browser import view
print('Opening Flask app with embedded Bokeh application on http://localhost:8080/')
# This uses Tornado to server the WSGI app that flask provides. Presumably the IOLoop
# could also be started in a thread, and Flask could server its own app directly
http_server = HTTPServer(WSGIContainer(flask_app))
http_server.listen(8080)
io_loop.add_callback(view, "http://localhost:8080/")
io_loop.start()
Here's some debug output...
C:\TestApp>python flask_embedJSONRoute.py
Opening Flask app with embedded Bokeh application on http://localhost:8080/
> C:\TestApp\flask_embedjsonroute.py(52)modify_doc2()
-> res = requests.get( url , timeout=None , verify=False )
(Pdb) n
> C:\TestApp\flask_embedjsonroute.py(53)modify_doc2()
-> print( "CODE %s" % res.status_code )
(Pdb) n
CODE 503
> C:\TestApp\flask_embedjsonroute.py(54)modify_doc2()
-> print( "ENCODING %s" % res.encoding )
(Pdb) n
ENCODING utf-8
> C:\TestApp\flask_embedjsonroute.py(55)modify_doc2()
-> print( "TEXT %s" % res.text )
(Pdb) n
TEXT
> C:\TestApp\flask_embedjsonroute.py(56)modify_doc2()
-> data = res.json()
(Pdb)
File "C:\Anaconda3\lib\json\decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

