jupyterpack 0.4.0 → 0.5.2
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 +107 -3
- package/lib/document/widgetFactory.js +3 -1
- package/lib/pythonServer/baseServer.d.ts +78 -0
- package/lib/pythonServer/baseServer.js +175 -0
- package/lib/pythonServer/dash/dashServer.d.ts +4 -17
- package/lib/pythonServer/dash/dashServer.js +14 -22
- package/lib/pythonServer/dash/deps.d.ts +2 -0
- package/lib/pythonServer/dash/deps.js +4 -0
- 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/deps.d.ts +2 -0
- package/lib/pythonServer/shiny/deps.js +4 -0
- package/lib/pythonServer/shiny/shinyServer.d.ts +10 -0
- package/lib/pythonServer/shiny/shinyServer.js +54 -0
- package/lib/pythonServer/starlette/starletteServer.d.ts +13 -0
- package/lib/pythonServer/starlette/starletteServer.js +49 -0
- package/lib/pythonServer/streamlit/deps.d.ts +2 -0
- package/lib/pythonServer/streamlit/deps.js +10 -0
- package/lib/pythonServer/streamlit/streamlitServer.d.ts +4 -9
- package/lib/pythonServer/streamlit/streamlitServer.js +16 -15
- package/lib/pythonServer/tornado/tornadoServer.d.ts +2 -24
- package/lib/pythonServer/tornado/tornadoServer.js +10 -35
- package/lib/pythonWidget/pythonWidgetModel.js +2 -0
- 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 +27 -9
- 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 +285 -0
- package/src/pythonServer/dash/dashServer.ts +18 -35
- package/src/pythonServer/dash/deps.ts +6 -0
- package/src/pythonServer/index.ts +9 -5
- package/src/pythonServer/kernelExecutor.ts +3 -156
- package/src/pythonServer/shiny/deps.ts +6 -0
- package/src/pythonServer/shiny/shinyServer.ts +63 -0
- package/src/pythonServer/starlette/starletteServer.ts +59 -0
- package/src/pythonServer/streamlit/deps.ts +12 -0
- package/src/pythonServer/streamlit/streamlitServer.ts +19 -20
- package/src/pythonServer/tornado/tornadoServer.ts +11 -55
- package/src/pythonWidget/pythonWidgetModel.ts +5 -2
- package/src/swConnection/mainConnectionManager.ts +28 -20
- package/src/tools.ts +9 -0
- package/src/type.ts +34 -12
- package/src/websocket/websocket.ts +9 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
arrayBufferToBase64,
|
|
5
|
+
base64ToArrayBuffer,
|
|
6
|
+
base64ToString,
|
|
7
|
+
isBinaryContentType,
|
|
8
|
+
stringOrNone
|
|
9
|
+
} from '../tools';
|
|
10
|
+
import {
|
|
11
|
+
IBasePythonServer,
|
|
12
|
+
IDependencies,
|
|
13
|
+
IDict,
|
|
14
|
+
IKernelExecutor,
|
|
15
|
+
IPythonServerInitOptions,
|
|
16
|
+
JupyterPackFramework
|
|
17
|
+
} from '../type';
|
|
18
|
+
import websocketPatch from '../websocket/websocket.js?raw';
|
|
19
|
+
import { KernelExecutor } from './kernelExecutor';
|
|
20
|
+
|
|
21
|
+
export abstract class BasePythonServer implements IBasePythonServer {
|
|
22
|
+
constructor(options: KernelExecutor.IOptions) {
|
|
23
|
+
this._kernelExecutor = new KernelExecutor(options);
|
|
24
|
+
this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
abstract reloadPythonServer(options: {
|
|
28
|
+
entryPath?: string;
|
|
29
|
+
initCode?: string;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
|
|
32
|
+
get isDisposed(): boolean {
|
|
33
|
+
return this._isDisposed;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get kernelExecutor() {
|
|
37
|
+
return this._kernelExecutor;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
dispose(): void {
|
|
41
|
+
if (this._isDisposed) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this._isDisposed = true;
|
|
45
|
+
this.disposePythonServer()
|
|
46
|
+
.then(() => {
|
|
47
|
+
this._kernelExecutor.dispose();
|
|
48
|
+
})
|
|
49
|
+
.catch(console.error);
|
|
50
|
+
}
|
|
51
|
+
async init(options: IPythonServerInitOptions): Promise<void> {
|
|
52
|
+
const patchCode = `
|
|
53
|
+
from jupyterpack.common import patch_all
|
|
54
|
+
patch_all()
|
|
55
|
+
`;
|
|
56
|
+
await this._kernelExecutor.executeCode({ code: patchCode });
|
|
57
|
+
if (!options.disableDependencies) {
|
|
58
|
+
const { dependencies } = options;
|
|
59
|
+
if (dependencies?.mamba) {
|
|
60
|
+
const mambaDeps = `
|
|
61
|
+
%mamba install ${dependencies.mamba.join(' ')}
|
|
62
|
+
True
|
|
63
|
+
`;
|
|
64
|
+
await this.kernelExecutor.executeCode(
|
|
65
|
+
{
|
|
66
|
+
code: mambaDeps
|
|
67
|
+
},
|
|
68
|
+
true
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (dependencies?.pip) {
|
|
72
|
+
const pipDeps = `
|
|
73
|
+
%pip install ${dependencies.pip.join(' ')}
|
|
74
|
+
True
|
|
75
|
+
`;
|
|
76
|
+
await this.kernelExecutor.executeCode(
|
|
77
|
+
{
|
|
78
|
+
code: pipDeps
|
|
79
|
+
},
|
|
80
|
+
true
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async disposePythonServer(): Promise<void> {
|
|
87
|
+
await this.kernelExecutor.executeCode({
|
|
88
|
+
code: `${this._server_var}.dispose()`
|
|
89
|
+
});
|
|
90
|
+
for (const element of this._openedWebsockets) {
|
|
91
|
+
await this.closeWebsocket(element);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getResponseFunctionFactory(options: {
|
|
96
|
+
urlPath: string;
|
|
97
|
+
method: string;
|
|
98
|
+
headers: IDict;
|
|
99
|
+
params?: string;
|
|
100
|
+
content?: string;
|
|
101
|
+
}) {
|
|
102
|
+
const { method, urlPath, headers, params, content } = options;
|
|
103
|
+
const code = `await ${this._server_var}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
|
|
104
|
+
return code;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
openWebsocketFunctionFactory(options: {
|
|
108
|
+
instanceId: string;
|
|
109
|
+
kernelId: string;
|
|
110
|
+
wsUrl: string;
|
|
111
|
+
protocol?: string;
|
|
112
|
+
}): string {
|
|
113
|
+
const { instanceId, kernelId, wsUrl, protocol } = options;
|
|
114
|
+
const code = `await ${this._server_var}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
|
|
115
|
+
return code;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
closeWebsocketFunctionFactory(options: {
|
|
119
|
+
instanceId: string;
|
|
120
|
+
kernelId: string;
|
|
121
|
+
wsUrl: string;
|
|
122
|
+
}): string {
|
|
123
|
+
const { instanceId, kernelId, wsUrl } = options;
|
|
124
|
+
const code = `await ${this._server_var}.close_ws("${instanceId}", "${kernelId}", "${wsUrl}")`;
|
|
125
|
+
return code;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
sendWebsocketMessageFunctionFactory(options: {
|
|
129
|
+
instanceId: string;
|
|
130
|
+
kernelId: string;
|
|
131
|
+
wsUrl: string;
|
|
132
|
+
message: string;
|
|
133
|
+
}): string {
|
|
134
|
+
const { instanceId, kernelId, wsUrl, message } = options;
|
|
135
|
+
const code = `await ${this._server_var}.receive_ws_message("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
|
|
136
|
+
return code;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async openWebsocket(options: {
|
|
140
|
+
instanceId: string;
|
|
141
|
+
kernelId: string;
|
|
142
|
+
wsUrl: string;
|
|
143
|
+
protocol?: string;
|
|
144
|
+
}): Promise<void> {
|
|
145
|
+
const code = this.openWebsocketFunctionFactory(options);
|
|
146
|
+
if (code) {
|
|
147
|
+
try {
|
|
148
|
+
await this._kernelExecutor.executeCode({ code });
|
|
149
|
+
this._openedWebsockets.push(options);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error('Failed to open websocket', e);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error('Missing websocket open code');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async closeWebsocket(options: {
|
|
159
|
+
instanceId: string;
|
|
160
|
+
kernelId: string;
|
|
161
|
+
wsUrl: string;
|
|
162
|
+
}): Promise<void> {
|
|
163
|
+
const code = this.closeWebsocketFunctionFactory(options);
|
|
164
|
+
if (code) {
|
|
165
|
+
await this._kernelExecutor.executeCode({ code });
|
|
166
|
+
} else {
|
|
167
|
+
throw new Error('Missing websocket close code');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async sendWebsocketMessage(options: {
|
|
172
|
+
instanceId: string;
|
|
173
|
+
kernelId: string;
|
|
174
|
+
wsUrl: string;
|
|
175
|
+
message: string;
|
|
176
|
+
}): Promise<void> {
|
|
177
|
+
const code = this.sendWebsocketMessageFunctionFactory(options);
|
|
178
|
+
if (code) {
|
|
179
|
+
await this._kernelExecutor.executeCode({ code });
|
|
180
|
+
} else {
|
|
181
|
+
throw new Error('Missing websocket send code');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async getResponse(options: {
|
|
186
|
+
method: string;
|
|
187
|
+
urlPath: string;
|
|
188
|
+
headers: IDict;
|
|
189
|
+
requestBody?: ArrayBuffer;
|
|
190
|
+
params?: string;
|
|
191
|
+
}): Promise<IDict> {
|
|
192
|
+
const { method, urlPath, requestBody, params, headers } = options;
|
|
193
|
+
const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
|
|
194
|
+
const code = this.getResponseFunctionFactory({
|
|
195
|
+
method,
|
|
196
|
+
urlPath,
|
|
197
|
+
headers,
|
|
198
|
+
params,
|
|
199
|
+
content
|
|
200
|
+
});
|
|
201
|
+
const raw = await this._kernelExecutor.executeCode({ code }, true);
|
|
202
|
+
if (!raw) {
|
|
203
|
+
throw new Error(`Missing response for ${urlPath}`);
|
|
204
|
+
}
|
|
205
|
+
const jsonStr = raw.replaceAll("'", '');
|
|
206
|
+
const obj: {
|
|
207
|
+
headers: string;
|
|
208
|
+
status_code: number;
|
|
209
|
+
content: string;
|
|
210
|
+
} = JSON.parse(jsonStr);
|
|
211
|
+
const responseHeaders: IDict<string> = JSON.parse(atob(obj.headers));
|
|
212
|
+
const contentType: string | undefined =
|
|
213
|
+
responseHeaders?.['Content-Type'] ?? responseHeaders?.['content-type'];
|
|
214
|
+
let responseContent: string | Uint8Array;
|
|
215
|
+
|
|
216
|
+
if (isBinaryContentType(contentType)) {
|
|
217
|
+
responseContent = base64ToArrayBuffer(obj.content);
|
|
218
|
+
} else {
|
|
219
|
+
responseContent = base64ToString(obj.content);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (contentType && contentType.toLowerCase().includes('text/html')) {
|
|
223
|
+
responseContent = (responseContent as string).replace(
|
|
224
|
+
'<head>',
|
|
225
|
+
`<head>\n<script>\n${this._wsPatch}\n</script>\n`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const decodedObj = {
|
|
230
|
+
status_code: obj.status_code,
|
|
231
|
+
headers: responseHeaders,
|
|
232
|
+
content: responseContent
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return decodedObj;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
protected buildBaseURL(options: {
|
|
239
|
+
instanceId: string;
|
|
240
|
+
kernelClientId: string;
|
|
241
|
+
framework: JupyterPackFramework;
|
|
242
|
+
}) {
|
|
243
|
+
const { instanceId, kernelClientId, framework } = options;
|
|
244
|
+
const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
|
|
245
|
+
|
|
246
|
+
const baseURL = URLExt.join(
|
|
247
|
+
fullLabextensionsUrl,
|
|
248
|
+
'jupyterpack/static',
|
|
249
|
+
instanceId,
|
|
250
|
+
framework,
|
|
251
|
+
kernelClientId,
|
|
252
|
+
'/'
|
|
253
|
+
);
|
|
254
|
+
this._baseUrl = baseURL;
|
|
255
|
+
|
|
256
|
+
return baseURL;
|
|
257
|
+
}
|
|
258
|
+
protected mergeDependencies(
|
|
259
|
+
spkDeps?: IDependencies,
|
|
260
|
+
defaultDeps?: IDependencies
|
|
261
|
+
): IDependencies | undefined {
|
|
262
|
+
if (!spkDeps) {
|
|
263
|
+
return defaultDeps;
|
|
264
|
+
}
|
|
265
|
+
if (!defaultDeps) {
|
|
266
|
+
return spkDeps;
|
|
267
|
+
}
|
|
268
|
+
const merged: IDependencies = {};
|
|
269
|
+
merged.mamba = [...(spkDeps.mamba ?? []), ...(defaultDeps.mamba ?? [])];
|
|
270
|
+
merged.pip = [...(spkDeps.pip ?? []), ...(defaultDeps.pip ?? [])];
|
|
271
|
+
return merged;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
protected _baseUrl: string | undefined;
|
|
275
|
+
protected readonly _openedWebsockets: {
|
|
276
|
+
instanceId: string;
|
|
277
|
+
kernelId: string;
|
|
278
|
+
wsUrl: string;
|
|
279
|
+
}[] = [];
|
|
280
|
+
protected readonly _server_var = '__jupyterpack_python_server';
|
|
281
|
+
|
|
282
|
+
private _kernelExecutor: IKernelExecutor;
|
|
283
|
+
private _isDisposed: boolean = false;
|
|
284
|
+
private _wsPatch: string;
|
|
285
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { IPythonServerInitOptions, JupyterPackFramework } from '../../type';
|
|
2
|
+
import { BasePythonServer } from '../baseServer';
|
|
3
|
+
import { DEPENDENCIES } from './deps';
|
|
4
|
+
|
|
5
|
+
export class DashServer extends BasePythonServer {
|
|
6
|
+
async init(options: IPythonServerInitOptions) {
|
|
7
|
+
const mergedOptions: IPythonServerInitOptions = {
|
|
8
|
+
...options,
|
|
9
|
+
dependencies: this.mergeDependencies(options.dependencies, DEPENDENCIES)
|
|
10
|
+
};
|
|
11
|
+
await super.init(mergedOptions);
|
|
4
12
|
|
|
5
|
-
export class DashServer extends KernelExecutor {
|
|
6
|
-
async init(options: {
|
|
7
|
-
initCode?: string;
|
|
8
|
-
instanceId: string;
|
|
9
|
-
kernelClientId: string;
|
|
10
|
-
}) {
|
|
11
|
-
await super.init(options);
|
|
12
13
|
const { initCode, instanceId, kernelClientId } = options;
|
|
13
14
|
|
|
14
15
|
const baseURL = this.buildBaseURL({
|
|
@@ -16,36 +17,20 @@ export class DashServer extends KernelExecutor {
|
|
|
16
17
|
kernelClientId,
|
|
17
18
|
framework: JupyterPackFramework.DASH
|
|
18
19
|
});
|
|
19
|
-
await this.executeCode({
|
|
20
|
+
await this.kernelExecutor.executeCode({
|
|
20
21
|
code: `
|
|
21
22
|
import os
|
|
22
23
|
os.environ["DASH_URL_BASE_PATHNAME"] = "${baseURL}"
|
|
23
24
|
`
|
|
24
25
|
});
|
|
25
26
|
if (initCode) {
|
|
26
|
-
await this.executeCode({ code: initCode });
|
|
27
|
+
await this.kernelExecutor.executeCode({ code: initCode });
|
|
27
28
|
}
|
|
28
29
|
const loaderCode = `
|
|
29
30
|
from jupyterpack.dash import DashServer
|
|
30
|
-
${this.
|
|
31
|
+
${this._server_var} = DashServer(app, "${baseURL}")
|
|
31
32
|
`;
|
|
32
|
-
await this.executeCode({ code: loaderCode });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
getResponseFunctionFactory(options: {
|
|
36
|
-
urlPath: string;
|
|
37
|
-
method: string;
|
|
38
|
-
headers: IDict;
|
|
39
|
-
params?: string;
|
|
40
|
-
content?: string;
|
|
41
|
-
}) {
|
|
42
|
-
const { method, urlPath, headers, params, content } = options;
|
|
43
|
-
const code = `${this._DASH_SERVER_VAR}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
|
|
44
|
-
return code;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async disposePythonServer(): Promise<void> {
|
|
48
|
-
await this.executeCode({ code: `${this._DASH_SERVER_VAR}.dispose()` });
|
|
33
|
+
await this.kernelExecutor.executeCode({ code: loaderCode });
|
|
49
34
|
}
|
|
50
35
|
|
|
51
36
|
async reloadPythonServer(options: {
|
|
@@ -54,13 +39,11 @@ export class DashServer extends KernelExecutor {
|
|
|
54
39
|
}): Promise<void> {
|
|
55
40
|
const { initCode } = options;
|
|
56
41
|
if (initCode) {
|
|
57
|
-
await this.executeCode({ code: initCode });
|
|
42
|
+
await this.kernelExecutor.executeCode({ code: initCode });
|
|
58
43
|
}
|
|
59
|
-
await this.executeCode(
|
|
60
|
-
{ code: `${this.
|
|
44
|
+
await this.kernelExecutor.executeCode(
|
|
45
|
+
{ code: `${this._server_var}.reload(app)` },
|
|
61
46
|
true
|
|
62
47
|
);
|
|
63
48
|
}
|
|
64
|
-
|
|
65
|
-
private _DASH_SERVER_VAR = '__jupyterpack_dash_server';
|
|
66
49
|
}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IBasePythonServer, JupyterPackFramework } from '../type';
|
|
2
2
|
import { DashServer } from './dash/dashServer';
|
|
3
3
|
import { KernelExecutor } from './kernelExecutor';
|
|
4
|
+
import { ShinyServer } from './shiny/shinyServer';
|
|
5
|
+
import { StarletteServer } from './starlette/starletteServer';
|
|
4
6
|
import { StreamlitServer } from './streamlit/streamlitServer';
|
|
5
7
|
import { TornadoServer } from './tornado/tornadoServer';
|
|
6
8
|
|
|
7
|
-
type
|
|
9
|
+
type BasePythonServerConstructor = new (
|
|
8
10
|
options: KernelExecutor.IOptions
|
|
9
|
-
) =>
|
|
11
|
+
) => IBasePythonServer;
|
|
10
12
|
|
|
11
13
|
export const PYTHON_SERVER = new Map<
|
|
12
14
|
JupyterPackFramework,
|
|
13
|
-
|
|
15
|
+
BasePythonServerConstructor
|
|
14
16
|
>([
|
|
15
17
|
[JupyterPackFramework.DASH, DashServer],
|
|
16
18
|
[JupyterPackFramework.STREAMLIT, StreamlitServer],
|
|
17
|
-
[JupyterPackFramework.TORNADO, TornadoServer]
|
|
19
|
+
[JupyterPackFramework.TORNADO, TornadoServer],
|
|
20
|
+
[JupyterPackFramework.SHINY, ShinyServer],
|
|
21
|
+
[JupyterPackFramework.STARLETTE, StarletteServer]
|
|
18
22
|
]);
|
|
@@ -1,146 +1,17 @@
|
|
|
1
|
-
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
2
1
|
import { KernelMessage, Session } from '@jupyterlab/services';
|
|
3
2
|
import stripAnsi from 'strip-ansi';
|
|
4
|
-
import {
|
|
5
|
-
arrayBufferToBase64,
|
|
6
|
-
base64ToArrayBuffer,
|
|
7
|
-
base64ToString,
|
|
8
|
-
isBinaryContentType
|
|
9
|
-
} from '../tools';
|
|
10
|
-
import { IDict, IKernelExecutor, JupyterPackFramework } from '../type';
|
|
11
|
-
import websocketPatch from '../websocket/websocket.js?raw';
|
|
12
3
|
|
|
13
|
-
|
|
4
|
+
import { IKernelExecutor } from '../type';
|
|
5
|
+
|
|
6
|
+
export class KernelExecutor implements IKernelExecutor {
|
|
14
7
|
constructor(options: KernelExecutor.IOptions) {
|
|
15
8
|
this._sessionConnection = options.sessionConnection;
|
|
16
|
-
this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
|
|
17
9
|
}
|
|
18
10
|
|
|
19
11
|
get isDisposed(): boolean {
|
|
20
12
|
return this._isDisposed;
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
abstract disposePythonServer(): Promise<void>;
|
|
24
|
-
abstract reloadPythonServer(options: {
|
|
25
|
-
entryPath?: string;
|
|
26
|
-
initCode?: string;
|
|
27
|
-
}): Promise<void>;
|
|
28
|
-
|
|
29
|
-
abstract getResponseFunctionFactory(options: {
|
|
30
|
-
urlPath: string;
|
|
31
|
-
method: string;
|
|
32
|
-
headers: IDict;
|
|
33
|
-
params?: string;
|
|
34
|
-
content?: string;
|
|
35
|
-
}): string;
|
|
36
|
-
|
|
37
|
-
async init(options: {
|
|
38
|
-
entryPath?: string;
|
|
39
|
-
initCode?: string;
|
|
40
|
-
instanceId: string;
|
|
41
|
-
kernelClientId: string;
|
|
42
|
-
}): Promise<void> {
|
|
43
|
-
const patchCode = `
|
|
44
|
-
from jupyterpack.common import patch_all
|
|
45
|
-
patch_all()
|
|
46
|
-
`;
|
|
47
|
-
await this.executeCode({ code: patchCode });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
openWebsocketFunctionFactory(options: {
|
|
51
|
-
instanceId: string;
|
|
52
|
-
kernelId: string;
|
|
53
|
-
wsUrl: string;
|
|
54
|
-
protocol?: string;
|
|
55
|
-
}): string | undefined {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
sendWebsocketMessageFunctionFactory(options: {
|
|
60
|
-
instanceId: string;
|
|
61
|
-
kernelId: string;
|
|
62
|
-
wsUrl: string;
|
|
63
|
-
message: string;
|
|
64
|
-
}): string | undefined {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async openWebsocket(options: {
|
|
69
|
-
instanceId: string;
|
|
70
|
-
kernelId: string;
|
|
71
|
-
wsUrl: string;
|
|
72
|
-
protocol?: string;
|
|
73
|
-
}): Promise<void> {
|
|
74
|
-
const code = this.openWebsocketFunctionFactory(options);
|
|
75
|
-
if (code) {
|
|
76
|
-
await this.executeCode({ code });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async sendWebsocketMessage(options: {
|
|
81
|
-
instanceId: string;
|
|
82
|
-
kernelId: string;
|
|
83
|
-
wsUrl: string;
|
|
84
|
-
message: string;
|
|
85
|
-
}): Promise<void> {
|
|
86
|
-
const code = this.sendWebsocketMessageFunctionFactory(options);
|
|
87
|
-
if (code) {
|
|
88
|
-
await this.executeCode({ code });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async getResponse(options: {
|
|
93
|
-
method: string;
|
|
94
|
-
urlPath: string;
|
|
95
|
-
headers: IDict;
|
|
96
|
-
requestBody?: ArrayBuffer;
|
|
97
|
-
params?: string;
|
|
98
|
-
}): Promise<IDict> {
|
|
99
|
-
const { method, urlPath, requestBody, params, headers } = options;
|
|
100
|
-
const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
|
|
101
|
-
const code = this.getResponseFunctionFactory({
|
|
102
|
-
method,
|
|
103
|
-
urlPath,
|
|
104
|
-
headers,
|
|
105
|
-
params,
|
|
106
|
-
content
|
|
107
|
-
});
|
|
108
|
-
const raw = await this.executeCode({ code }, true);
|
|
109
|
-
if (!raw) {
|
|
110
|
-
throw new Error(`Missing response for ${urlPath}`);
|
|
111
|
-
}
|
|
112
|
-
const jsonStr = raw.replaceAll("'", '');
|
|
113
|
-
const obj: {
|
|
114
|
-
headers: string;
|
|
115
|
-
status_code: number;
|
|
116
|
-
content: string;
|
|
117
|
-
} = JSON.parse(jsonStr);
|
|
118
|
-
const responseHeaders: IDict<string> = JSON.parse(atob(obj.headers));
|
|
119
|
-
const contentType: string | undefined =
|
|
120
|
-
responseHeaders?.['Content-Type'] ?? responseHeaders?.['content-type'];
|
|
121
|
-
let responseContent: string | Uint8Array;
|
|
122
|
-
|
|
123
|
-
if (isBinaryContentType(contentType)) {
|
|
124
|
-
responseContent = base64ToArrayBuffer(obj.content);
|
|
125
|
-
} else {
|
|
126
|
-
responseContent = base64ToString(obj.content);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (contentType && contentType.toLowerCase() === 'text/html') {
|
|
130
|
-
responseContent = (responseContent as string).replace(
|
|
131
|
-
'<head>',
|
|
132
|
-
`<head>\n<script>\n${this._wsPatch}\n</script>\n`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const decodedObj = {
|
|
137
|
-
status_code: obj.status_code,
|
|
138
|
-
headers: responseHeaders,
|
|
139
|
-
content: responseContent
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
return decodedObj;
|
|
143
|
-
}
|
|
144
15
|
async executeCode(
|
|
145
16
|
code: KernelMessage.IExecuteRequestMsg['content'],
|
|
146
17
|
waitForResult?: boolean
|
|
@@ -208,32 +79,8 @@ export abstract class KernelExecutor implements IKernelExecutor {
|
|
|
208
79
|
this._sessionConnection.dispose();
|
|
209
80
|
}
|
|
210
81
|
|
|
211
|
-
protected buildBaseURL(options: {
|
|
212
|
-
instanceId: string;
|
|
213
|
-
kernelClientId: string;
|
|
214
|
-
framework: JupyterPackFramework;
|
|
215
|
-
}) {
|
|
216
|
-
const { instanceId, kernelClientId, framework } = options;
|
|
217
|
-
const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
|
|
218
|
-
|
|
219
|
-
const baseURL = URLExt.join(
|
|
220
|
-
fullLabextensionsUrl,
|
|
221
|
-
'jupyterpack/static',
|
|
222
|
-
instanceId,
|
|
223
|
-
framework,
|
|
224
|
-
kernelClientId,
|
|
225
|
-
'/'
|
|
226
|
-
);
|
|
227
|
-
this._baseUrl = baseURL;
|
|
228
|
-
|
|
229
|
-
return baseURL;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
protected _baseUrl: string | undefined;
|
|
233
|
-
|
|
234
82
|
private _isDisposed: boolean = false;
|
|
235
83
|
private _sessionConnection: Session.ISessionConnection;
|
|
236
|
-
private _wsPatch: string;
|
|
237
84
|
}
|
|
238
85
|
|
|
239
86
|
export namespace KernelExecutor {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { IPythonServerInitOptions, JupyterPackFramework } from '../../type';
|
|
2
|
+
import { BasePythonServer } from '../baseServer';
|
|
3
|
+
import { DEPENDENCIES } from './deps';
|
|
4
|
+
|
|
5
|
+
export class ShinyServer extends BasePythonServer {
|
|
6
|
+
async init(options: IPythonServerInitOptions) {
|
|
7
|
+
const mergedOptions: IPythonServerInitOptions = {
|
|
8
|
+
...options,
|
|
9
|
+
dependencies: this.mergeDependencies(options.dependencies, DEPENDENCIES)
|
|
10
|
+
};
|
|
11
|
+
await super.init(mergedOptions);
|
|
12
|
+
|
|
13
|
+
const { instanceId, kernelClientId, entryPath } = options;
|
|
14
|
+
const baseURL = this.buildBaseURL({
|
|
15
|
+
instanceId,
|
|
16
|
+
kernelClientId,
|
|
17
|
+
framework: JupyterPackFramework.SHINY
|
|
18
|
+
});
|
|
19
|
+
const bootstrapCode = `
|
|
20
|
+
from jupyterpack.common import set_base_url_env
|
|
21
|
+
set_base_url_env("${baseURL}")
|
|
22
|
+
from jupyterpack.shiny import patch_shiny
|
|
23
|
+
patch_shiny()
|
|
24
|
+
`;
|
|
25
|
+
await this.kernelExecutor.executeCode({ code: bootstrapCode });
|
|
26
|
+
if (entryPath) {
|
|
27
|
+
const loaderCode = `
|
|
28
|
+
from jupyterpack.shiny import ShinyServer, get_shiny_app
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
${this._server_var} = ShinyServer(get_shiny_app("${entryPath}"), "${baseURL}")
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
await this.kernelExecutor.executeCode({ code: loaderCode });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async disposePythonServer(): Promise<void> {
|
|
39
|
+
await this.kernelExecutor.executeCode({
|
|
40
|
+
code: `${this._server_var}.dispose()`
|
|
41
|
+
});
|
|
42
|
+
for (const element of this._openedWebsockets) {
|
|
43
|
+
await this.closeWebsocket(element);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async reloadPythonServer(options: {
|
|
48
|
+
entryPath?: string;
|
|
49
|
+
initCode?: string;
|
|
50
|
+
}): Promise<void> {
|
|
51
|
+
const { entryPath } = options;
|
|
52
|
+
if (entryPath) {
|
|
53
|
+
const reloadCode = `
|
|
54
|
+
from jupyterpack.shiny import get_shiny_app
|
|
55
|
+
|
|
56
|
+
await ${this._server_var}.dispose()
|
|
57
|
+
${this._server_var}.reload(get_shiny_app("${entryPath}"))
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
await this.kernelExecutor.executeCode({ code: reloadCode }, true);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|