Search code examples
pythontornadobokeh

Programmatic Bokeh Server


I am new to Bokeh. I am trying to run a Bokeh server programatically which is started within a PyQt application.

This article http://matthewrocklin.com/blog/work/2017/06/28/simple-bokeh-server describes how to do this, but it requires using python notebook, since the Tornado web server piggybacks on Jupyter notebook’s own IOLoop.

I do not want to use Jupyter notebook, but I want to run it so it "piggybacks on PyQt's io loop".

I have tried running the server as its standalone Thread, but it does not work (accessing the website localhost:5001 just hangs saying "waiting for localhost").

Here is how the thread I currently have. It is called from a QThreadPool within my main PyQt application when a user clicks a button.

class Streamer(QtCore.QRunnable):

    def __init__(self):
        super(Streamer, self).__init__()

    def update(self):
        new = {'x': [random.random()],
               'y': [random.random()],
               'color': [random.choice(['red', 'blue', 'green'])]}
        self.source.stream(new)

    def make_document(self, doc):
        self.source = ColumnDataSource({'x': [], 'y': [], 'color': []})
        doc.add_periodic_callback(self.update, 100)

        self.fig = figure(title='Streaming Circle Plot!', sizing_mode='scale_width',
                     x_range=[0, 1], y_range=[0, 1])
        self.fig.circle(source=self.source, x='x', y='y', color='color', size=10)

        doc.title = "Now with live updating!"
        doc.add_root(self.fig)

    def run(self):
        self.apps = {'/': Application(FunctionHandler(self.make_document))}
        server = Server(self.apps, port=5001)
        server.start()
        server.show('/')
        while True:
            QtCore.QThread.sleep(1)

EDIT:

I tried using server.run_until_shutdown() instead of server.start() but I get an error saying

signal only works in main thread

But I cannot run the bokeh server in my main thread since it hangs my GUI application.


Solution

  • I found one way to get around this.

    Using server.start() does not start the tornado webserver, so nothing will happen in the initial approach. On the other hand, looking at the code for server.run_until_shutdown() generates a system signal before running the tornado IOLoop`, which would not work if the server is running on a thread.

    I found an example in bokeh repository, and linked from the docs, where the torando server can be started manually in a blocking call on the thread (no signals required). Here the code which should go in the run method of the thread:

    def run(self):
      server = Server({'/': self.make_document})
      server.start()
    
      server.io_loop.add_callback(server.show, "/")
      server.io_loop.start()