Panel regression? (embedding panel in gunicorn+Flask)

I previously developed an application that embeds a panel server in Flask running via gunicorn that uses panel 0.9.7.

After upgrading to panel 0.10.2, in order to incorporate new features, e.g. a panel Card layout, things no longer work. The symptom of not working is no UI widgets or bokeh figures in the panels being rendered, and a collection of ERR_ABORTED 404 (NOT FOUND) errors in the JavaScript console of the client browser.

To rule out anything related to my quite-involved application, I reverted to the bokeh-inspired reference example outlined here.

I have confirmed that this yields similar behavior. It works in panel 0.9.7, but fails in panel 0.10.2.

The JavaScript console errors are shown in the following screen capture.

2 Likes

@_jm, I was able to replicate your error on my machine, and then resolve it by adding the following to the top of flask_gunicorn_embed.py:

from bokeh.settings import settings

settings.resources = 'inline'

Please let me know if you run into any other issues. I have a working example which I can reference for further detail, but I’ll refrain from overloading specifics here, because I do think that’s all it should take.

1 Like

@cisaacstern

Great. Thanks!

Can confirm that the above settings is a solution for my actual, deployed applications as well, when upgrading from panel 0.9.7 to 0.10.2

2 Likes

Happy to help! My first solution :partying_face:

2 Likes

Do you have a minimal example I could reproduce with? Trying to ensure this is fixed in Panel 0.10.3.

@philippjfr

Thanks for the follow-up. This topic mentions using panel 0.10.2 and flask under gunicorn. However, the problem can still be reproduced using just panel and flask, i.e. without the gunicorn layer.

Starting with the bokeh server embed example, flask_embed.py, I stripped out the theme and replaced the bokeh layout with panel’s syntax to yield the following example. This example works as expected in panel 0.9.7 but throws 404 errors in panel 0.10.2

 * Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET / HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/alerts.css HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/alerts.css HTTP/1.1" 404 -
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/card.css HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/card.css HTTP/1.1" 404 -
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/widgets.css HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/widgets.css HTTP/1.1" 404 -
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/markdown.css HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/markdown.css HTTP/1.1" 404 -
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/json.css HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/json.css HTTP/1.1" 404 -
127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/dataframe.css HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [18/Jan/2021 08:42:27] "GET /static/extensions/panel/css/dataframe.css HTTP/1.1" 404 -
^C^CException ignored in: <module 'threading' from '/Users/monaco/opt/anaconda3/lib/python3.8/threading.py'> 

Here’s the example code and HTML embed template.

flask_embed.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""

from threading import Thread

from flask import Flask, render_template
from tornado.ioloop import IOLoop

from bokeh.embed import server_document
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
from bokeh.server.server import Server

import panel as pn

app = Flask(__name__)


def bkapp(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data=df)

    plot = figure(x_axis_type='datetime', y_range=(0, 25), y_axis_label='Temperature (Celsius)',
                  title="Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source=source)

    def callback(attr, old, new):
        if new == 0:
            data = df
        else:
            data = df.rolling(f"{new}D").mean()
        source.data = ColumnDataSource.from_df(data)

    slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
    slider.on_change('value', callback)

    pn.Column(slider, plot).server_doc(doc)


@app.route('/', methods=['GET'])
def bkapp_page():
    script = server_document('http://localhost:5006/bkapp')
    return render_template("embed.html", script=script, template="Flask")


def bk_worker():
    # Can't pass num_procs > 1 in this configuration. If you need to run multiple
    # processes, see e.g. flask_gunicorn_embed.py
    server = Server({'/bkapp': bkapp}, io_loop=IOLoop(), allow_websocket_origin=["localhost:8000"])
    server.start()
    server.io_loop.start()

Thread(target=bk_worker).start()

if __name__ == '__main__':
    print('Opening single process Flask app with embedded Bokeh application on http://localhost:8000/')
    print()
    print('Multiple connections may block the Bokeh app in this configuration!')
    print('See "flask_gunicorn_embed.py" for one way to run multi-process')
    app.run(port=8000)

embed.html

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Embedding a Bokeh Server With {{ framework }}</title>
</head>

<body>
  <div>
    This Bokeh app below served by a Bokeh server that has been embedded
    in another web app framework. For more information see the section
    <a  target="_blank" href="https://docs.bokeh.org/en/latest/docs/user_guide/server.html#embedding-bokeh-server-as-a-library">Embedding Bokeh Server as a Library</a>
    in the User's Guide.
  </div>
  {{ script|safe }}
</body>
</html>

Thanks, appears to be fixed with a recent dev release. Planning to release Panel 0.10.3 today so would you mind testing the dev release? You can install it with pip install --pre panel or conda install -c pyviz/label/dev panel.

The dev version installed via conda fixes the issue with the flask-embed example. For completeness, here’s the panel version info I see with the dev version.

Type "help", "copyright", "credits" or "license" for more information.
>>> import panel
>>> panel.__version__
'0.11.0a6'
>>>
2 Likes

Thanks for checking. Will be releasing Panel 0.10.3 in the next hour barring any last minute issues cropping up.

1 Like

Hi Philipp,

I encounter the same issue with Panel 0.11.3, I had to downgrade to 0.11.1 for my project to work. The problem appears starting 0.11.2.

The bug is still the same : the URL of the CSS are relative, not absolute. Thus, if I serve bokeh documents (with port 5006) via Flask (port 8080), Panel CSS are loading with port 8080 instead of 5006.

# from flask app, when reaching 127.0.0.1:8080
script = server_document('http://127.0.0.1:5006/bokeh_page')
return render_template("embed.html", script=script)

When I check the page http://127.0.0.1:5006:bokeh_page/autoload.js, I see the following :

the last CSS urls are relative, which results in a 404 when the page is embedded.

This suggested workaround above still works though :

from bokeh.settings import settings
settings.resources = 'inline'

I hope this helps. Thanks for your time and your help :slight_smile: