针对多个应用程序#
让你的扩展在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中无缝工作。
一个扩展,它在用户界面的顶部栏添加了一个独立的文本小部件,例如,不需要做任何事情就可以兼容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的Provider-Consumer模式(一种依赖注入模式)的解释,请阅读扩展开发文档。