How to add more applications to a running bokeh server

Is it possible to add further applications (that is, bokeh documents) to a running bokeh server?

For example, consider the flask application with embedded bokeh server:
https://github.com/bokeh/bokeh/blob/0.12.15/examples/howto/server_embed/flask_embed.py

Namely, how to add further applications to the server created in this step:
server = Server({’/bkapp’: modify_doc}, io_loop=IOLoop(), allow_websocket_origin=[“localhost:8000”])

``

Artur

To answer your first question, you can also simply call
bokeh serve path/to/app1 path/to/app2

``

As for your second question regarding the specific use of Server, you can see that in the docs (bokeh.server.server — Bokeh 3.3.2 Documentation) you pass a dictionary of applications. Just add them in the dictionary.

···

Le lundi 7 mai 2018 13:04:21 UTC+2, Artur Scholz a écrit :

Is it possible to add further applications (that is, bokeh documents) to a running bokeh server?

For example, consider the flask application with embedded bokeh server:
https://github.com/bokeh/bokeh/blob/0.12.15/examples/howto/server_embed/flask_embed.py

Namely, how to add further applications to the server created in this step:
server = Server({‘/bkapp’: modify_doc}, io_loop=IOLoop(), allow_websocket_origin=[“localhost:8000”])

``

Artur

Thanks. I was referring to embedded server only. And my question was on how to add applications

afterwards, i.e. after having passed the applications dict to the bokeh server instantiation.

After reading through the docs and code, I am convinced that is not possible in that way.

So instead, rather than using one bokeh server and adding applications on the fly, I create one
dedicated embedded bokeh server for each application at startup, each at a distinct port.

server.py:
from threading import Thread, Lock
import socket

from bokeh.server.server import Server
from bokeh.embed import server_document
from tornado.ioloop import IOLoop

mutex = Lock()

def get_free_tcp_port():
tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp.bind((‘’, 0))
addr, port = tcp.getsockname()
tcp.close()
return port

class BokehServer:
“”“Bokeh server (based on Tornado HTTPServer) to synchronize client and server
documents via websocket.
“””
def init(self, application, websocket_origin=[‘*’]):
if not callable(application):
raise ValueError()
self.application = application
self.websocket_origin = websocket_origin
self.thread = Thread(target=self._server_thread)
self.thread.start()

def _server_thread(self):
    self.io_loop = IOLoop()
    with mutex:
        self.port = get_free_tcp_port()
        self.server = Server(
            self.application, io_loop=self.io_loop,
            allow_websocket_origin=self.websocket_origin,
            port=self.port)
    self.server.start()
    self.server.io_loop.start()

def get_script(self):
    return server_document('[http://localhost](http://localhost):' + str(self.port))

``

view.py
def plot(doc):

… create a bokeh figure and add as root element to doc

server = server.BokehServer(plot)

@demo.route(‘/domain/demo/view/demo-plot’)
def view_demo_plot():
script = server.get_script()
return render_template(‘demo/plot.html’, script=script)

``

···

On Tue, May 8, 2018 at 3:41 PM, hyamanieu [email protected] wrote:

To answer your first question, you can also simply call
bokeh serve path/to/app1 path/to/app2

``

As for your second question regarding the specific use of Server, you can see that in the docs (https://bokeh.pydata.org/en/latest/docs/reference/server/server.html#bokeh.server.server.Server) you pass a dictionary of applications. Just add them in the dictionary.

Le lundi 7 mai 2018 13:04:21 UTC+2, Artur Scholz a écrit :

Is it possible to add further applications (that is, bokeh documents) to a running bokeh server?

For example, consider the flask application with embedded bokeh server:
https://github.com/bokeh/bokeh/blob/0.12.15/examples/howto/server_embed/flask_embed.py

Namely, how to add further applications to the server created in this step:
server = Server({‘/bkapp’: modify_doc}, io_loop=IOLoop(), allow_websocket_origin=[“localhost:8000”])

``

Artur

You received this message because you are subscribed to a topic in the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this topic, visit https://groups.google.com/a/continuum.io/d/topic/bokeh/HNsguacFpYA/unsubscribe.

To unsubscribe from this group and all its topics, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/764f822a-09a3-4bdb-9b12-ec0f98a1ccfb%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi,

It's probably possible to find some way to do this, since Tornado seems to support adding routes dynamically:

  Adding new handler to running python tornado server - Stack Overflow

Unfortunately it's not something I can devote time to looking into just now, but if someone plays around some and finds a way, we'd be happy to codify it more formally in the core library.

Thanks,

Bryan

···

On May 9, 2018, at 02:33, Artur Scholz <[email protected]> wrote:

Thanks. I was referring to embedded server only. And my question was on how to add applications
afterwards, i.e. after having passed the applications dict to the bokeh server instantiation.

After reading through the docs and code, I am convinced that is not possible in that way.

So instead, rather than using one bokeh server and adding applications on the fly, I create one
dedicated embedded bokeh server for each application at startup, each at a distinct port.

server.py:
from threading import Thread, Lock
import socket

from bokeh.server.server import Server
from bokeh.embed import server_document
from tornado.ioloop import IOLoop

mutex = Lock()

def get_free_tcp_port():
    tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp.bind(('', 0))
    addr, port = tcp.getsockname()
    tcp.close()
    return port

class BokehServer:
    """Bokeh server (based on Tornado HTTPServer) to synchronize client and server
    documents via websocket.
    """
    def __init__(self, application, websocket_origin=['*']):
        if not callable(application):
            raise ValueError()
        self.application = application
        self.websocket_origin = websocket_origin
        self.thread = Thread(target=self._server_thread)
        self.thread.start()

    def _server_thread(self):
        self.io_loop = IOLoop()
        with mutex:
            self.port = get_free_tcp_port()
            self.server = Server(
                self.application, io_loop=self.io_loop,
                allow_websocket_origin=self.websocket_origin,
                port=self.port)
        self.server.start()
        self.server.io_loop.start()

    def get_script(self):
        return server_document('http://localhost:' + str(self.port))

view.py
def plot(doc):
    # ... create a bokeh figure and add as root element to doc

server = server.BokehServer(plot)

@demo.route('/domain/demo/view/demo-plot')
def view_demo_plot():
    script = server.get_script()
    return render_template('demo/plot.html', script=script)

On Tue, May 8, 2018 at 3:41 PM, hyamanieu <[email protected]> wrote:
To answer your first question, you can also simply call
bokeh serve path/to/app1 path/to/app2

As for your second question regarding the specific use of Server, you can see that in the docs (bokeh.server.server — Bokeh 3.3.2 Documentation) you pass a dictionary of applications. Just add them in the dictionary.

Le lundi 7 mai 2018 13:04:21 UTC+2, Artur Scholz a écrit :

Is it possible to add further applications (that is, bokeh documents) to a running bokeh server?

For example, consider the flask application with embedded bokeh server:
https://github.com/bokeh/bokeh/blob/0.12.15/examples/howto/server_embed/flask_embed.py

Namely, how to add further applications to the server created in this step:
server = Server({'/bkapp': modify_doc}, io_loop=IOLoop(), allow_websocket_origin=["localhost:8000"])

Artur

--
You received this message because you are subscribed to a topic in the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this topic, visit https://groups.google.com/a/continuum.io/d/topic/bokeh/HNsguacFpYA/unsubscribe\.
To unsubscribe from this group and all its topics, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/764f822a-09a3-4bdb-9b12-ec0f98a1ccfb%40continuum.io\.

For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/CALqfExZgnqJ6172uVYg%3Dt47SYyRY_QJdSspB6qD2%2B4qk0T3jOA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

So, I have been working on a solution to this, and so far I have a (not very good) way to add applications. Trying to find a way to remove them as well.

I essentially reused a part from the __init__ code from bokeh.server.tornado.BokehTornado and called the add_handlers from tornado.web.Application.

Maybe this can be more refined and implemented inside the BokehTornado class.

from bokeh.server.contexts import ApplicationContext
from bokeh.application import Application
from bokeh.server.server import Server
from bokeh.server.urls import per_app_patterns
from tornado.web import StaticFileHandler
from typing import Dict

def add_applications(applications: Dict[str, Application], server: Server):
    
    for k, v in applications.items():
        server._tornado._applications[k] = ApplicationContext(v, url=k)

        app_patterns = []
        for p in per_app_patterns:
            route = server._tornado._prefix + ('' if k == '/' else k) + p[0]
            app_patterns.append((route, p[1], {"application_context": server._tornado._applications[k]}))
        
        websocket_path = None
        for r in app_patterns:
            if r[0].endswith('/ws'):
                websocket_path = r[0]
        if not websocket_path:
            raise RuntimeError("Couldn't find websocket path")
        for r in app_patterns:
            r[2]["bokeh_websocket_path"] = websocket_path
        
        if v.static_path is not None:
            route = server._tornado._prefix + ('' if k == '/' else k) + "/static/(.*)"
            app_patterns.append((route, StaticFileHandler, {"path": v.static_path}))
        server._tornado.add_handlers(host_pattern='.*$', host_handlers=app_patterns)
        
        server._tornado._applications[k]._loop = server._loop

        if server._started:
            server._tornado._applications[k].run_load_hook()

Hi Artur,

I’m facing a similar situation, where I want to design some sort of dataset browser, and launching a bokeh app/ or redirecting to a running app to visualize a dataset. I’m wondering if you ever found out the best way to go about this?

There’s no way to add applications to a standard Bokeh server, e.g if you are using bokeh serve. The Bokeh server can be embedded as programmatically in which case (in principle) you might be able to add applications dynamically by configuring properties and methods on the Server object directly (I have never tried, personally, though).

Can I ask why you want to add applications dynamically? If you want to e.g. add new applications based on user input, that seems like a bad idea from a security perspectice, since the user-submitted code would have unfettered access to the python interpreter.

I see. Thanks for the advice. I am currently building a sort of browser for an image viewer built in Bokeh. So the idea is to have a list of images that can be visualized with the app, maybe in the form of an html page, and have the ability to click on an image to launch the app to visualize it. If you have thoughts on the best way to go about that, I’d greatly appreciate it.

Are all the images visualized in the same way (i.e. is the only thing that is different the images?) If so, definitely one app, that has individual sessions parameterized for different images by HTTP query arguments: