面向多个应用程序#

让您的扩展在 JupyterLab、Notebook 7+ 及更多环境中运行!

Jupyter Notebook 7 使用 JupyterLab 的组件构建,由于两者使用相同的构建块,这意味着您的扩展可以在两者(或任何其他使用 JupyterLab 组件构建的前端)上运行,并且根据其设计,几乎不需要或根本不需要修改。

本指南将概述兼容性功能,然后提供一个教程和参考代码,涵盖此处提到的部分主题。如果您不知道如何制作扩展,您可以阅读有关基础知识的更多信息,请访问 扩展教程扩展页面

入门#

从高层次来看,JupyterLab 和 Jupyter Notebook 的扩展通常都从一个模板项目开始,您可以使用 扩展教程 中的说明下载并设置该项目。模板准备就绪后,您可以开始添加组件和功能来构建您的扩展。

兼容性工作原理#

JupyterLab(以及 Notebook 7)的扩展由一系列捆绑的 插件 组成,这些插件通常使用来自界面工具包 Lumino 以及 JupyterLab API(以及其他)的组件来帮助构建扩展的外观和行为。Lumino 和 JupyterLab API 都是用 Typescript 编写的。

这就是基本兼容性功能的工作原理:这两个应用程序使用相同的构建块和方法。例如,JupyterLab 和 Notebook 7 都接受 Lumino 小部件作为界面组件,这两个应用程序都允许您通过指定一个“区域”来将它们添加到界面中,并且这两个应用程序的扩展都使用相同的 JupyterFrontendPlugin 类。

如何实现兼容性#

在一些简单的情况下,无需额外努力即可实现兼容性。但是,对于更复杂的情况,如果扩展使用了某个应用程序中其他应用程序不可用的功能,您需要决定如何在其他应用程序中处理这些功能。

兼容性的技术解决方案基本上提供了禁用某些应用程序的功能的方法,或者检查特定应用程序或功能是否存在,然后相应地调整行为。

还有一些设计模式和方法可以更容易地在您的扩展中实现兼容性。

您可以在下面阅读更多相关内容。以下是一些使您的扩展兼容的一般提示

  • 尽可能使用通用功能,这些功能无需任何额外努力即可实现兼容性

  • 在没有先检查扩展加载时或运行时是否可用(稍后将详细介绍)的情况下,避免使用特定于应用程序的功能(例如 JupyterLab 状态栏)

  • 尝试将特定于应用程序的功能与两个应用程序都支持的通用功能分开

  • 决定如何处理依赖于特定应用程序功能(如 JupyterLab 状态栏)的任何扩展功能。您可以使用本文档后面列出的技术在其他应用程序中禁用或修改这些功能。

仅使用通用功能#

如果您的扩展只使用 JupyterLab 和 Notebook 7 都有的功能,您可以直接安装它,它将在 JupyterLab 和 Notebook 7 中无缝运行。

例如,一个向 UI 顶部栏添加单个独立文本小部件的扩展,不需要做任何事情就能与 JupyterLab 和 Notebook 7 兼容(这两个应用程序都有一个可以容纳小部件的顶部区域,因此它在安装和启动后将在 JupyterLab 和 Notebook 7 中可见)。

请参阅此示例视频,它展示了一个兼容的顶部栏文本小部件,该小部件在 JupyterLab 和 Notebook 7 中开箱即用,以及此处阅读完整的扩展示例代码

请注意,使用 JupyterLab 和 Notebook 7(或其他应用程序)不共有的功能将导致在该功能不可用的应用程序中出现兼容性问题,除非采取特殊措施(更多信息将在后面介绍)。JupyterLab 中的状态栏就是一个例子,它在 JupyterLab 中可用,但在 Notebook 7 中不可用。

使用插件元数据来驱动兼容性#

JupyterLab 的扩展系统旨在使插件能够依赖于彼此的功能并重用彼此的功能。这种方法的关键部分是JupyterLab 的提供者-消费者模式(一种依赖注入模式),它使本文档中讨论的兼容性解决方案成为可能。

每个插件都使用一些属性(requiresoptional 属性)来请求它想要的功能,这些功能由已加载到 JupyterLab 中的其他插件提供。当您的插件请求功能时,系统会将这些功能发送到您的插件的激活函数(如果可用)。

您可以利用这些插件属性以及插件系统如何使用它们来构建兼容的扩展。

  • 当您在插件的 requires 列表中指定一个功能时,JupyterLab 只有在该功能可用时才会加载您的插件。

  • 通过在 optional 列表中指定一个功能,JupyterLab 将为您传递一个该功能的对象(如果可用)或 null(如果不可用)。

因此,这些功能构成了扩展兼容性的基础。您可以使用它们在扩展中进行检查,以使它们能够在 JupyterLab 和 Jupyter Notebook 7(以及其他应用程序)中正常运行。

JupyterLab 本身是提供者,通过其内置插件提供许多功能,您可以在通用扩展点文档中了解更多信息。在构建扩展时,最好使用这些扩展点(这样做时,您充当 JupyterLab 的提供者-消费者模式中的消费者)。

测试可选功能#

使特定于应用程序的功能可选,并在使用它之前检查它是否可用,是使扩展兼容的一种技术。

请查看示例存储库中此示例扩展中的代码片段(您可以在那里阅读完整的扩展示例代码)。

const plugin: JupyterFrontEndPlugin<void> = {
  id: '@jupyterlab-examples/shout-button:plugin',
  description:
    'An extension that adds a button and message to the right toolbar, with optional status bar widget in JupyterLab.',
  autoStart: true,
  // The IStatusBar is marked optional here. If it's available, it will
  // be provided to the plugin as an argument to the activate function
  // (shown below), and if not it will be null.
  optional: [IStatusBar],
  // Make sure to list any 'requires' and 'optional' features as arguments
  // to your activate function (activate is always passed an Application,
  // then required arguments, then optional arguments)
  activate: (app: JupyterFrontEnd, statusBar: IStatusBar | null) => {
    // ... Extension code ...
  }
};

此插件将 IStatusBar 标记为可选,并为插件的 activate 函数添加了一个参数(该函数将在扩展加载时由 JupyterLab 调用)。如果 IStatusBar 不可用,则 activate 函数的第二个参数将为 null,就像在 Jupyter Notebook 7 中加载扩展时一样。

扩展始终创建一个通用的主小部件,但在使用状态栏时,扩展首先检查 IStatusBar 是否可用,然后才创建状态栏项。这使得扩展能够在 JupyterLab 和 Jupyter Notebook 7 中成功运行。

// Create a ShoutWidget and add it to the interface in the right sidebar
const shoutWidget: ShoutWidget = new ShoutWidget();
shoutWidget.id = 'JupyterShoutWidget'; // Widgets need an id

app.shell.add(shoutWidget, 'right');

// Check if the status bar is available, and if so, make
// a status bar widget to hold some information
if (statusBar) {
  const statusBarWidget = new ShoutStatusBarSummary();

  statusBar.registerStatusItem('shoutStatusBarSummary', {
    item: statusBarWidget
  });

  // Connect to the messageShouted to be notified when a new message
  // is published and react to it by updating the status bar widget.
  shoutWidget.messageShouted.connect((widget: ShoutWidget, time: Date) => {
    statusBarWidget.setSummary(
      'Last Shout: ' + widget.lastShoutTime?.toString() ?? '(None)'
    );
  });
}

使用必需功能切换行为#

您可以遵循的另一种模式是从您的扩展程序中导出插件列表,然后使用不同的“requires”功能根据扩展程序当前运行的应用程序选择不同的行为。

以下是从 此示例扩展程序 中摘取的一段代码,它在 JupyterLab 的顶部区域或 Jupyter Notebook 7 的右侧边栏中添加了一个鼓掌按钮(您可以在那里阅读完整的扩展程序示例代码)。

/**
 * Data for the @jupyterlab-examples/clap-button JupyterLab plugin.
 */
const pluginJupyterLab: JupyterFrontEndPlugin<void> = {
  id: '@jupyterlab-examples/clap-button:pluginLab',
  description: 'Adds a clap button to the top area JupyterLab',
  autoStart: true,
  requires: [ILabShell],
  activate: (app: JupyterFrontEnd, labShell: ILabShell) => {
    console.log(
      'JupyterLab extension @jupyterlab-examples/clap-button is activated!'
    );

    // Create a ClapWidget and add it to the interface in the top area
    const clapWidget = new ClapWidget();
    clapWidget.id = 'JupyterLabClapWidgetLab';
    app.shell.add(clapWidget, 'top');
  }
};

/**
 * Data for the @jupyterlab-examples/clap-button Jupyter Notebook plugin.
 */
const pluginJupyterNotebook: JupyterFrontEndPlugin<void> = {
  id: '@jupyterlab-examples/clap-button:pluginNotebook',
  description: 'Adds a clap button to the right sidebar of Jupyter Notebook 7',
  autoStart: true,
  requires: [INotebookShell],
  activate: (app: JupyterFrontEnd, notebookShell: INotebookShell) => {
    console.log(
      'Jupyter Notebook extension @jupyterlab-examples/clap-button is activated!'
    );

    // Create a ClapWidget and add it to the interface in the right area
    const clapWidget = new ClapWidget();
    clapWidget.id = 'JupyterNotebookClapWidgetNotebook';
    app.shell.add(clapWidget, 'right');
  }
};

/**
 * Gather all plugins defined by this extension
 */
const plugins: JupyterFrontEndPlugin<void>[] = [
  pluginJupyterLab,
  pluginJupyterNotebook
];

export default plugins;

如上所示,此扩展程序在一个列表中导出多个插件,每个插件使用不同的requires功能在不同的行为(在本例中,不同的布局区域)之间切换,具体取决于它被加载到的应用程序。第一个插件需要 ILabShell(在 JupyterLab 中可用),第二个插件需要 INotebookShell(在 Jupyter Notebook 7 中可用)。

这种方法(在插件加载时测试 shell)不是创建兼容扩展程序的首选方法,因为它粒度较低,通用性较差(因为 shell 通常特定于给定应用程序),并且只提供非常广泛的行为切换,尽管它可以用于在您的扩展程序中创建针对特定应用程序的专用功能。一般来说,您应该优先考虑“测试可选功能”方法,并针对上面提到的“通用扩展点”。

进一步阅读#

有关 JupyterLab 的插件系统和 JupyterLab 的提供者-消费者模式(一种 依赖注入 模式)的说明,请阅读 扩展开发文档