Proof of Concept — embedding a Bokeh server in a Notebook

This notebook shows an apprioach to embedding Bokeh server application inside Jupyter notebook. Note that this method has some potential drawbacks, and that in future releases, there will be new APIs to mitigate these drawbacks and streamline usage.


In [ ]:
import numpy as np

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure

There are various application handlers that can be used to build up Bokeh documents. For example, there is a ScriptHandler that uses the code from a .py file to produce Bokeh documents. This is the handler that is used when we run bokeh serve app.py. Here we are going to use the lesser-known FunctionHandler, that gets configured with a plain Python function to build up a document.

Here is the function modify_doc(doc) that defines our app:


In [ ]:
def modify_doc(doc):
    x = np.linspace(0, 10, 1000)
    y = np.log(x) * np.sin(x)

    source = ColumnDataSource(data=dict(x=x, y=y))

    plot = figure()
    plot.line('x', 'y', source=source)

    slider = Slider(start=1, end=10, value=1, step=0.1)

    def callback(attr, old, new):
        y = np.log(x) * np.sin(x*slider.value)
        source.data = dict(x=x, y=y)
    slider.on_change('value', callback)
    
    doc.add_root(column(slider, plot))

We take the function above and configure a FunctionHandler with it. Then we create an Application that uses handler. (It is possible, but uncommon, for Bokeh applications to have more than one handler.) The end result is that the Bokeh server will call modify_doc to build new documents for every new sessions that is opened.


In [ ]:
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application

handler = FunctionHandler(modify_doc)
app = Application(handler)

The Bokeh server runs on a Tornado IOLoop. When we run bokeh serve at the command line, an IOLoop is created and started automatically. In this case we are going to piggy-back off an existing IOLoop, the one that the Jupyter Notebook has.


In [ ]:
from tornado.ioloop import IOLoop
loop = IOLoop.current()

Below is a convenience function for showing the resulting app inline in a Jupyter notebook. In the future, there will be an API built into Bokeh that does this.

WARNING The function below leaks resources. It creates a new Server (with app and handler) every time it is executed, so re-executing some (or all) cells will leave stray Bokeh servers attached to the notebook IOLoop. For apps without periodioc callbacks this is probably often "OK", because the leftover server objects are effectively completely dormant when no events come in to them. But if an app has periodic callbacks, they will continue to run indefinitely, which is probably undesirable. In this case, until there is proper Bokeh API to handle cleanup, it is recommended to run the code below explicitly, and "keep track" of any servers you create. Their sessions can be shut off by executing:

server.get_sessions()[0].destroy()

In [ ]:
def show_app(app, notebook_url="127.0.0.1:8888"):
    from IPython.display import HTML, display
    from bokeh.embed import autoload_server
    from bokeh.server.server import Server
    
    server = Server({'/': app}, io_loop=loop, port=0, host='*', allow_websocket_origin=[notebook_url])
    server.start()
    
    script = autoload_server(model=None, url='http://127.0.0.1:%d' % server.port)
    
    display(HTML(script))

In [ ]:
show_app(app)

In [ ]: