jupyterpack 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/document/widgetFactory.js +3 -1
- package/lib/pythonServer/baseServer.d.ts +82 -0
- package/lib/pythonServer/baseServer.js +141 -0
- package/lib/pythonServer/dash/dashServer.d.ts +2 -12
- package/lib/pythonServer/dash/dashServer.js +8 -21
- package/lib/pythonServer/index.d.ts +3 -3
- package/lib/pythonServer/index.js +5 -1
- package/lib/pythonServer/kernelExecutor.d.ts +2 -58
- package/lib/pythonServer/kernelExecutor.js +0 -72
- package/lib/pythonServer/shiny/shinyServer.d.ts +14 -0
- package/lib/pythonServer/shiny/shinyServer.js +49 -0
- package/lib/pythonServer/starlette/starletteServer.d.ts +13 -0
- package/lib/pythonServer/starlette/starletteServer.js +49 -0
- package/lib/pythonServer/streamlit/streamlitServer.d.ts +0 -1
- package/lib/pythonServer/streamlit/streamlitServer.js +7 -11
- package/lib/pythonServer/tornado/tornadoServer.d.ts +2 -24
- package/lib/pythonServer/tornado/tornadoServer.js +10 -35
- package/lib/swConnection/mainConnectionManager.d.ts +4 -4
- package/lib/swConnection/mainConnectionManager.js +23 -12
- package/lib/tools.d.ts +1 -0
- package/lib/tools.js +8 -0
- package/lib/type.d.ts +12 -3
- package/lib/type.js +2 -0
- package/lib/websocket/websocket.js +5 -1
- package/package.json +1 -1
- package/src/document/widgetFactory.ts +3 -1
- package/src/pythonServer/baseServer.ts +245 -0
- package/src/pythonServer/dash/dashServer.ts +10 -29
- package/src/pythonServer/index.ts +9 -5
- package/src/pythonServer/kernelExecutor.ts +3 -156
- package/src/pythonServer/shiny/shinyServer.ts +62 -0
- package/src/pythonServer/starlette/starletteServer.ts +59 -0
- package/src/pythonServer/streamlit/streamlitServer.ts +7 -9
- package/src/pythonServer/tornado/tornadoServer.ts +11 -55
- package/src/pythonWidget/pythonWidgetModel.ts +2 -2
- package/src/swConnection/mainConnectionManager.ts +28 -20
- package/src/tools.ts +9 -0
- package/src/type.ts +17 -6
- package/src/websocket/websocket.ts +9 -1
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { JupyterPackFramework } from '../../type';
|
|
2
2
|
import { TornadoServer } from '../tornado/tornadoServer';
|
|
3
3
|
export class StreamlitServer extends TornadoServer {
|
|
4
|
-
constructor() {
|
|
5
|
-
super(...arguments);
|
|
6
|
-
this._SERVER_VAR = '__jupyterpack_streamlit_server';
|
|
7
|
-
}
|
|
8
4
|
async init(options) {
|
|
9
5
|
const { instanceId, kernelClientId, entryPath } = options;
|
|
10
6
|
if (!entryPath) {
|
|
@@ -21,18 +17,18 @@ export class StreamlitServer extends TornadoServer {
|
|
|
21
17
|
patch_tornado()
|
|
22
18
|
set_base_url_env("${baseURL}")
|
|
23
19
|
`;
|
|
24
|
-
await this.executeCode({ code: patchCode });
|
|
20
|
+
await this.kernelExecutor.executeCode({ code: patchCode });
|
|
25
21
|
const bootstrapCode = `
|
|
26
22
|
from jupyterpack.streamlit import patch_streamlit
|
|
27
23
|
patch_streamlit()
|
|
28
24
|
`;
|
|
29
|
-
await this.executeCode({ code: bootstrapCode });
|
|
25
|
+
await this.kernelExecutor.executeCode({ code: bootstrapCode });
|
|
30
26
|
const stCode = `
|
|
31
27
|
from jupyterpack.streamlit import StreamlitServer, create_streamlit_app
|
|
32
28
|
__jupyterpack_st_server, __jupyterpack_tor_app = await create_streamlit_app("${entryPath}", "${baseURL}")
|
|
33
|
-
${this.
|
|
29
|
+
${this._server_var} = StreamlitServer(__jupyterpack_tor_app, "${baseURL}", __jupyterpack_st_server)
|
|
34
30
|
`;
|
|
35
|
-
await this.executeCode({ code: stCode });
|
|
31
|
+
await this.kernelExecutor.executeCode({ code: stCode });
|
|
36
32
|
}
|
|
37
33
|
async reloadPythonServer(options) {
|
|
38
34
|
const { entryPath } = options;
|
|
@@ -40,11 +36,11 @@ export class StreamlitServer extends TornadoServer {
|
|
|
40
36
|
return;
|
|
41
37
|
}
|
|
42
38
|
const reloadCode = `
|
|
43
|
-
${this.
|
|
39
|
+
${this._server_var}.dispose()
|
|
44
40
|
__jupyterpack_st_server, __jupyterpack_tor_app = await create_streamlit_app("${entryPath}", "${this._baseUrl}")
|
|
45
|
-
${this.
|
|
41
|
+
${this._server_var}.reload(__jupyterpack_tor_app, __jupyterpack_st_server)
|
|
46
42
|
`;
|
|
47
|
-
await this.executeCode({
|
|
43
|
+
await this.kernelExecutor.executeCode({
|
|
48
44
|
code: reloadCode
|
|
49
45
|
}, true);
|
|
50
46
|
}
|
|
@@ -1,34 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export declare class TornadoServer extends KernelExecutor {
|
|
1
|
+
import { BasePythonServer } from '../baseServer';
|
|
2
|
+
export declare class TornadoServer extends BasePythonServer {
|
|
4
3
|
init(options: {
|
|
5
4
|
initCode?: string;
|
|
6
5
|
instanceId: string;
|
|
7
6
|
kernelClientId: string;
|
|
8
7
|
}): Promise<void>;
|
|
9
|
-
getResponseFunctionFactory(options: {
|
|
10
|
-
urlPath: string;
|
|
11
|
-
method: string;
|
|
12
|
-
headers: IDict;
|
|
13
|
-
params?: string;
|
|
14
|
-
content?: string;
|
|
15
|
-
}): string;
|
|
16
|
-
openWebsocketFunctionFactory(options: {
|
|
17
|
-
instanceId: string;
|
|
18
|
-
kernelId: string;
|
|
19
|
-
wsUrl: string;
|
|
20
|
-
protocol?: string;
|
|
21
|
-
}): string;
|
|
22
|
-
sendWebsocketMessageFunctionFactory(options: {
|
|
23
|
-
instanceId: string;
|
|
24
|
-
kernelId: string;
|
|
25
|
-
wsUrl: string;
|
|
26
|
-
message: string;
|
|
27
|
-
}): string;
|
|
28
|
-
disposePythonServer(): Promise<void>;
|
|
29
8
|
reloadPythonServer(options: {
|
|
30
9
|
entryPath?: string;
|
|
31
10
|
initCode?: string;
|
|
32
11
|
}): Promise<void>;
|
|
33
|
-
protected _SERVER_VAR: string;
|
|
34
12
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { stringOrNone } from '../../tools';
|
|
2
1
|
import { JupyterPackFramework } from '../../type';
|
|
3
|
-
import {
|
|
4
|
-
export class TornadoServer extends
|
|
5
|
-
constructor() {
|
|
6
|
-
super(...arguments);
|
|
7
|
-
this._SERVER_VAR = '__jupyterpack_tornado_server';
|
|
8
|
-
}
|
|
2
|
+
import { BasePythonServer } from '../baseServer';
|
|
3
|
+
export class TornadoServer extends BasePythonServer {
|
|
9
4
|
async init(options) {
|
|
10
5
|
await super.init(options);
|
|
11
6
|
const { initCode, instanceId, kernelClientId } = options;
|
|
@@ -20,49 +15,29 @@ export class TornadoServer extends KernelExecutor {
|
|
|
20
15
|
patch_tornado()
|
|
21
16
|
|
|
22
17
|
`;
|
|
23
|
-
await this.executeCode({ code: bootstrapCode });
|
|
18
|
+
await this.kernelExecutor.executeCode({ code: bootstrapCode });
|
|
24
19
|
if (initCode) {
|
|
25
20
|
const initCodeWithUrl = initCode.replaceAll('{{base_url}}', baseURL);
|
|
26
|
-
await this.executeCode({ code: initCodeWithUrl });
|
|
21
|
+
await this.kernelExecutor.executeCode({ code: initCodeWithUrl });
|
|
27
22
|
const loaderCode = `
|
|
28
23
|
from jupyterpack.tornado import TornadoServer
|
|
29
|
-
${this.
|
|
24
|
+
${this._server_var} = TornadoServer(app, "${baseURL}")
|
|
30
25
|
`;
|
|
31
|
-
await this.executeCode({ code: loaderCode });
|
|
26
|
+
await this.kernelExecutor.executeCode({ code: loaderCode });
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
|
-
getResponseFunctionFactory(options) {
|
|
35
|
-
const { method, urlPath, headers, params, content } = options;
|
|
36
|
-
const code = `await ${this._SERVER_VAR}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
|
|
37
|
-
return code;
|
|
38
|
-
}
|
|
39
|
-
openWebsocketFunctionFactory(options) {
|
|
40
|
-
const { instanceId, kernelId, wsUrl, protocol } = options;
|
|
41
|
-
const code = `await ${this._SERVER_VAR}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
|
|
42
|
-
return code;
|
|
43
|
-
}
|
|
44
|
-
sendWebsocketMessageFunctionFactory(options) {
|
|
45
|
-
const { instanceId, kernelId, wsUrl, message } = options;
|
|
46
|
-
const code = `await ${this._SERVER_VAR}.receive_ws_message("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
|
|
47
|
-
return code;
|
|
48
|
-
}
|
|
49
|
-
async disposePythonServer() {
|
|
50
|
-
await this.executeCode({
|
|
51
|
-
code: `${this._SERVER_VAR}.dispose()`
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
29
|
async reloadPythonServer(options) {
|
|
55
30
|
var _a;
|
|
56
31
|
const { initCode } = options;
|
|
57
32
|
if (initCode) {
|
|
58
|
-
await this.executeCode({
|
|
33
|
+
await this.kernelExecutor.executeCode({
|
|
59
34
|
code: initCode.replaceAll('{{base_url}}', (_a = this._baseUrl) !== null && _a !== void 0 ? _a : '')
|
|
60
35
|
});
|
|
61
36
|
const reloadCode = `
|
|
62
|
-
${this.
|
|
63
|
-
${this.
|
|
37
|
+
${this._server_var}.dispose()
|
|
38
|
+
${this._server_var}.reload(app)
|
|
64
39
|
`;
|
|
65
|
-
await this.executeCode({ code: reloadCode }, true);
|
|
40
|
+
await this.kernelExecutor.executeCode({ code: reloadCode }, true);
|
|
66
41
|
}
|
|
67
42
|
}
|
|
68
43
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IConnectionManager, IDict
|
|
1
|
+
import { IBasePythonServer, IConnectionManager, IDict } from '../type';
|
|
2
2
|
/**
|
|
3
3
|
* Manages connections between clients and kernel executors.
|
|
4
4
|
* This class handles the registration of kernel executors and the generation of responses
|
|
@@ -10,7 +10,7 @@ import { IConnectionManager, IDict, IKernelExecutor } from '../type';
|
|
|
10
10
|
export declare class ConnectionManager implements IConnectionManager {
|
|
11
11
|
instanceId: string;
|
|
12
12
|
constructor(instanceId: string);
|
|
13
|
-
registerConnection(
|
|
13
|
+
registerConnection(pythonServer: IBasePythonServer): Promise<{
|
|
14
14
|
instanceId: string;
|
|
15
15
|
kernelClientId: string;
|
|
16
16
|
}>;
|
|
@@ -23,6 +23,6 @@ export declare class ConnectionManager implements IConnectionManager {
|
|
|
23
23
|
params?: string;
|
|
24
24
|
}): Promise<IDict | null>;
|
|
25
25
|
private _initWsChannel;
|
|
26
|
-
private
|
|
27
|
-
private
|
|
26
|
+
private _pythonServers;
|
|
27
|
+
private _wsBroadcastChannelMap;
|
|
28
28
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { arrayBufferToBase64 } from '../tools';
|
|
1
|
+
import { arrayBufferToBase64, stringToBase64 } from '../tools';
|
|
2
2
|
import { UUID } from '@lumino/coreutils';
|
|
3
3
|
/**
|
|
4
4
|
* Manages connections between clients and kernel executors.
|
|
@@ -11,18 +11,20 @@ import { UUID } from '@lumino/coreutils';
|
|
|
11
11
|
export class ConnectionManager {
|
|
12
12
|
constructor(instanceId) {
|
|
13
13
|
this.instanceId = instanceId;
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
this._initWsChannel();
|
|
14
|
+
this._pythonServers = new Map();
|
|
15
|
+
this._wsBroadcastChannelMap = new Map();
|
|
17
16
|
}
|
|
18
|
-
async registerConnection(
|
|
17
|
+
async registerConnection(pythonServer) {
|
|
19
18
|
const uuid = UUID.uuid4();
|
|
20
|
-
this.
|
|
19
|
+
this._pythonServers.set(uuid, pythonServer);
|
|
20
|
+
const wsbc = new BroadcastChannel(`/jupyterpack/ws/${this.instanceId}/${uuid}`);
|
|
21
|
+
this._initWsChannel(wsbc);
|
|
22
|
+
this._wsBroadcastChannelMap.set(`${this.instanceId}/${uuid}`, wsbc);
|
|
21
23
|
return { instanceId: this.instanceId, kernelClientId: uuid };
|
|
22
24
|
}
|
|
23
25
|
async generateResponse(options) {
|
|
24
26
|
const { urlPath, kernelClientId, method, params, requestBody, headers } = options;
|
|
25
|
-
const executor = this.
|
|
27
|
+
const executor = this._pythonServers.get(kernelClientId);
|
|
26
28
|
if (!executor) {
|
|
27
29
|
return null;
|
|
28
30
|
}
|
|
@@ -35,8 +37,8 @@ export class ConnectionManager {
|
|
|
35
37
|
});
|
|
36
38
|
return response;
|
|
37
39
|
}
|
|
38
|
-
_initWsChannel() {
|
|
39
|
-
|
|
40
|
+
_initWsChannel(broadcastChannel) {
|
|
41
|
+
broadcastChannel.onmessage = event => {
|
|
40
42
|
const rawData = event.data;
|
|
41
43
|
let data;
|
|
42
44
|
if (typeof rawData === 'string') {
|
|
@@ -46,9 +48,9 @@ export class ConnectionManager {
|
|
|
46
48
|
data = rawData;
|
|
47
49
|
}
|
|
48
50
|
const { action, dest, wsUrl, payload } = data;
|
|
49
|
-
const executor = this.
|
|
51
|
+
const executor = this._pythonServers.get(dest);
|
|
50
52
|
if (!executor) {
|
|
51
|
-
console.error('Missing kernel handle for message', data, dest, this.
|
|
53
|
+
console.error('Missing kernel handle for message', data, dest, this._pythonServers);
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
switch (action) {
|
|
@@ -61,6 +63,14 @@ export class ConnectionManager {
|
|
|
61
63
|
});
|
|
62
64
|
break;
|
|
63
65
|
}
|
|
66
|
+
case 'close': {
|
|
67
|
+
executor.closeWebsocket({
|
|
68
|
+
instanceId: this.instanceId,
|
|
69
|
+
kernelId: dest,
|
|
70
|
+
wsUrl
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
64
74
|
case 'send': {
|
|
65
75
|
let serializedData;
|
|
66
76
|
let isBinary;
|
|
@@ -70,7 +80,8 @@ export class ConnectionManager {
|
|
|
70
80
|
isBinary = true;
|
|
71
81
|
}
|
|
72
82
|
else if (typeof payload === 'string') {
|
|
73
|
-
|
|
83
|
+
// convert string to base64 string to avoid encoding problem
|
|
84
|
+
serializedData = stringToBase64(payload);
|
|
74
85
|
isBinary = false;
|
|
75
86
|
}
|
|
76
87
|
else {
|
package/lib/tools.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export declare function removePrefix(path: string, prefix: string): string;
|
|
|
7
7
|
export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
|
|
8
8
|
export declare function base64ToArrayBuffer(base64: string): Uint8Array;
|
|
9
9
|
export declare function base64ToString(base64: string): string;
|
|
10
|
+
export declare function stringToBase64(str: string): string;
|
|
10
11
|
export declare function stringOrNone(content?: string): string;
|
|
11
12
|
export declare function isBinaryContentType(contentType?: string): boolean;
|
package/lib/tools.js
CHANGED
|
@@ -44,6 +44,14 @@ export function base64ToString(base64) {
|
|
|
44
44
|
const bytes = base64ToArrayBuffer(base64);
|
|
45
45
|
return new TextDecoder('utf-8').decode(bytes);
|
|
46
46
|
}
|
|
47
|
+
export function stringToBase64(str) {
|
|
48
|
+
const bytes = new TextEncoder().encode(str);
|
|
49
|
+
let binary = '';
|
|
50
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
51
|
+
binary += String.fromCharCode(bytes[i]);
|
|
52
|
+
}
|
|
53
|
+
return window.btoa(binary);
|
|
54
|
+
}
|
|
47
55
|
export function stringOrNone(content) {
|
|
48
56
|
return content ? `"${content}"` : 'None';
|
|
49
57
|
}
|
package/lib/type.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export declare enum JupyterPackFramework {
|
|
|
16
16
|
REACT = "react",
|
|
17
17
|
DASH = "dash",
|
|
18
18
|
STREAMLIT = "streamlit",
|
|
19
|
-
TORNADO = "tornado"
|
|
19
|
+
TORNADO = "tornado",
|
|
20
|
+
SHINY = "shiny",
|
|
21
|
+
STARLETTE = "starlette"
|
|
20
22
|
}
|
|
21
23
|
export interface IJupyterPackFileFormat {
|
|
22
24
|
entry: string;
|
|
@@ -38,6 +40,9 @@ export interface IKernelExecutorParams {
|
|
|
38
40
|
requestBody?: ArrayBuffer;
|
|
39
41
|
}
|
|
40
42
|
export interface IKernelExecutor extends IDisposable {
|
|
43
|
+
executeCode(code: KernelMessage.IExecuteRequestMsg['content'], waitForResult?: boolean): Promise<string | null>;
|
|
44
|
+
}
|
|
45
|
+
export interface IBasePythonServer extends IDisposable {
|
|
41
46
|
getResponse(options: IKernelExecutorParams): Promise<IDict>;
|
|
42
47
|
openWebsocket(options: {
|
|
43
48
|
instanceId: string;
|
|
@@ -45,13 +50,17 @@ export interface IKernelExecutor extends IDisposable {
|
|
|
45
50
|
wsUrl: string;
|
|
46
51
|
protocol?: string;
|
|
47
52
|
}): Promise<void>;
|
|
53
|
+
closeWebsocket(options: {
|
|
54
|
+
instanceId: string;
|
|
55
|
+
kernelId: string;
|
|
56
|
+
wsUrl: string;
|
|
57
|
+
}): Promise<void>;
|
|
48
58
|
sendWebsocketMessage(options: {
|
|
49
59
|
instanceId: string;
|
|
50
60
|
kernelId: string;
|
|
51
61
|
wsUrl: string;
|
|
52
62
|
message: string;
|
|
53
63
|
}): Promise<void>;
|
|
54
|
-
executeCode(code: KernelMessage.IExecuteRequestMsg['content'], waitForResult?: boolean): Promise<string | null>;
|
|
55
64
|
init(options: {
|
|
56
65
|
entryPath?: string;
|
|
57
66
|
initCode?: string;
|
|
@@ -72,7 +81,7 @@ export interface IKernelExecutor extends IDisposable {
|
|
|
72
81
|
}): string;
|
|
73
82
|
}
|
|
74
83
|
export interface IConnectionManager {
|
|
75
|
-
registerConnection(kernelExecutor:
|
|
84
|
+
registerConnection(kernelExecutor: IBasePythonServer): Promise<{
|
|
76
85
|
instanceId: string;
|
|
77
86
|
kernelClientId: string;
|
|
78
87
|
}>;
|
package/lib/type.js
CHANGED
|
@@ -4,6 +4,8 @@ export var JupyterPackFramework;
|
|
|
4
4
|
JupyterPackFramework["DASH"] = "dash";
|
|
5
5
|
JupyterPackFramework["STREAMLIT"] = "streamlit";
|
|
6
6
|
JupyterPackFramework["TORNADO"] = "tornado";
|
|
7
|
+
JupyterPackFramework["SHINY"] = "shiny";
|
|
8
|
+
JupyterPackFramework["STARLETTE"] = "starlette";
|
|
7
9
|
})(JupyterPackFramework || (JupyterPackFramework = {}));
|
|
8
10
|
export var MessageAction;
|
|
9
11
|
(function (MessageAction) {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
return data;
|
|
38
38
|
};
|
|
39
|
-
const bcWsChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}`);
|
|
39
|
+
const bcWsChannel = new BroadcastChannel(`/jupyterpack/ws/${instanceId}/${kernelClientId}`);
|
|
40
40
|
class BroadcastChannelWebSocket {
|
|
41
41
|
constructor(url, protocols) {
|
|
42
42
|
this.onclose = () => {
|
|
@@ -128,6 +128,10 @@
|
|
|
128
128
|
cb();
|
|
129
129
|
}
|
|
130
130
|
this._eventHandlers['close'] = [];
|
|
131
|
+
sendTypedMessage({
|
|
132
|
+
action: 'close',
|
|
133
|
+
wsUrl: this.url
|
|
134
|
+
});
|
|
131
135
|
bcWsChannel.removeEventListener('message', this._bcMessageHandler);
|
|
132
136
|
this.readyState = this.CLOSED;
|
|
133
137
|
}
|
package/package.json
CHANGED
|
@@ -54,7 +54,9 @@ export class JupyterPackWidgetFactory extends ABCWidgetFactory<JupyterPackDocWid
|
|
|
54
54
|
}
|
|
55
55
|
case JupyterPackFramework.DASH:
|
|
56
56
|
case JupyterPackFramework.STREAMLIT:
|
|
57
|
-
case JupyterPackFramework.TORNADO:
|
|
57
|
+
case JupyterPackFramework.TORNADO:
|
|
58
|
+
case JupyterPackFramework.STARLETTE:
|
|
59
|
+
case JupyterPackFramework.SHINY: {
|
|
58
60
|
const model = new PythonWidgetModel({
|
|
59
61
|
jpackModel,
|
|
60
62
|
context,
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { KernelExecutor } from './kernelExecutor';
|
|
2
|
+
import websocketPatch from '../websocket/websocket.js?raw';
|
|
3
|
+
import {
|
|
4
|
+
IDict,
|
|
5
|
+
IBasePythonServer,
|
|
6
|
+
IKernelExecutor,
|
|
7
|
+
JupyterPackFramework
|
|
8
|
+
} from '../type';
|
|
9
|
+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
10
|
+
import {
|
|
11
|
+
arrayBufferToBase64,
|
|
12
|
+
base64ToArrayBuffer,
|
|
13
|
+
base64ToString,
|
|
14
|
+
isBinaryContentType,
|
|
15
|
+
stringOrNone
|
|
16
|
+
} from '../tools';
|
|
17
|
+
|
|
18
|
+
export abstract class BasePythonServer implements IBasePythonServer {
|
|
19
|
+
constructor(options: KernelExecutor.IOptions) {
|
|
20
|
+
this._kernelExecutor = new KernelExecutor(options);
|
|
21
|
+
this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
abstract reloadPythonServer(options: {
|
|
25
|
+
entryPath?: string;
|
|
26
|
+
initCode?: string;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
|
|
29
|
+
get isDisposed(): boolean {
|
|
30
|
+
return this._isDisposed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get kernelExecutor() {
|
|
34
|
+
return this._kernelExecutor;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
dispose(): void {
|
|
38
|
+
if (this._isDisposed) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this._isDisposed = true;
|
|
42
|
+
this.disposePythonServer()
|
|
43
|
+
.then(() => {
|
|
44
|
+
this._kernelExecutor.dispose();
|
|
45
|
+
})
|
|
46
|
+
.catch(console.error);
|
|
47
|
+
}
|
|
48
|
+
async init(options: {
|
|
49
|
+
entryPath?: string;
|
|
50
|
+
initCode?: string;
|
|
51
|
+
instanceId: string;
|
|
52
|
+
kernelClientId: string;
|
|
53
|
+
}): Promise<void> {
|
|
54
|
+
const patchCode = `
|
|
55
|
+
from jupyterpack.common import patch_all
|
|
56
|
+
patch_all()
|
|
57
|
+
`;
|
|
58
|
+
await this._kernelExecutor.executeCode({ code: patchCode });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async disposePythonServer(): Promise<void> {
|
|
62
|
+
await this.kernelExecutor.executeCode({
|
|
63
|
+
code: `${this._server_var}.dispose()`
|
|
64
|
+
});
|
|
65
|
+
for (const element of this._openedWebsockets) {
|
|
66
|
+
await this.closeWebsocket(element);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getResponseFunctionFactory(options: {
|
|
71
|
+
urlPath: string;
|
|
72
|
+
method: string;
|
|
73
|
+
headers: IDict;
|
|
74
|
+
params?: string;
|
|
75
|
+
content?: string;
|
|
76
|
+
}) {
|
|
77
|
+
const { method, urlPath, headers, params, content } = options;
|
|
78
|
+
const code = `await ${this._server_var}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
|
|
79
|
+
return code;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
openWebsocketFunctionFactory(options: {
|
|
83
|
+
instanceId: string;
|
|
84
|
+
kernelId: string;
|
|
85
|
+
wsUrl: string;
|
|
86
|
+
protocol?: string;
|
|
87
|
+
}): string {
|
|
88
|
+
const { instanceId, kernelId, wsUrl, protocol } = options;
|
|
89
|
+
const code = `await ${this._server_var}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
|
|
90
|
+
return code;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
closeWebsocketFunctionFactory(options: {
|
|
94
|
+
instanceId: string;
|
|
95
|
+
kernelId: string;
|
|
96
|
+
wsUrl: string;
|
|
97
|
+
}): string {
|
|
98
|
+
const { instanceId, kernelId, wsUrl } = options;
|
|
99
|
+
const code = `await ${this._server_var}.close_ws("${instanceId}", "${kernelId}", "${wsUrl}")`;
|
|
100
|
+
return code;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
sendWebsocketMessageFunctionFactory(options: {
|
|
104
|
+
instanceId: string;
|
|
105
|
+
kernelId: string;
|
|
106
|
+
wsUrl: string;
|
|
107
|
+
message: string;
|
|
108
|
+
}): string {
|
|
109
|
+
const { instanceId, kernelId, wsUrl, message } = options;
|
|
110
|
+
const code = `await ${this._server_var}.receive_ws_message("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
|
|
111
|
+
return code;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async openWebsocket(options: {
|
|
115
|
+
instanceId: string;
|
|
116
|
+
kernelId: string;
|
|
117
|
+
wsUrl: string;
|
|
118
|
+
protocol?: string;
|
|
119
|
+
}): Promise<void> {
|
|
120
|
+
const code = this.openWebsocketFunctionFactory(options);
|
|
121
|
+
if (code) {
|
|
122
|
+
try {
|
|
123
|
+
await this._kernelExecutor.executeCode({ code });
|
|
124
|
+
this._openedWebsockets.push(options);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.error('Failed to open websocket', e);
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error('Missing websocket open code');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async closeWebsocket(options: {
|
|
134
|
+
instanceId: string;
|
|
135
|
+
kernelId: string;
|
|
136
|
+
wsUrl: string;
|
|
137
|
+
}): Promise<void> {
|
|
138
|
+
const code = this.closeWebsocketFunctionFactory(options);
|
|
139
|
+
if (code) {
|
|
140
|
+
await this._kernelExecutor.executeCode({ code });
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error('Missing websocket close code');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async sendWebsocketMessage(options: {
|
|
147
|
+
instanceId: string;
|
|
148
|
+
kernelId: string;
|
|
149
|
+
wsUrl: string;
|
|
150
|
+
message: string;
|
|
151
|
+
}): Promise<void> {
|
|
152
|
+
const code = this.sendWebsocketMessageFunctionFactory(options);
|
|
153
|
+
if (code) {
|
|
154
|
+
await this._kernelExecutor.executeCode({ code });
|
|
155
|
+
} else {
|
|
156
|
+
throw new Error('Missing websocket send code');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getResponse(options: {
|
|
161
|
+
method: string;
|
|
162
|
+
urlPath: string;
|
|
163
|
+
headers: IDict;
|
|
164
|
+
requestBody?: ArrayBuffer;
|
|
165
|
+
params?: string;
|
|
166
|
+
}): Promise<IDict> {
|
|
167
|
+
const { method, urlPath, requestBody, params, headers } = options;
|
|
168
|
+
const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
|
|
169
|
+
const code = this.getResponseFunctionFactory({
|
|
170
|
+
method,
|
|
171
|
+
urlPath,
|
|
172
|
+
headers,
|
|
173
|
+
params,
|
|
174
|
+
content
|
|
175
|
+
});
|
|
176
|
+
const raw = await this._kernelExecutor.executeCode({ code }, true);
|
|
177
|
+
if (!raw) {
|
|
178
|
+
throw new Error(`Missing response for ${urlPath}`);
|
|
179
|
+
}
|
|
180
|
+
const jsonStr = raw.replaceAll("'", '');
|
|
181
|
+
const obj: {
|
|
182
|
+
headers: string;
|
|
183
|
+
status_code: number;
|
|
184
|
+
content: string;
|
|
185
|
+
} = JSON.parse(jsonStr);
|
|
186
|
+
const responseHeaders: IDict<string> = JSON.parse(atob(obj.headers));
|
|
187
|
+
const contentType: string | undefined =
|
|
188
|
+
responseHeaders?.['Content-Type'] ?? responseHeaders?.['content-type'];
|
|
189
|
+
let responseContent: string | Uint8Array;
|
|
190
|
+
|
|
191
|
+
if (isBinaryContentType(contentType)) {
|
|
192
|
+
responseContent = base64ToArrayBuffer(obj.content);
|
|
193
|
+
} else {
|
|
194
|
+
responseContent = base64ToString(obj.content);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (contentType && contentType.toLowerCase().includes('text/html')) {
|
|
198
|
+
responseContent = (responseContent as string).replace(
|
|
199
|
+
'<head>',
|
|
200
|
+
`<head>\n<script>\n${this._wsPatch}\n</script>\n`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const decodedObj = {
|
|
205
|
+
status_code: obj.status_code,
|
|
206
|
+
headers: responseHeaders,
|
|
207
|
+
content: responseContent
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return decodedObj;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
protected buildBaseURL(options: {
|
|
214
|
+
instanceId: string;
|
|
215
|
+
kernelClientId: string;
|
|
216
|
+
framework: JupyterPackFramework;
|
|
217
|
+
}) {
|
|
218
|
+
const { instanceId, kernelClientId, framework } = options;
|
|
219
|
+
const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
|
|
220
|
+
|
|
221
|
+
const baseURL = URLExt.join(
|
|
222
|
+
fullLabextensionsUrl,
|
|
223
|
+
'jupyterpack/static',
|
|
224
|
+
instanceId,
|
|
225
|
+
framework,
|
|
226
|
+
kernelClientId,
|
|
227
|
+
'/'
|
|
228
|
+
);
|
|
229
|
+
this._baseUrl = baseURL;
|
|
230
|
+
|
|
231
|
+
return baseURL;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
protected _baseUrl: string | undefined;
|
|
235
|
+
protected readonly _openedWebsockets: {
|
|
236
|
+
instanceId: string;
|
|
237
|
+
kernelId: string;
|
|
238
|
+
wsUrl: string;
|
|
239
|
+
}[] = [];
|
|
240
|
+
protected readonly _server_var = '__jupyterpack_python_server';
|
|
241
|
+
|
|
242
|
+
private _kernelExecutor: IKernelExecutor;
|
|
243
|
+
private _isDisposed: boolean = false;
|
|
244
|
+
private _wsPatch: string;
|
|
245
|
+
}
|