面向多个应用程序#
让你的扩展在 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 的提供者-消费者模式(一种依赖注入模式),它正是实现此处讨论的兼容性解决方案的原因。
每个插件都使用一些属性(requires
和optional
属性)来请求它想要的功能,这些功能由已加载到 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 提供者-消费者模式(一种依赖注入模式)的解释,请阅读扩展开发文档。