如何获取 JupyterLab 中当前活动的笔记本名称?

Tre*_*ock 5 jupyter jupyter-notebook jupyter-lab

我正在 JupyterLab 中创建服务器端扩展,并且一直在寻找一种方法来获取 index.ts 文件中当前活动的笔记本名称。我发现这个现有的扩展可以获取当前活动的选项卡名称,ILabShell并将浏览器选项卡设置为具有相同的名称。就我而言,我将使用笔记本工具栏上的按钮触发该过程,因此活动选项卡将始终是笔记本。activate但是,当我尝试使用扩展部分中的代码时,我得到了TypeError: labShell.currentChanged is undefinedwherelabShell是 的实例ILabShell。该扩展不支持 JupyterLab 3.0+,所以我认为这是问题的一部分。然而,currentChangedfor在这里ILabShell被明确定义。如果我可以改变什么以使其发挥作用怎么办?还有其他方法可以完成我想做的事情吗?我知道类似这样的事情可以在笔记本中获取笔记本名称,但这并不是我想要做的。

Windows 10、
节点 v14.17.0、
npm 6.14.13、
jlpm 1.21.1、
jupyter lab 3.0.14

我使用此示例服务器扩展作为模板:https ://github.com/jupyterlab/extension-examples/tree/master/server-extension

从现有扩展的 index.ts 文件获取当前选项卡名称:

import {
  ILabShell,
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';

import { Title, Widget } from '@lumino/widgets';

/**
 * Initialization data for the jupyterlab-active-as-tab-name extension.
 */
const extension: JupyterFrontEndPlugin<void> = {
  id: 'jupyterlab-active-as-tab-name',
  autoStart: true,
  requires: [ILabShell],
  activate: (app: JupyterFrontEnd, labShell: ILabShell) => {
    const onTitleChanged = (title: Title<Widget>) => {
      console.log('the JupyterLab main application:', title);
      document.title = title.label;
    };

    // Keep the session object on the status item up-to-date.
    labShell.currentChanged.connect((_, change) => {
      const { oldValue, newValue } = change;

      // Clean up after the old value if it exists,
      // listen for changes to the title of the activity
      if (oldValue) {
        oldValue.title.changed.disconnect(onTitleChanged);
      }
      if (newValue) {
        newValue.title.changed.connect(onTitleChanged);
      }
    });
  }
};

export default extension;
Run Code Online (Sandbox Code Playgroud)

我的index.ts文件:

import {
  ILabShell,
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';

import { ICommandPalette } from '@jupyterlab/apputils';

import { ILauncher } from '@jupyterlab/launcher';

import { requestAPI } from './handler';

import { ToolbarButton } from '@jupyterlab/apputils';

import { DocumentRegistry } from '@jupyterlab/docregistry';

import { INotebookModel, NotebookPanel } from '@jupyterlab/notebook';

import { IDisposable } from '@lumino/disposable';

import { Title, Widget } from '@lumino/widgets';

export class ButtonExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
    
  constructor(app: JupyterFrontEnd) { 
      this.app = app;
  }
  
  readonly app: JupyterFrontEnd
    
  createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {

    // dummy json data to test post requests
    const data2 = {"test message" : "message"}
    const options = {
      method: 'POST',
      body: JSON.stringify(data2),
      headers: {'Content-Type': 'application/json'
      }
    };
 
    // Create the toolbar button
    let mybutton = new ToolbarButton({ 
      label: 'Measure Energy Usage',
      onClick: async () => {

        // POST request to Jupyter server
        const dataToSend = { file: 'nbtest.ipynb' };
        try {
          const reply = await requestAPI<any>('hello', {
            body: JSON.stringify(dataToSend),
            method: 'POST'
          });
          console.log(reply);
        } catch (reason) {
          console.error(
            `Error on POST /jlab-ext-example/hello ${dataToSend}.\n${reason}`
          );
        }
        
        // sample POST request to svr.js
        fetch('http://localhost:9898/api', options);
      }
    });
    // Add the toolbar button to the notebook toolbar
    panel.toolbar.insertItem(10, 'MeasureEnergyUsage', mybutton);
    console.log("MeasEnerUsage activated");

    // The ToolbarButton class implements `IDisposable`, so the
    // button *is* the extension for the purposes of this method.
    return mybutton;
  }
}

/**
 * Initialization data for the server-extension-example extension.
 */
const extension: JupyterFrontEndPlugin<void> = {
  id: 'server-extension-example',
  autoStart: true,
  optional: [ILauncher],
  requires: [ICommandPalette, ILabShell],
  activate: async (
    app: JupyterFrontEnd,
    palette: ICommandPalette,
    launcher: ILauncher | null,
    labShell: ILabShell
  ) => {
    console.log('JupyterLab extension server-extension-example is activated!');
    const your_button = new ButtonExtension(app);
    app.docRegistry.addWidgetExtension('Notebook', your_button);
    
    // sample GET request to jupyter server
    try {
      const data = await requestAPI<any>('hello');
      console.log(data);
    } catch (reason) {
      console.error(`Error on GET /jlab-ext-example/hello.\n${reason}`);
    }

    // get name of active tab

    const onTitleChanged = (title: Title<Widget>) => {
      console.log('the JupyterLab main application:', title);
      document.title = title.label;
    };
    
    // Keep the session object on the status item up-to-date.
    labShell.currentChanged.connect((_, change) => {
      const { oldValue, newValue } = change;

      // Clean up after the old value if it exists,
      // listen for changes to the title of the activity
      if (oldValue) {
        oldValue.title.changed.disconnect(onTitleChanged);
      }
      if (newValue) {
        newValue.title.changed.connect(onTitleChanged);
      }
    });
  }
};

export default extension;

Run Code Online (Sandbox Code Playgroud)

kra*_*ski 3

有两种方法可以修复它,一种方法可以改进它。我建议使用(2)和(3)。

  1. 你的参数顺序activate是错误的。参数类型与签名函数没有神奇的匹配;requires相反,参数按照and then中给出的顺序传递optional。这意味着您将收到:

    ...[JupyterFrontEnd, ICommandPalette, ILabShell, ILauncher]
    
    Run Code Online (Sandbox Code Playgroud)

    但你所期待的是:

    ...[JupyterFrontEnd, ICommandPalette, ILauncher, ILabShell]
    
    Run Code Online (Sandbox Code Playgroud)

    换句话说,选项总是在最后。没有静态类型检查,因此这是常见的错误来源 - 只需确保下次仔细检查顺序(或调试/console.log看看你得到什么)。

  2. 实际上,不需要ILabShell作为令牌。使用ILabShell进来的app.shell代替。这样,您的扩展也将与使用 JupyterLab 组件构建的其他前端兼容。

    shell = app.shell as ILabShell
    
    Run Code Online (Sandbox Code Playgroud)
  3. (可选改进)将RetroLab安装为仅开发要求并使用import type(这样它不是运行时要求)以确保与以下内容的兼容性RetroLab

    import type { IRetroShell } from '@retrolab/application';
    // ... and then in `activate()`:
    shell = app.shell as ILabShell | IRetroShell
    
    Run Code Online (Sandbox Code Playgroud)

    需要明确的是:不这样做不会使您的扩展不兼容;ILabShell它的作用是确保您不会因依赖未来实验室特定的行为而使其不兼容。

所以总的来说它看起来像:

import {
  ILabShell,
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';
import type { IRetroShell } from '@retrolab/application';

// ...

const extension: JupyterFrontEndPlugin<void> = {
  id: 'server-extension-example',
  autoStart: true,
  optional: [ILauncher],
  requires: [ICommandPalette],
  activate: async (
    app: JupyterFrontEnd,
    palette: ICommandPalette,
    launcher: ILauncher | null
  ) => {
    let shell = app.shell as ILabShell | IRetroShell ;
    shell.currentChanged.connect((_, change) => {
        console.log(change);
        // ...
    });
  }
};

export default extension;
Run Code Online (Sandbox Code Playgroud)