Jupyter server provider sample

This commit is contained in:
Don Jayamanne
2023-09-21 12:21:12 +10:00
parent 69333818a4
commit f047b9f481
12 changed files with 2208 additions and 0 deletions

View File

@ -0,0 +1,23 @@
/**@type {import('eslint').Linter.Config} */
// eslint-disable-next-line no-undef
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'semi': [
2,
"always"
],
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-non-null-assertion': 0,
}
};

View File

@ -0,0 +1,6 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-toolsai.jupyter"
]
}

View File

@ -0,0 +1,22 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"trace": true,
// "preLaunchTask": "npm: watch"
}
]
}

View File

@ -0,0 +1,6 @@
{
"search.exclude": {
"out": true
},
"typescript.tsc.autoDetect": "off"
}

View File

@ -0,0 +1,13 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "compile",
"problemMatcher": ["$tsc"],
"group": "build"
}
]
}

View File

@ -0,0 +1,12 @@
.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts
**/*.tsbuildinfo

View File

@ -0,0 +1,16 @@
# jupyter-servedr-provider-sample
This is a very simple extension sample demonstrating the use of the Jupyter Extension API allowing other extensions to contribute [Jupyter](https://jupyter.org/) Servers via the Kernel Picker for Jupyter notebooks:
- The sample extension finds Jupyter Servers running locally on the current machine
- This list of servers is then provided to the Jupyter extension via the Jupyter Extension API
- Upon opening a notebook and selecting a kernel the option `Local JupyterLab Servers...` will display the above servers.
- From there, the user can select a kernel and run code in the notebook against one of the local kernels.
## Running this sample
1. `cd jupyter-server-provider-sample`
1. `code .`: Open the folder in VS Code
1. Hit `F5` to build+debug
1. Select the kernel Picker for a notebook and select the option `Local JupyterLab Servers...`
1. To see servers show up in this list, start Jupyter Lab or Jupyter Notebook outside VS Code or from within the integrated terminal (e.g. [jupyter lab](https://jupyterlab.readthedocs.io/en/stable/getting_started/starting.html))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
{
"name": "jupyter-server-provider-sample",
"displayName": "notebook-serializer-sample",
"description": "Jupyter Notebook Kernel Picker with custom servers using the Jupyter API sample",
"publisher": "vscode-samples",
"version": "0.0.1",
"engines": {
"vscode": "^1.82.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onNotebook:jupyter-notebook",
"onNotebook:interactive"
],
"extensionDependencies": [
"ms-toolsai.jupyter"
],
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -b",
"lint": "eslint src --ext ts",
"watch": "tsc -b --watch"
},
"devDependencies": {
"@types/node": "14.x",
"@types/vscode": "^1.82.0",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"@vscode/jupyter-extension": "^0.1.0",
"eslint": "^7.27.0",
"typescript": "^5.2.2"
}
}

View File

@ -0,0 +1,128 @@
import {
CancellationToken,
ExtensionContext,
ProgressLocation,
Uri,
extensions,
window,
} from 'vscode';
import { Jupyter } from '@vscode/jupyter-extension';
import { findLocallyRunningServers } from './jupyter';
export function activate(context: ExtensionContext) {
const jupyterExt = extensions.getExtension<Jupyter>('ms-toolsai.jupyter');
if (!jupyterExt) {
throw new Error('Jupyter Extension not installed');
}
if (!jupyterExt.isActive) {
jupyterExt.activate();
}
const jupyterLab = jupyterExt.exports.createJupyterServerCollection(
`${context.extension.id}:lab`,
'Local JupyterLab Servers...',
{
provideJupyterServers: () => provideJupyterServers('lab'),
resolveJupyterServer: (server) => server,
}
);
context.subscriptions.push(jupyterLab);
// // Commands are optional.
jupyterLab.commandProvider = {
provideCommands: () => {
return [
{
label: 'Start Jupyter Lab',
description: 'Start a new server in the terminal',
},
];
},
handleCommand: async (command, token) => {
if (command.label === 'Start Jupyter Lab') {
return startJupyterInTerminal('lab', token);
}
},
};
// Each collection is treated as a logical collection of Jupyter Servers.
// Thus an extension can contribute more than one collection of servers.
const jupyterNotebook = jupyterExt.exports.createJupyterServerCollection(
`${context.extension.id}:notebook`,
'Local Jupyter Notebook Servers...',
{
provideJupyterServers: () => provideJupyterServers('notebook'),
resolveJupyterServer: (server) => server,
}
);
context.subscriptions.push(jupyterNotebook);
// Commands are optional.
jupyterNotebook.commandProvider = {
provideCommands: () => {
return [
{
label: 'Start Jupyter Notebook',
description: 'Start a new server in the terminal',
},
];
},
handleCommand: async (command, token) => {
if (command.label === 'Start Jupyter Notebook') {
return startJupyterInTerminal('notebook', token);
}
},
};
}
async function provideJupyterServers(type: 'lab' | 'notebook') {
const servers = await findLocallyRunningServers(type);
return servers.map((server) => {
return {
id: `${server.pid}:${server.port}`,
label: server.url,
connectionInformation: {
baseUrl: Uri.parse(server.url),
token: server.token,
},
};
});
}
async function startJupyterInTerminal(type: 'lab' | 'notebook', token: CancellationToken) {
return await window.withProgress(
{
location: ProgressLocation.Notification,
title: 'Starting Jupyter Lab',
},
async () => {
const servers = await findLocallyRunningServers(type);
const existingPids = new Set(servers.map((s) => s.pid));
const terminal = window.createTerminal(
type === 'lab' ? 'Jupyter Lab' : 'Jupyter Notebook'
);
terminal.show();
terminal.sendText(`jupyter ${type}`);
// Wait for 5 seconds, then give up, this is only a sample.
await new Promise((resolve) => setTimeout(resolve, 5_000));
const newServers = (await findLocallyRunningServers(type)).filter(
(s) => !existingPids.has(s.pid)
);
if (token.isCancellationRequested) {
return;
}
if (newServers.length) {
const server = newServers[0];
return {
id: `${server.pid}:${server.port}`,
label: new URL(server.url).hostname,
connectionInformation: {
baseUrl: Uri.parse(server.url),
token: server.token,
},
};
} else {
window.showInformationMessage(
'Timeout waiting for Jupyter to start in the terminal'
);
}
}
);
}

View File

@ -0,0 +1,108 @@
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import { promisify } from 'util';
const homeDir = getUserHomeDir();
export async function findLocallyRunningServers(type: 'lab' | 'notebook') {
const runtimeDir = path.join(getDataDirectory(), 'runtime');
const files = await promisify(fs.readdir)(runtimeDir);
const servers: {
url: string;
token: string;
root_dir: string;
pid: number;
port: number;
}[] = [];
const prefix = type === 'lab' ? 'jpserver-' : 'nbserver';
await Promise.all(
files.map(async (file) => {
if (!file.startsWith(prefix) || !file.endsWith('.json')) {
return;
}
const contents = await promisify(fs.readFile)(path.join(runtimeDir, file)).then(
(c) => c.toString()
);
const json: {
url: string;
token: string;
root_dir: string;
pid: number;
port: number;
} = JSON.parse(contents);
if (!json.token) {
console.warn(`Ignoring url ${json.url} as it does not contain a token`);
return;
}
try {
process.kill(json.pid, 0);
servers.push(json);
} catch {
//
}
})
);
return servers;
}
function getDataDirectory() {
if (process.env['JUPYTER_DATA_DIR']) {
return path.normalize(process.env['JUPYTER_DATA_DIR']);
}
switch (getOSType()) {
case 'osx':
return path.join(homeDir, 'Library', 'Jupyter');
case 'windows': {
const appData = process.env['APPDATA']
? path.normalize(process.env['APPDATA'])
: '';
if (appData) {
return path.join(appData, 'jupyter');
}
const configDir = getJupyterConfigDir();
if (configDir) {
return path.join(configDir, 'data');
}
return path.join(homeDir, 'Library', 'Jupyter');
}
default: {
// Linux, non-OS X Unix, AIX, etc.
const xdgDataHome = process.env['XDG_DATA_HOME']
? path.normalize(process.env['XDG_DATA_HOME'])
: path.join(homeDir, '.local', 'share');
return path.join(xdgDataHome, 'jupyter');
}
}
}
function getJupyterConfigDir() {
if (process.env['JUPYTER_CONFIG_DIR']) {
return path.normalize(process.env['JUPYTER_CONFIG_DIR']);
}
return path.join(homeDir, '.jupyter');
}
function getOSType() {
const platform = process.platform;
if (/^win/.test(platform)) {
return 'windows';
} else if (/^darwin/.test(platform)) {
return 'osx';
} else {
return 'linux';
}
}
function getUserHomeDir() {
const homePath = os.homedir();
if (getOSType() === 'windows') {
return process.env['USERPROFILE'] || homePath;
}
const homeVar = process.env['HOME'] || process.env['HOMEPATH'] || homePath;
// Make sure if linux, it uses linux separators
return homeVar.replace(/\\/g, '/');
}

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"lib": [
"es2020"
],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "src"
},
"exclude": [
"node_modules",
".vscode-test"
]
}