在代理/jupyterhub 后面的 jupyter notebook 中使用散景服务器

Using bokeh server in jupyter notebook behind proxy / jupyterhub

我想在 jupyter notebook 实例上开发 bokeh 应用程序,该实例在 jupyterhub(也称为身份验证代理)后面运行。我想让交互式散景应用程序回调到笔记本内核。我不想使用笔记本小部件等,因为我希望能够将笔记本导出为 python 文件,并且我可以使用散景服务器提供一些东西。

我笔记本中的以下代码给出了没有错误的空输出:

from bokeh.layouts import row
from bokeh.models.widgets import Button
from bokeh.io import show, output_notebook
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
output_notebook()
# Create the Document Application
def modify_doc(doc):
    layout = row(Button(label="Hello,"),Button(label="world!"))
    doc.add_root(layout)

handler = FunctionHandler(modify_doc)
app = Application(handler)

# Output = BokehJS 0.12.10 successfully loaded.


# New cell
show(app, notebook_url="my-jupyterhub-url.com:80")

# Output = "empty" cell

正在检查已添加脚本标记的单元格:

<script src="http://my-jupyterhub-url.com:46249/autoload.js?bokeh-autoload-element=f8fa3bd0-9caf-473d-87a5-6c7b9680648b&amp;bokeh-absolute-url=http://my-jupyterhub-url.com:46249" id="f8fa3bd0-9caf-473d-87a5-6c7b9680648b" data-bokeh-model-id="" data-bokeh-doc-id=""></script>

这将不起作用,因为端口 46249 在 jupyterhub 代理上未打开。另外路由到 my jupyter 实例的路径是 my-jupyterhub-url.com/user/my-username/ 所以 my-jupyterhub-url.com/autoload.js 不会路由到任何地方。

感觉这可能是一个常见的要求,但搜索尚未显示解决方案。

有什么想法吗?

所以我找到了一个我不满意但有效的解决方案..差不多。

首先在您的 Jupyter 实例上安装 nbserverproxy。这允许您通过 JupyterHub(您经过身份验证的地方)代理到 Jupyter machine/container 上的任意端口。我通过从 Jupyter Web 前端打开终端并输入:

来安装
pip install git+https://github.com/jupyterhub/nbserverproxy --user
jupyter serverextension enable --py nbserverproxy --user

然后重启你的服务器。对于我安装的 JupyterHub,这是 control panel -> stop my server 等待 start my server.

最后我像这样在笔记本中修补了 Ipython.display.publish_display_data(因为源代码显示 bokeh 在调用 show 时使用了它)。

from unittest.mock import patch

from IPython.display import publish_display_data
orig = publish_display_data

import re
def proxy_replacer(display_data):
    for key, item in display_data.items():
        if isinstance(item, str):
            item= re.sub(r'(/user/tam203)/?:([0-9]+)', r'/proxy/', item)
            item = re.sub(r'http:' , 'https:', item)
            display_data[key] = item 
    return display_data


def mock(data, metadata=None, source=None):
    data = proxy_replacer(data) if data else data 
    metadata = proxy_replacer(metadata) if metadata else metadata
    return orig(data, metadata=metadata, source=source)


patcher = patch('IPython.display.publish_display_data', new=mock)
patcher.start()

完成所有这些后,我就可以 运行 下面看到一个不错的动态更新图。

import random
from bokeh.io import output_notebook 
output_notebook()
from bokeh.io import show
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource


def make_document(doc):
    source = ColumnDataSource({'x': [], 'y': [], 'color': []})

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

    doc.add_periodic_callback(update, 100)

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

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

app =  Application(FunctionHandler(make_document))
show(app, notebook_url="<my-domain>.co.uk/user/tam203/")

因此,虽然我很高兴找到了解决方法,但它并不是真正的解决方案。我认为 bokeh 的微小变化可以解决这个问题(类似于 url 模板字符串,您可以在其中指定路径和端口)。

根据官方散景 documentation show(obj, notebook_url=remote_jupyter_proxy_url) 接受 notebook_url 参数值。显然这可以是一个接受端口参数值的函数。

文档更进一步,在 jupyterhub/jupyterlab 和代理扩展的上下文中提供了函数 remote_jupyter_proxy_url 的参考实现。