本文介绍如何制作一个 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
点击保存按钮, 保存函数
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. 管理函数
可以看到已经调用了一次
点击删除按钮,可以把 函数删除。
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'}
保存函数,观察后端日志,可以发现每秒被执行一次
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