I'm creating a bokeh application, and would like to make the state (widgets, which affect the figure) "shareable". My first thought was to use a query string in the URL. However, I'm not sure if the actual HTTP request is available to the application.
An example:
# main.py
from bokeh.models.widgets import Select
from bokeh.plotting import Figure
from bokeh.io import curdoc, vform
p = Figure()
selector = Select(title="Select", options=["1", "2", "3"], value="2")
p.line([1, selector.value], [1, selector.value])
curdoc().add_root(vform(p, selector))
which is served with bokeh serve main.py and accessed at http://localhost:5006/main.
If I navigate to http://localhost:5006/main?select=3, is there a way for the application to know that the original request included select=3 and have that reflected in the figure going up to 3 instead of the default 2? Or am I approaching this entirely the wrong way, and missing a better solution?
This question and answer is related, but I think out of date now that bokeh server is built on tornado.
Eventually I was able to get this working. I'm not sure I would really recommend anyone use this code, but in case it's useful....
I wrapped the Bokeh application in a Flask app. The basic idea is to parse the query parameters in the flask view, and walk the Bokeh document, updating the objects as directed by the query parameter.
I updated main.py to include state, a list of widgets controllable by the query parameter
# file: main.py
# ...
state = [selector]
# ...
We import that in app.py, the Flask (or Django/whatever) app.
# file: app.py
import json
from urllib.parse import urlencode
from flask import Flask, request, render_template
from bokeh.client import pull_session
from bokeh.embed import autoload_server
app = Flask(__name__)
@app.route('/')
def index():
session = pull_session(app_path='/main')
doc = session.document
update_document(doc, request) # this is the important part
script = autoload_server(model=None,
app_path='/main', session_id=session.id)
return render_template('index.html', script=script, session_id=session.id)
def update_document(doc, request):
'''
Update a Bokeh document according to the query string in ``request``
Parameters
----------
doc: Bokeh.Document
request: flask.request
Returns
-------
None: (Modifies doc inplace)
'''
for k, v in request.args.items():
# titles must be unique
model = doc.select_one(dict(title=k))
v = parse_query_value(v)
model.update(value=v)
def parse_query_value(v):
'''
Parse a single parameter's value from the query string
Parameters
----------
v : str
Returns
-------
parsed : object
'''
if v.startswith('['):
return json.loads(v.replace("'", '"'))
return v
@app.route('/state/<session_id>')
def get_state(session_id):
'''
Get the current widget state.
Parameters
----------
session_id : int
id of the connection to the bokeh server from ``session.id``.
this should be unique per tab.
'''
session = pull_session(app_path='/main',
session_id=session_id)
doc = session.document
params = {}
for key in state:
params[key] = doc.select_one(dict(title=key)).value
return urlencode(params)
if __name__ == '__main__':
app.run()
And index.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 class='title'>Title</h1>
<div class="plot">{{ script|safe }}</div>
</body>
</html>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With