【问题标题】:How do you get the currently active notebook name in JupyterLab?您如何在 JupyterLab 中获取当前活动的笔记本名称?
【发布时间】:2021-06-24 17:23:53
【问题描述】:

我正在 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;

【问题讨论】:

    标签: jupyter-notebook jupyter jupyter-lab


    【解决方案1】:

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

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

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

      但你所期待的是:

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

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

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

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

      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
      ) => {
        let shell = app.shell as ILabShell | IRetroShell ;
        shell.currentChanged.connect((_, change) => {
            console.log(change);
            // ...
        });
      }
    };
    
    export default extension;
    

    【讨论】:

    • #2 和 #3 完美地解决了问题,非常感谢!
    猜你喜欢
    • 2021-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    • 2013-06-25
    相关资源
    最近更新 更多