Skip to content

Instantly share code, notes, and snippets.

@Sklavit
Last active May 18, 2023 19:11
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Sklavit/c378a18661f0d91918931eba5a1d7553 to your computer and use it in GitHub Desktop.
Save Sklavit/c378a18661f0d91918931eba5a1d7553 to your computer and use it in GitHub Desktop.
Tornado HTTP web page with embedded Bokeh widget which communicates with other page of the same application

Tornado HTTP web page with embedded Bokeh widget which communicates with other page of the same application

Tornado HTTP web page with embedded Bokeh widget which communicates with other page of the same application.

Features

  • Full Tornado server
  • Bokeh server is started from Tornado server and is executed in the same ioloop
  • Embedded Bohek widget by autoload_server
  • 2 web page communication:
    • one page is data source
    • the second one (with bokeh widgte) is data receiver
  • communicated data is bound to user_id

References

https://github.com/bokeh/bokeh/blob/0.12.4/examples/howto/server_embed/tornado_embed.py http://bokeh.pydata.org/en/latest/docs/user_guide/embed.html#server-data http://bokeh.pydata.org/en/latest/docs/user_guide/server.html#embedding-bokeh-server-as-a-library http://bokeh.pydata.org/en/latest/docs/reference/embed.html#bokeh.embed.autoload_server

Requirement

conda environment description

name: py34
channels: !!python/tuple
- defaults
dependencies:
- backports_abc=0.5=py34_0
- bokeh=0.12.4=py34_0
- jinja2=2.9.5=py34_0
- markupsafe=0.23=py34_2
- mkl=2017.0.1=0
- numpy=1.11.3=py34_0
- pip=9.0.1=py34_1
- python=3.4.5=0
- python-dateutil=2.6.0=py34_0
- pyyaml=3.12=py34_0
- requests=2.13.0=py34_0
- setuptools=27.2.0=py34_1
- six=1.10.0=py34_0
- tornado=4.4.2=py34_0
- vs2010_runtime=10.00.40219.1=2
- wheel=0.29.0=py34_0
- pip:
- backports-abc==0.5
prefix: C:\Miniconda35\envs\py34
# coding=utf-8
from collections import defaultdict
from datetime import datetime
from random import randint
import random
import bokeh
import bokeh.application as bokeh_app
import tornado.web
from bokeh.application.handlers import FunctionHandler
from bokeh.document import Document
from bokeh.embed import autoload_server
from bokeh.layouts import column, widgetbox
from bokeh.models import ColumnDataSource, Button, TableColumn, DateFormatter, DataTable
from tornado import gen
data_by_user = defaultdict(lambda: dict(file_names=[], dates=[], downloads=[]))
doc_by_user_str = dict()
source_by_user_str = dict()
data = dict(x=[], y=[])
class SecondHandler(tornado.web.RequestHandler):
def get(self):
self.render("second_page_template.html")
@gen.coroutine
def post(self, *args, **kwargs):
user_str = str(self.current_user)
data['x'] = random.sample(range(10), 10)
data['y'] = random.sample(range(10), 10)
data_by_user[user_str] = data
source = source_by_user_str[user_str]
@gen.coroutine
def update():
source.data = data
doc = doc_by_user_str[user_str] # type: Document
# Bryan Van de Ven @bryevdv Feb 27 22:23
# @Sklavit actually I can see why next_tick_callback would be needed from another request handler.
# We had other threads in mind but it's also the case that nothing triggering your other request handler
# would acquire a bokeh document lock, so you need to request one by using next_tick_callback
doc.add_next_tick_callback(update)
self.render('second_page_template.html')
class MainHandler(tornado.web.RequestHandler):
@staticmethod
def modify_doc(doc):
source = ColumnDataSource(dict(x=[], y=[]))
columns = [
TableColumn(field="x", title="X"),
TableColumn(field="y", title="Y"),
]
data_table = DataTable(source=source, columns=columns)
user_str = doc.session_context.id
doc_by_user_str[user_str] = doc
source_by_user_str[user_str] = source
doc.add_root(widgetbox(data_table))
_bokeh_app = None
@classmethod
def get_bokeh_app(cls):
if cls._bokeh_app is None:
cls._bokeh_app = bokeh.application.Application(FunctionHandler(MainHandler.modify_doc))
return cls._bokeh_app
@gen.coroutine
def get(self):
user_str = str(self.current_user)
script = autoload_server(model=None, session_id=user_str, # NOTE: MUST be string
app_path='/bokeh/app',
url='http://localhost:5006')
self.render(
'main_page_template.html', active_page='inks_upload',
script=script
)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<iframe src="/second_page" width="100%"></iframe>
{% raw script %}
</body>
</html>
# coding=utf-8
import os.path
import bokeh
import tornado
from bokeh.application.handlers import FunctionHandler
from bokeh.server.server import Server
from bokeh.util.browser import view
from tornado.options import define, options
from tornado_bokeh_inside_embedded_widget.handlers import MainHandler, SecondHandler
define("port", default=8888, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/main_page", MainHandler),
(r"/second_page", SecondHandler),
]
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
# NOTE: some random value as secret (i.e. generated by uuid4())
cookie_secret="YOUR SECRET HERE",
login_url="/auth/login",
debug=True,
)
super(Application, self).__init__(handlers, **settings)
if __name__ == '__main__':
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
io_loop = tornado.ioloop.IOLoop.current()
bokeh_app = bokeh.application.Application(FunctionHandler(MainHandler.modify_doc))
bokeh_server = Server({'/bokeh/app': bokeh_app},
io_loop=io_loop, allow_websocket_origin=['localhost:8888'])
bokeh_server.start(start_loop=False)
io_loop.add_callback(view, "http://localhost:8888/main_page")
io_loop.start()
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form method="post">
<input type="submit">
{% module xsrf_form_html() %}
</form>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment