Index argument to Server

I am trying to debug a bokeh app, and thus make explicit calls to the Server. That works fine, apart from the fact that it doesn’t see my bespoke template, i.e., I use

io_loop = IOLoop.current()
server = Server(applications={
    '/dashboard': Application(FunctionHandler(main))}, io_loop=io_loop, port=5001, index=index
    )
server.start()
server.show('/dashboard')
io_loop.start()

that serves me my app fine, but with the default bokeh template, not my index.html (and yes, I checked that index is set correctly). No error message either, so I am really stumped what I am doing wrong.

It’s possible there is a bug, or maybe it is some usage issue. There’s not really enough information here to speculate. If you can provide a complete Minimal Reproducible Example I will run it directly to investigate.

OK, a very short minimal example (to be put in a directory, to run as app):
Python code (main.py)

#!/usr/bin/python3.9
# 3.9 minimum required for type hinting

import bokeh.models as bkm
import bokeh.layouts as bkl

def main(doc) -> None:
  #define a dropdown (left) for menu
  menu1 = ['a','b']
  dropdown1 = bkm.Dropdown(label="select discipline", menu=menu1)
  pnl = bkl.column(dropdown1, name="menu")
  doc.add_root(pnl)
  #define a placeholder for the right
  div=bkm.Div(text='Placeholder',height=40)
  pn2= bkl.column(div, name="tabs")
  doc.add_root(pn2)

if __name__.startswith('bokeh_app'):
  #run under bokeh serve
  from bokeh.plotting import curdoc
  main(curdoc())
else:
  #run as pythion script
  from bokeh.application.handlers.function import FunctionHandler
  from bokeh.application import Application
  from bokeh.server.server import Server
  from tornado.ioloop import IOLoop

  io_loop = IOLoop.current()
  server = Server(applications={'/app': Application(FunctionHandler(main))},
                  io_loop=io_loop, port=5001,
                  index='./templates/index.html'
  )
  server.start()
  server.show('/app')
  io_loop.start()

templates/index.html

{% extends base %}

{% block title %}FSE Examination dashboard{% endblock %}

{% block postamble %}
<style>
    {% include 'special.css'%}
</style>
{% endblock %}

{% block contents %}
<div class="content">

        <div class="row">
            <div class="leftcol"> <h2>Data Selector</h2><div class="center">{{ embed(roots.menu) }}</div> </div>
            <div class="rightcol"> {{ embed(roots.tabs) }} </div>
        </div>
</div>
{% endblock %}

and the custom css with that


html {
    width: 100%;
}
body {
    min-width: 600px;
    max-width: 900px;
    width: 75%;
    margin: auto;
}
.content {
    margin: 2em;
}
h1 {
    margin: 2em 0 0 0;
    color: #2e484c;
    font-family: 'Julius Sans One', sans-serif;
    font-size: 1.8em;
    text-transform: uppercase;
}
a:link {
    font-weight: bold;
    text-decoration: none;
    color: #0d8ba1;
}
a:visited {
    font-weight: bold;
    text-decoration: none;
    color: #1a5952;
}
a:hover, a:focus, a:active {
    text-decoration: underline;
    color: #9685BA;
}
p {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 1.2em;
    text-align: justify;
    text-justify: inter-word;
}
.row {
  display: flex
}

.leftcol {
  flex: 15%;
}

.rightcol {
  flex: 85%;
}

.center {
  align-content: center;
}

OK, so there is a workaround: template is a document property, so the following in the main(doc) function works:

 from jinja2 import Environment, FileSystemLoader, Template
 env = Environment(loader=FileSystemLoader('./templates'))
 doc._template=env.get_template('index.html')

@Nirwa I think you have misunderstood what the index parameter is for. It controls whether an automatic index page (i.e. at /) is generated, that lists all the apps currently running on the server. It has nothing to do with templates for an app.

You should set the property setter doc.template and not use the private _template version. Also, FYI setting doc.template is in fact the expected mechanism to specify a template, not a workaround.

Actually I just realized perhaps you are thinking of this?

Please be advised that to get all the extra features of directory-style app, you must either:

  • invoke the bokeh serve command line executable with a path to a directory

  • or, if using programmatic APIs, then explicitly construct a DirectoryHandler for the app.

If you use a basic FunctionHandler then you are responsible for setting any templates, static dirs, etc. (i.e. by setting doc.template yourself)

1 Like

Hi Bryan,

I now see I misunderstood what index meant.

I tried the setter first, of course, and it just did not work. The private variable was used as a work around (and it does work!).

Yes, I want to use the bokeh serve in the end, but since my application is a beast (it is a transfer from static matplotlib to bokeh on the web), I have needed python debugging. I want to be able to make a seamless transfer from one to the other–and now I can.

Thanks for the helpful response.

To be honest I don’t really see how that is possible since at present all the setter does is this:

    @template.setter
    def template(self, template: Template) -> None:
        if not isinstance(template, (Template, str)):
            raise ValueError("document template must be Jinja2 template or a string")
        self._template = template

Thanks Brian,
Your argument looks correct to me!
It just was a bit tired of trying to work out why
doc._template = env.get_template('index.html')
works, and
doc.template(env.get_template('index.html'))
doesn’t work (TypeError: ‘Template’ object is not callable)

template is a Python property, it can only be assigned to (or read from). It can’t be called as a function [1]


  1. except in the somewhat obscure situation where the property value is itself another callable object ↩︎

Aha, misread this. I thought it was a setter… Shows what a clumsy python hacker I am, I guess.