自定义 Voilà#

您可以通过多种方式自定义 Voilà,以控制您创建的仪表板的外观和感觉。

切换到经典树页面#

Voilà 的默认树页面现在是一个基于 JupyterLab 的应用程序,使用文件浏览器小部件。

基于 jinja 的树页面仍然受支持,但用户需要使用 --classic-tree CLI 选项、VoilaConfiguration.classic_tree 配置或查询字符串中的 ?classic-tree=True 来激活它,例如

voila <path-to-notebook> --classic-tree

JupyterLab 自定义主题在新树页面中受支持,对于经典树页面,仅支持浅色和深色主题。

更改主题#

默认情况下,Voilà 使用 **浅色** 主题,但您可以通过传递以下选项将主题设置为 **深色**

voila <path-to-notebook> --theme=dark

或者通过传递查询参数 theme,例如 URL https://127.0.0.1:8867/voila/render/query-strings.ipynb?theme=dark

主题也可以在笔记本元数据中设置,在 metadata/voila/theme 下,通过手动编辑笔记本文件,或使用例如经典笔记本中的元数据编辑器。

Edit metadata

想要禁用更改主题的系统管理员可以传递 --VoilaConfiguration.allow_theme_override=NO--VoilaConfiguration.allow_theme_override=NOTEBOOK 来完全禁用更改主题,或仅允许从笔记本元数据中更改。

与 nbconvert 一样,Voilà 默认支持 **浅色** 和 **深色** 主题,但您也可以使用自定义 JupyterLab 主题

pip install jupyterlab_miami_nights
voila <path-to-notebook> --theme="JupyterLab Miami Nights"

注意

主题参数是 JupyterLab 中显示的主题名称,而不是 Python 包的名称。如果此功能迁移到 nbconvert,则从笔记本元数据中更改主题可能会在将来发生变化。

警告

主题特定于“lab”模板,它们不适用于“classic”模板。自定义 JupyterLab 主题仅适用于默认树页面,经典树页面仅支持浅色和深色主题。

控制 nbconvert 模板#

Voilà 使用 **nbconvert** 将您的 Jupyter Notebook 转换为 HTML 仪表板。nbconvert 具有丰富的模板系统,允许您自定义将 Jupyter Notebook 转换为 HTML 的方式。

默认情况下,Voilà 将以与笔记本相同的线性方式从笔记本中呈现 HTML。如果您想使用不同的布局,可以通过创建一个新的 nbconvert 模板、将其注册到 Voilà 并从命令行调用它来控制它,如下所示

voila <path-to-notebook> --template=<name-of-template>

例如,Voilà 包含另一个模板,该模板使用 Javascript 库和备用 <div> 布局,以便用户可以拖放单元格。

例如,要使用 gridstack 模板,请使用以下命令

voila <path-to-notebook> --template=gridstack

或者通过传递查询参数 template,例如 URL https://127.0.0.1:8867/voila/render/query-strings.ipynb?template=material(注意,这需要安装 voila-material)。

模板也可以在笔记本元数据中设置,在 metadata/voila/template 下,通过手动编辑笔记本文件,或使用例如经典笔记本中的元数据编辑器。

Edit metadata

想要禁用更改主题的系统管理员可以传递 --VoilaConfiguration.allow_template_override=NO` or ``--VoilaConfiguration.allow_template_override=NOTEBOOK 来完全禁用更改主题,或仅允许从笔记本元数据中更改。

注意

如果此功能迁移到 nbconvert,则从笔记本元数据中更改主题可能会在将来发生变化。

警告

“classic”模板即将弃用,其支持将在 Voilà 1.0.0 中删除,可以使用 voila --template lab --show-margins 来获得类似的外观。

创建您自己的模板#

您可以创建自己的 nbconvert 模板供 Voilà 使用。这使您可以控制仪表板的外观和感觉。

为了创建您自己的模板,首先熟悉 **Jinja**、**HTML** 和 **CSS**。这些都用于创建自定义模板。有关更多信息,请参阅 nbconvert 模板文档。例如,查看 nbconvert 基本 HTML 模板

一些示例 voila/nbconvert 模板项目是

Voilà 模板位于何处?#

所有 Voilà 模板都存储为包含特定配置/模板文件的文件夹。这些文件夹可以存在于标准 Jupyter 配置位置中,位于名为 voila/templates 的文件夹中。例如

~/.local/share/jupyter/voila/templates
~/path/to/env/dev/share/jupyter/voila/templates
/usr/local/share/jupyter/voila/templates
/usr/share/jupyter/voila/templates

Voilà 将在这些位置搜索文件夹(每个模板一个),其中文件夹名称定义模板名称。

Voilà 模板结构#

在每个模板文件夹中,您可以提供自己的 nbconvert 模板、静态文件和 HTML 模板(用于 404 错误等页面)。例如,以下是基本 Voilà 模板(称为“default”)的文件夹结构

tree path/to/env/share/jupyter/voila/templates/default/
├── nbconvert_templates
│   ├── base.tpl
│   └── voila.tpl
└── templates
    ├── 404.html
    ├── error.html
    ├── page.html
    └── tree.html

**要自定义 nbconvert 模板**,请将其存储在名为 templatename/nbconvert_templates/voila.tpl 的文件夹中。在默认模板的情况下,我们还提供了一个 base.tpl,我们的自定义模板使用它作为基础。名称 voila.tpl 是特殊的 - 您不能将自定义 nbconvert 命名为其他名称。

**要自定义 HTML 页面模板**,请将其存储在名为 templatename/templates/<name>.html 的文件夹中。这些是 Voilà 可以作为独立 HTML 提供的文件(例如,tree.html 模板定义了如何在 localhost:8866/voila/tree 中显示文件夹/文件)。您可以通过提供相同名称的自己的 HTML 文件来覆盖默认值。

**要配置您的 Voilà 模板**,您应该在模板文件夹的根目录中添加一个 config.json 文件。

警告

自定义树页面模板仅适用于经典树页面。

自定义模板示例#

为了展示如何创建您自己的自定义模板,让我们创建自己的 nbconvert 模板。我们将有两个目标

  1. 在 Voilà 仪表板中添加一个显示“我们的精彩模板”的 <h1> 标题。

  2. 添加一个显示图像的自定义 404.html 页面。

首先,我们将在 ~/.local/share/jupyter/voila/templates 中创建一个名为 mytemplate 的文件夹

mkdir ~/.local/share/jupyter/voila/templates/mytemplate
cd ~/.local/share/jupyter/voila/templates/mytemplate

接下来,我们将复制 Voilà 的基本模板文件,我们将对其进行修改

cp -r path/to/env/share/jupyter/voila/templates/default/nbconvert_templates ./
cp -r path/to/env/share/jupyter/voila/templates/default/templates ./

现在我们应该有一个这样的文件夹结构

tree .
├── nbconvert_templates
│   ├── base.tpl
│   └── voila.tpl
└── templates
    ├── 404.html
    ├── error.html
    ├── page.html
    └── tree.html

现在,我们将编辑 nbconvert_templates/voila.tpl 以包含自定义 H1 标题。

以及 templates/tree.html 以包含图像。

最后,我们可以告诉 Voilà 在下次我们在 Jupyter 笔记本上使用它时使用此自定义模板,方法是在 --template 参数中使用文件夹的名称

voila mynotebook.ipynb --template=mytemplate

结果应该是带有您自定义修改的 Voilà 仪表板!

Voilà 模板 cookiecutter#

有一个 Voilà 模板 cookiecutter 可供使用,让您有一个良好的开端。此 cookiecutter 包含一些 docker 配置,用于实时重新加载您的模板更改,以使开发更容易。有关如何使用 Voilà 模板 cookiecutter 的更多信息,请参阅 cookiecutter 仓库

访问 tornado 请求(prelaunch-hook#

在某些自定义设置中,当您需要访问 tornado 请求对象以检查身份验证 cookie、访问有关请求标头的详细信息或在渲染之前修改笔记本时。您可以利用 prelaunch-hook,它允许您注入一个函数来在执行它们之前检查笔记本和请求。

警告

因为 prelaunch-hook 仅在收到新请求后但在笔记本执行之前运行,它与 preheated kernels 不兼容。

创建钩子函数#

此钩子的格式应为

def hook(req: tornado.web.RequestHandler,
         notebook: nbformat.NotebookNode,
         cwd: str) -> Optional[nbformat.NotebookNode]:
  • 第一个参数将是对 tornado RequestHandler 的引用,您可以使用它来检查参数、标头等。

  • 第二个参数将是 NotebookNode,您可以对其进行修改,例如注入单元格或进行其他笔记本级修改。

  • 最后一个参数是当前工作目录,如果您需要修改磁盘上的任何内容。

  • 钩子函数的返回值可以是 None,也可以是 NotebookNode

将钩子函数添加到 Voilà#

有两种方法可以将钩子函数添加到 Voilà

  • 使用 voila.py 配置文件

以下是一个配置文件示例。此文件需要放置在您启动 Voilà 的目录中。

def hook_function(req, notebook, cwd):
   """Do your stuffs here"""
   return notebook

c.Voila.prelaunch_hook = hook_function
  • 从 Python 脚本启动 Voilà

以下是一个使用 papermill 执行笔记本的自定义 prelaunch-hook 示例

def parameterize_with_papermill(req, notebook, cwd):
    import tornado

    # Grab parameters
    parameters = req.get_argument("parameters", {})

    # try to convert to dict if not e.g. string/unicode
    if not isinstance(parameters, dict):
        try:
            parameters = tornado.escape.json_decode(parameters)
        except ValueError:
            parameters = None

    # if passed and a dict, use papermill to inject parameters
    if parameters and isinstance(parameters, dict):
        from papermill.parameterize import parameterize_notebook

        # setup for papermill
        #
        # these two blocks are done
        # to avoid triggering errors
        # in papermill's notebook
        # loading logic
        for cell in notebook.cells:
            if 'tags' not in cell.metadata:
                cell.metadata.tags = []
            if "papermill" not in notebook.metadata:
                notebook.metadata.papermill = {}

        # Parameterize with papermill
        return parameterize_notebook(notebook, parameters)

要将此钩子添加到您的 Voilà 应用程序中

from voila.app import Voila
from voila.config import VoilaConfiguration

# customize config how you like
config = VoilaConfiguration()

# create a voila instance
app = Voila()

# set the config
app.voila_configuration = config

# set the prelaunch hook
app.prelaunch_hook = parameterize_with_papermill

# launch
app.start()

添加您自己的静态文件#

如果您创建了自己的主题,您可能还想定义和使用自己的静态文件,例如 CSS 和 Javascript。要使用您自己的静态文件,请按照以下步骤操作

  1. 与您的模板一起创建一个文件夹(例如,mytemplate/static/)。

  2. 将您的静态文件放在此模板中。

  3. 在您的模板文件(例如 voila.tpl)中,使用以下路径链接这些静态文件

    {{resources.base_url}}voila/static/<path-to-static-files>
    
  4. 当您调用 voila 时,请使用 --static kwarg 配置静态文件夹,或配置 --VoilaConfiguration.static_root

此配置中给出的文件夹/文件中的任何文件夹/文件都将被复制到 {{resources.base_url}}voila/static/

例如,如果您在 static/css 中有一个名为 custom.css 的 CSS 文件,您将在模板中像这样链接它

<link rel="stylesheet" type="text/css" href="{{resources.base_url}}voila/static/css/custom.css"></link>

为 Jupyter Server 配置 Voilà#

voila 运行时,可以控制其几个功能。这可以通过独立 CLI 或 Jupyter Server 完成。要配置 voila,当 Jupyter Server 运行时,在调用运行 Jupyter 的命令(例如,Jupyter Lab 或 Jupyter Notebook)时使用以下模式

<jupyter-command> --VoilaConfiguration.<config-key>=<config-value>

例如,要从 Jupyter Lab 会话中控制 voila 使用的模板,在启动服务器时使用以下命令

jupyter lab --VoilaConfiguration.template=distill

当用户通过点击 voila/ 端点运行 voila 时,将使用此配置。

激活令牌身份验证#

通过使用 jupyter-server 2,Voilà 支持令牌身份验证,但默认情况下它是禁用的。

  • 要使用自动生成的令牌启动 Voila

voila --token notebook.ipynb
  • 要使用个性化令牌启动 Voila

voila --token=my-secret-token notebook.ipynb

提供静态文件#

与 JupyterLab 或经典笔记本服务器不同,voila 不会提供笔记本目录中存在的所有文件。只有与允许列表中的一个正则表达式匹配且与拒绝列表中的任何正则表达式都不匹配的文件才会由 Voilà 提供

voila mydir --VoilaConfiguration.file_allowlist="['.*']" \
  --VoilaConfiguration.file_denylist="['private.*', '.*\.(ipynb)']"

这将提供所有文件,除了以 private 开头的任何文件或笔记本文件

voila mydir --VoilaConfiguration.file_allowlist="['.*\.(png|jpg|gif|svg|mp4|avi|ogg)']"

将提供许多媒体文件,并且永远不会提供笔记本文件(这是默认的拒绝列表)。

运行脚本#

Voilà 可以通过配置文件扩展名与内核语言的映射关系来运行文本(或脚本)文件。

voila mydir --VoilaConfiguration.extension_language_mapping='{".py": "python", ".jl": "julia"}'

Voilà 会找到与指定语言匹配的内核,但也可以配置为对每种语言使用特定的内核。

voila mydir --VoilaConfiguration.extension_language_mapping='{".py": "python", ".jl": "julia"}'\
  --VoilaConfiguration.language_kernel_mapping='{"python": "xpython"}'

在这种情况下,它将使用 xeus-python 内核来运行 .py 文件。

请注意,脚本将作为具有单个单元格的笔记本执行,这意味着只有最后一个表达式将作为输出打印。使用 Jupyter 显示机制来输出任何文本或富输出,例如 Jupyter 小部件。对于 Python,这将是调用 IPython.display.display

使用 Jupytext 是支持脚本文件的另一种方法。安装 jupytext 后,Voilà 会将脚本文件视为笔记本,无需额外配置。

清理空闲内核#

每次向用户呈现笔记本时,Voilà 都会启动一个新的 Jupyter 内核。在某些情况下,这会导致更高的内存消耗。

Jupyter Server 公开了几个选项,可用于终止不再活动的内核。它们可以使用 Voilà 独立应用程序进行配置。

voila --MappingKernelManager.cull_interval=60 --MappingKernelManager.cull_idle_timeout=120

服务器将定期检查空闲内核,在本例中每 60 秒检查一次,如果它们空闲超过 120 秒,则会清理它们。

使用 Voilà 作为服务器扩展时,相同的参数适用。

jupyter notebook --MappingKernelManager.cull_interval=60 --MappingKernelManager.cull_idle_timeout=120

还有 MappingKernelManager.cull_busyMappingKernelManager.cull_connected 选项来清理繁忙的内核和具有活动连接的内核。

有关这些选项的更多信息,请查看 Jupyter Server 文档。

预热内核#

由于 Voilà 需要为每个连接启动一个新的 jupyter 内核并在该内核中执行请求的笔记本,这会导致在小部件显示在浏览器中之前出现较长的等待时间。为了减少这种等待时间,尤其是对于大型笔记本,用户可以激活 Voilà 的预热内核选项。

警告

由于预热内核不是按需执行的,因此此功能与 prelaunch-hook 功能不兼容。

此选项将启用两个功能

  • 为每个笔记本启动一个内核池并保持待机状态,然后在池中的每个内核中执行笔记本。当新客户端请求内核时,将使用此池中的预热内核,并异步启动另一个内核以补充池。

  • 笔记本的 HTML 版本在每个预热内核中呈现并存储,当客户端连接到 Voilà 时,在某些条件下,将提供缓存的 HTML 而不是重新呈现笔记本。

预热内核选项适用于任何内核管理器,默认情况下处于停用状态,通过设置 preheat_kernel = True 重新激活它。例如,使用此命令,对于每个启动的 Voilà 笔记本,都会创建一个包含 5 个内核的池,并将用于新的连接。

voila --preheat_kernel=True --pool_size=5

预热内核的默认环境变量可以通过 VoilaKernelManager.default_env_variables 设置进行设置。例如,此命令

voila --preheat_kernel=True --VoilaKernelManager.default_env_variables='{"FOO": "BAR"}'

将在所有预热内核中设置变量“FOO”。

如果池的大小不符合用户的要求,或者一些笔记本需要使用特定的环境变量……,则需要额外的设置。更改这些设置的最简单方法是在包含笔记本的同一文件夹中提供一个名为 voila.json 的文件。预热内核的设置(不需要预热内核的笔记本列表、池中的内核数量、重新填充延迟、启动内核的环境变量……)可以在 VoilaKernelManager 类名下设置。

以下是一个带有预热内核选项说明的设置示例。

# voila.json
{
   "VoilaConfiguration": {
      # Activate or deactivate preheat kernel option.
      "preheat_kernel": true
   },
   "VoilaKernelManager": {
      # A list of notebook name or regex patterns to exclude notebooks from using preheat kernel.
      "preheat_denylist": [
         "notebook-does-not-need-preheat.ipynb",
         "^.*foo.*$",
         ...
      ],
      # Configuration for kernel pools
      "kernel_pools_config": {
         # Setting for `voila.ipynb` notebook
         "voila.ipynb": {
            "pool_size": 3, # Size of pool
            "kernel_env_variables": { # The environment variables used to start kernel for `voila.ipynb`
               "foo2": "bar2"
            }
         },
         # Setting for `test/sub-voila.ipynb` notebook
         "test/sub-voila.ipynb": {
            "pool_size": 1
         },
         ...
         # If a notebook does not have setting, it will use default setting
         "default": {
            "pool_size": 2,
            "kernel_env_variables": {
               "foo": "bar"
            }
         },
      },
      # Delay time in second before filling the kernel pool.
      "fill_delay": 0
   }
}

笔记本 HTML 将使用 VoilaConfiguration 或笔记本元数据中定义的模板和主题进行预渲染。如果满足以下条件,则使用预热的内核和缓存的 HTML

  • 内核池中有一个可用的预热内核。

  • 如果用户使用查询字符串覆盖模板/主题,它必须与用于预渲染笔记本的模板/主题匹配。

如果内核池为空或请求不符合这些条件,Voilà 将回退到启动一个正常的内核并像往常一样渲染笔记本。

部分预渲染笔记本#

为了利用预热内核模式的加速优势,笔记本需要在用户实际连接到 Voilà 之前进行预渲染。但在许多实际情况下,笔记本需要一些特定于用户的数据才能正确渲染小部件,这使得预渲染变得不可能。为了克服这一限制,Voilà 提供了一个功能来处理提供用户数据的最常用方法:URL query string

注意

有关与龙卷风请求对象的更高级交互,请参阅 prelaunch-hook 功能。

在正常模式下,Voilà 用户可以通过 QUERY_STRING 环境变量在运行时获取 query string

import os
query_string = os.getenv('QUERY_STRING')

在预热内核模式下,用户可以在 voila.utils 中的 wait_for_request 前面添加。

import os
from voila.utils import wait_for_request
wait_for_request()
query_string = os.getenv('QUERY_STRING')

wait_for_request 将在预热内核中的此单元格暂停笔记本的执行,并等待实际用户连接到 Voilà,设置请求信息环境变量,然后继续执行剩余的单元格。

如果 Voilà websocket 处理程序不是使用默认协议 (ws)、默认 IP 地址 (127.0.0.1)、默认端口 (8866) 或使用 url 后缀启动的,用户需要通过环境变量 VOILA_WS_PROTOCOLVOILA_APP_IPVOILA_APP_PORTVOILA_WS_BASE_URL 提供这些值。设置这些变量的一种方法是在 voila.json 配置文件中,例如

# voila.json
{
   ...
   "VoilaKernelManager": {
      "kernel_pools_config": {
         "foo.ipynb": {
            "kernel_env_variables": {
               "VOILA_APP_IP": "192.168.1.1",
               "VOILA_APP_PORT": "6789",
               "VOILA_WS_PROTOCOL": "wss"
            }
         }
      },
   ...
   }
}

此外,您可以使用以下命令设置这些变量

voila --preheat_kernel=True --VoilaKernelManager.default_env_variables='{"VOILA_WS_PROTOCOL":"wss","VOILA_APP_IP":"192.168.1.1"}'

根据单元格标签隐藏输出和代码单元格#

Voilà 在幕后使用 nbconvert 来渲染笔记本,因此我们可以利用它的一些高级功能来根据单元格标签隐藏代码和输出单元格。

要隐藏笔记本中所有已标记 (如何标记) 为“hide”的单元格的单元格输出,在 Voilà 中

voila --TagRemovePreprocessor.remove_all_outputs_tags='{"hide"}' your_notebook.ipynb

要隐藏所有已标记为“hide”的单元格的代码单元格和输出单元格(如果有),

voila --TagRemovePreprocessor.remove_cell_tags='{"hide"}' your_notebook.ipynb

您可以使用任何您想要的标签,但请确保在 Voilà 命令中使用相同的标签名称。请注意,此功能只会隐藏 Voilà 中的单元格,但不会阻止它们执行。

单元格执行超时#

默认情况下,Voilà 没有执行超时,这意味着 Voilà 执行和渲染笔记本的时间没有限制。如果您有潜在的长时间运行的单元格,您可能希望设置单元格执行超时,以便在笔记本执行时间超过预期时,仪表板用户会收到错误。例如

voila --VoilaExecutor.timeout=30 your_notebook.ipynb

使用此设置,如果任何单元格运行时间超过 30 秒,将引发 TimeoutError。您可以使用 VoilaExecutor.timeout_funcVoilaExecutor.interrupt_on_timeout 选项进一步自定义此行为。

自定义 Voilà 预览小部件#

通过使用 JupyterLab 的 布局自定义系统,用户可以配置 Voilà 预览小部件的位置,使其在与 main 不同的区域打开。

Voila Preview 是预览小部件的设置键。例如,以下配置将在此小部件在 JupyterLab 的右侧面板中打开

"layout": {
   "multiple": {
      "Voila Preview": { "area": "right" }
   }
}