从零开发无服务函数管理器:jupyter lab 插件

2019-12-19 13:36:57 浏览数 (1)

本文介绍如何制作一个 jupyter lab 的插件。作为例子,我们将制作一个运行在 jupyter 中的 serveless 函数的管理插件。和各种其他无服务函数不同的是:这是一个极其轻量级的 无服务函数 管理插件,不依赖任何其他组件,所有组件都会运行在 jupyter lab 内部。

1. 创建开发环境

1.1 安装 conda/miniconda

1.2 创建开发环境,装各种库

代码语言:txt复制
conda create -n jupyterlab-ext --override-channels --strict-channel-priority -c conda-forge -c anaconda jupyterlab cookiecutter nodejs git
conda activate jupyterlab-ext

2. 创建 repo

代码语言:txt复制
mkdir jupyter-lab-serverless

3. 创建插件项目

3.1 使用 cookiecutter 创建项目模板

代码语言:txt复制
➜ cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts --checkout v1.0
author_name []: u2takey
extension_name [myextension]: jupyter-lab-serverless
project_short_description [A JupyterLab extension.]: Create And Run Serverless Function in JupyterLab
repository [https://github.com/my_name/myextension]: https://github.com/u2takey/jupyter-lab-serverless

可以发现一些模板文件已经创建出来了

代码语言:txt复制
➜ cd jupyter-lab-serverless
➜ ll
.rw-r--r--  475 leiwang  6 Dec 19:16 README.md
.rw-r--r-- 1.2k leiwang  6 Dec 19:16 package.json
drwxr-xr-x    - leiwang  6 Dec 19:16 src
drwxr-xr-x    - leiwang  6 Dec 19:16 style
.rw-r--r--  555 leiwang  6 Dec 19:16 tsconfig.json

3.2 直接 Build安装 试一下

代码语言:txt复制
jlpm install
jupyter labextension install . --no-build

3.3 打开观察 第一次 安装的效果

代码语言:txt复制
jupyter lab --watch

# 打开 浏览器 console,可以看到
> JupyterLab extension jupyter-lab-serverless is activated!

4. 开始制作 severless 插件

这个插件将分为两个部分,一部分是 server 部分,一部分是前端部分. 我们将先创建后端部分。

4.1 server 插件部分

server 插件本质是一个 tornado handler,首先在 init 种实现load

代码语言:txt复制
def load_jupyter_server_extension(nb_server_app):
    初始化

定义 API,我们的函数 Server API主要的作用是完成 无服务函数的 增删查改,以及触发.

为了让实现更简单,我们用 put/delete 带函数名实现增删改,post/get 带函数名用于实现触发,而get不带函数名作为 查的实现,返回所有函数。同时为了 重启后函数能得到保存,我们使用 sqite作为本地保存(jupyter lab serverside的 state保存可能有更好的办法)。

代码语言:txt复制
class FunctionHandler(APIHandler):
    """
    A handler that manage serverless functions.
    """

    def initialize(self, app):
        self.logger = app.log
        self.db = app.db

    @web.authenticated
    @gen.coroutine
    def get(self, function=''):
        if function:
            # 触发
        else:
            # List
        
        
    def trigger_function(self, function, method, query, body):
        #  触发实现

    @gen.coroutine
    def post(self, function=''):
        #  触法

    @web.authenticated
    @gen.coroutine
    def put(self, function=''):
        #  增

    @web.authenticated
    @gen.coroutine
    def delete(self, function=''):
        #  删

函数的执行:

代码语言:txt复制
class Function(Base):
    __tablename__ = 'functions'

    # 各种字段略
    
    def __call__(self, *args, **kwargs):
        import imp
        module = imp.new_module(self.name)
        exec(self.script, module.__dict__)
        module.handle.logger = self.logger
        return module.handle(*args, **kwargs)

4.2 前端插件部分

在 index.js 中实现一个 xx Plugin 的继承

增加前端依赖的办法:

代码语言:txt复制
jlpm add @jupyterlab/apputils
jlpm add @jupyterlab/application
jlpm run build
代码语言:txt复制
/**
 * Initialization data for the jupyter-lab-serverless extension.
 */
const extension: JupyterFrontEndPlugin<void> = {
  id: 'jupyter-lab-serverless',
  requires: [IStateDB],
  autoStart: true,
  activate: activate
};

设计上,我们并没有使用 jupyterlab 插件中常用的 platte,而是增加 toolbar 上的两个 button。其中一个按钮设计为增加函数,另一个函数用于管理包括删除函数。

代码语言:txt复制
/**
 * Save Current Script as A Serverless Function
 */
export
class ButtonExtensionAdd implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {

  createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
    let callback = () => {
      // 保存函数
    };
    let button = new ToolbarButton({
      className: 'serverless-add',
      iconClassName: 'fa fa-desktop',
      onClick: callback,
      tooltip: 'Save as Serverless Functions'
    });

    panel.toolbar.addItem('severless-add', button);
    return new DisposableDelegate(() => {
      button.dispose();
    });
  }
}

/**
 * Manager Serverless Function
 */
export
class ButtonExtensionManager implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
  delete(name: string){
    // 删除函数
  }
  createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
    let callback = () => {
      // 发送请求 获取函数
    };
    let button = new ToolbarButton({
      className: 'serverless-manager',
      iconClassName: 'fa fa-tasks',
      onClick: callback,
      tooltip: 'Show Serverless Functions'
    });

    panel.toolbar.addItem('severless-manager', button);
    return new DisposableDelegate(() => {
      button.dispose();
    });
  }
}

在回调函数中,我们实现 request 发送到刚刚实现的 server 插件,获取数据。

4.3 打包,发布插件

代码语言:txt复制
python3 setup.py sdist

// 发布后端插件
twine upload --skip-existing -u xx -p yy  dist/*

// 发布前端插件
npm publish --access=public

5. 使用演示

5.1. 启动

从镜像启动,镜像中到 jupyter 已经安装了 serveless 插件

代码语言:txt复制
docker run --rm -p 8888:8888 ccr.ccs.tencentyun.com/leiwang/jupterlab:serverless  /bin/bash  -c 'jupyter lab --ip=* --port=8888 --no-browser --allow-root'

5.2. 创建一个函数

函数需要有一个命名为 'handle' 的函数.

函数有个一个 logger,可以用于debug,logger输出内容将输出到 jupyter 后端。

代码语言:txt复制
def handle(event):
    logger = handle.logger
    logger.info(event)
    return event

点击保存按钮, 保存函数

image.pngimage.png

5.3. 本地测试

本地测试有两种方式

一: 直接调用 handle 函数

二: 打开另一个 notebook,模拟 request 触发函数,检查效果

代码语言:txt复制
handle({})

注意 调用时需要带上notebook的 Authorization,这个在 jupyter notebook 启动时可以查看到。

代码语言:txt复制
import requests
headers = {'Authorization': 'token 4b917c156ea968fdafb81308324b06c5a9154596ebfcfd67'}
data = {"test": "testdata"}
r = requests.post('http://127.0.0.1:8888/function/test1.ipynb', json=data, headers=headers)

r.text
'{"code": "success", "data": {"method": "POST", "query": {}, "body": {"test": "testdata"}}}'

5.4. 管理函数

可以看到已经调用了一次

image.pngimage.png

点击删除按钮,可以把 函数删除。

5.5. 定期执行函数

函数支持定期执行,schedule采用类似 https://schedule.readthedocs.io/en/stable/ 的语法表达方式

schedule 支持 'every'(默认1), 'unit'(默认为day), 'at' (默认为None) 三个参数

代码语言:txt复制
def handle(event):
    logger = handle.__dict__.get('logger')
    if logger:
        logger.info(event)
    return event

handle.schedule={'unit':'seconds'}

保存函数,观察后端日志,可以发现每秒被执行一次

image.pngimage.png

5.6. 其他应用

可以用于接收 weekhook 做 ci/cd 触发,聊天机器人,定期执行脚本等等。

本文完整的代码在 https://github.com/u2takey/jupyter-lab-serverless

参考

  • https://blog.jupyter.org/99-ways-to-extend-the-jupyter-ecosystem-11e5dab7c54
  • https://jupyterlab.readthedocs.io/en/stable/developer/notebook.html#extend-notebook-plugin
  • https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Distributing Jupyter Extensions as Python Packages.html
  • https://jupyter-notebook.readthedocs.io/en/stable/extending/handlers.html
  • https://github.com/jupyterlab/jupyterlab-latex/blob/master/docs/advanced.md
  • https://github.com/matplotlib/jupyter-matplotlib
  • https://github.com/mauhai/awesome-jupyterlab

0 人点赞