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

时间:2021-06-24 17:23:53

标签: jupyter-notebook jupyter jupyter-lab

我正在 JupyterLab 中创建服务器端扩展,并且一直在寻找一种方法来在我的 index.ts 文件中获取当前活动的笔记本名称。我发现 this existing extension 使用 ILabShell 获取当前活动的标签名称并将浏览器标签设置为具有相同的名称。在我的情况下,我将使用笔记本工具栏上的按钮触发该过程,因此活动选项卡将始终是笔记本。但是,当我尝试使用扩展程序的 activate 部分中的代码时,我得到 TypeError: labShell.currentChanged is undefined,其中 labShellILabShell 的一个实例。该扩展不支持 JupyterLab 3.0+,所以我相信这是问题的一部分。但是,currentChangedILabShell 被明确定义为 here。如果我可以更改任何内容以使其正常工作怎么办?有没有另一种方法来完成我想要做的事情?我知道 this 之类的东西可以在笔记本中获取笔记本名称,但这并不是我想要做的。

Windows 10,
节点 v14.17.0,
npm 6.14.13,
jlpm 1.21.1,
jupyter 实验室 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;

我的 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;

1 个答案:

答案 0 :(得分:2)

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

  1. 您在 activate 中的参数顺序是错误的。参数类型与签名函数没有神奇的匹配;而是按照 requiresoptional 中给出的顺序传递参数。这意味着您将收到:

    ...[JupyterFrontEnd, ICommandPalette, ILabShell, ILauncher]
    

    但您期望的是:

    ...[JupyterFrontEnd, ICommandPalette, ILauncher, ILabShell]
    

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

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

    shell = app.shell as ILabShell
    
  3. (可选改进)安装 RetroLab 作为仅开发要求并使用 import type(这样它不是运行时要求)以确保与 RetroLab 兼容:< /p>

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

    要明确:不这样做不会使您的扩展不兼容;它的作用是确保您不会通过依赖于未来 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
  ) => {
    shell = app.shell as ILabShell | IRetroShell
    shell.currentChanged.connect((_, change) => {
        console.log(change);
        // ...
    });
  }
};

export default extension;