jupyterpack 0.2.0 → 0.3.0
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 +2 -0
- package/lib/document/widgetFactory.js +4 -1
- package/lib/pythonServer/common/generatedPythonFiles.d.ts +2 -0
- package/lib/pythonServer/common/generatedPythonFiles.js +72 -0
- package/lib/pythonServer/dash/dashServer.d.ts +24 -0
- package/lib/pythonServer/dash/dashServer.js +39 -0
- package/lib/pythonServer/dash/generatedPythonFiles.d.ts +2 -0
- package/lib/pythonServer/dash/generatedPythonFiles.js +31 -0
- package/lib/pythonServer/index.d.ts +5 -0
- package/lib/pythonServer/index.js +9 -0
- package/lib/pythonServer/kernelExecutor.d.ts +66 -0
- package/lib/pythonServer/kernelExecutor.js +133 -0
- package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +2 -0
- package/lib/pythonServer/streamlit/generatedPythonFiles.js +147 -0
- package/lib/pythonServer/streamlit/streamlitServer.d.ts +33 -0
- package/lib/pythonServer/streamlit/streamlitServer.js +55 -0
- package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +3 -0
- package/lib/pythonServer/tornado/generatedPythonFiles.js +456 -0
- package/lib/pythonServer/tornado/tornadoServer.d.ts +32 -0
- package/lib/pythonServer/tornado/tornadoServer.js +51 -0
- package/lib/pythonWidget/pythonWidget.d.ts +1 -0
- package/lib/pythonWidget/pythonWidget.js +9 -3
- package/lib/pythonWidget/pythonWidgetModel.d.ts +12 -3
- package/lib/pythonWidget/pythonWidgetModel.js +32 -10
- package/lib/swConnection/index.js +2 -2
- package/lib/{pythonWidget/connectionManager.d.ts → swConnection/mainConnectionManager.d.ts} +10 -0
- package/lib/swConnection/mainConnectionManager.js +93 -0
- package/lib/swConnection/sw.js +5 -2
- package/lib/swConnection/swCommManager.d.ts +11 -0
- package/lib/swConnection/{comm_manager.js → swCommManager.js} +11 -3
- package/lib/tools.d.ts +4 -0
- package/lib/tools.js +58 -0
- package/lib/type.d.ts +37 -2
- package/lib/type.js +2 -0
- package/lib/websocket/websocket.d.ts +0 -0
- package/lib/websocket/websocket.js +152 -0
- package/package.json +8 -5
- package/src/document/widgetFactory.ts +4 -1
- package/src/global.d.ts +4 -0
- package/src/pythonServer/common/generatedPythonFiles.ts +73 -0
- package/src/pythonServer/dash/dashServer.ts +57 -0
- package/src/pythonServer/dash/generatedPythonFiles.ts +32 -0
- package/src/pythonServer/index.ts +18 -0
- package/src/pythonServer/kernelExecutor.ts +229 -0
- package/src/pythonServer/streamlit/generatedPythonFiles.ts +148 -0
- package/src/pythonServer/streamlit/streamlitServer.ts +87 -0
- package/src/pythonServer/tornado/generatedPythonFiles.ts +457 -0
- package/src/pythonServer/tornado/tornadoServer.ts +80 -0
- package/src/pythonWidget/pythonWidget.ts +20 -3
- package/src/pythonWidget/pythonWidgetModel.ts +53 -19
- package/src/swConnection/index.ts +5 -2
- package/src/swConnection/mainConnectionManager.ts +121 -0
- package/src/swConnection/sw.ts +5 -2
- package/src/swConnection/{comm_manager.ts → swCommManager.ts} +15 -5
- package/src/tools.ts +69 -0
- package/src/type.ts +47 -3
- package/src/websocket/websocket.ts +216 -0
- package/lib/pythonWidget/connectionManager.js +0 -27
- package/lib/pythonWidget/kernelExecutor.d.ts +0 -27
- package/lib/pythonWidget/kernelExecutor.js +0 -104
- package/lib/swConnection/comm_manager.d.ts +0 -6
- package/lib/swConnection/connection_manager.d.ts +0 -18
- package/lib/swConnection/connection_manager.js +0 -27
- package/src/pythonWidget/connectionManager.ts +0 -43
- package/src/pythonWidget/kernelExecutor.ts +0 -140
- package/src/swConnection/connection_manager.ts +0 -43
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Auto-generated TypeScript file from Python files
|
|
2
|
+
|
|
3
|
+
export const patch = `
|
|
4
|
+
import pyodide_http
|
|
5
|
+
import collections
|
|
6
|
+
|
|
7
|
+
if not hasattr(collections, "MutableSet"):
|
|
8
|
+
import collections.abc
|
|
9
|
+
|
|
10
|
+
collections.MutableSet = collections.abc.MutableSet
|
|
11
|
+
|
|
12
|
+
pyodide_http.patch_all()`;
|
|
13
|
+
export const tools = `
|
|
14
|
+
import importlib.util
|
|
15
|
+
import sys
|
|
16
|
+
from types import ModuleType
|
|
17
|
+
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import List
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
os.environ.setdefault("JUPYTERPACK_BASE_URL", "{{base_url}}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __jupyterpack_import_from_path(module_name: str, path: str) -> ModuleType:
|
|
27
|
+
"""
|
|
28
|
+
Import a Python module from a given file path.
|
|
29
|
+
Always reloads (does not use sys.modules cache).
|
|
30
|
+
"""
|
|
31
|
+
# Remove from sys.modules if already loaded
|
|
32
|
+
if module_name in sys.modules:
|
|
33
|
+
del sys.modules[module_name]
|
|
34
|
+
|
|
35
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
36
|
+
if spec is None or spec.loader is None:
|
|
37
|
+
raise ImportError(f"Cannot import module {module_name} from {path}")
|
|
38
|
+
|
|
39
|
+
module = importlib.util.module_from_spec(spec)
|
|
40
|
+
sys.modules[module_name] = module
|
|
41
|
+
spec.loader.exec_module(module)
|
|
42
|
+
return module
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __jupyterpack_create_mock_module(
|
|
46
|
+
module_names: List[str], mock_content: str, patch_parent=True
|
|
47
|
+
):
|
|
48
|
+
tmpdir = tempfile.TemporaryDirectory()
|
|
49
|
+
package_dir = Path(tmpdir.name) / "__jupyterpack_mock_module"
|
|
50
|
+
package_dir.mkdir()
|
|
51
|
+
(package_dir / "__init__.py").write_text(mock_content)
|
|
52
|
+
|
|
53
|
+
sys.path.insert(0, tmpdir.name)
|
|
54
|
+
mock_module = importlib.import_module("__jupyterpack_mock_module")
|
|
55
|
+
sys.path.pop(0)
|
|
56
|
+
for module_name in module_names:
|
|
57
|
+
if patch_parent:
|
|
58
|
+
parts = module_name.split(".")
|
|
59
|
+
for i in range(1, len(parts) + 1):
|
|
60
|
+
subpath = ".".join(parts[:i])
|
|
61
|
+
sys.modules[subpath] = mock_module
|
|
62
|
+
|
|
63
|
+
for i in range(1, len(parts)):
|
|
64
|
+
parent_name = ".".join(parts[:i])
|
|
65
|
+
child_name = ".".join(parts[: i + 1])
|
|
66
|
+
parent_mod = sys.modules[parent_name]
|
|
67
|
+
child_mod = sys.modules[child_name]
|
|
68
|
+
setattr(parent_mod, parts[i], child_mod)
|
|
69
|
+
else:
|
|
70
|
+
sys.modules[module_name] = mock_module
|
|
71
|
+
|
|
72
|
+
return tmpdir
|
|
73
|
+
`;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { stringOrNone } from '../../tools';
|
|
2
|
+
import { IDict, JupyterPackFramework } from '../../type';
|
|
3
|
+
import { patch } from '../common/generatedPythonFiles';
|
|
4
|
+
import { KernelExecutor } from '../kernelExecutor';
|
|
5
|
+
import { bootstrap, dashLoader } from './generatedPythonFiles';
|
|
6
|
+
|
|
7
|
+
export class DashServer extends KernelExecutor {
|
|
8
|
+
async init(options: {
|
|
9
|
+
initCode?: string;
|
|
10
|
+
instanceId: string;
|
|
11
|
+
kernelClientId: string;
|
|
12
|
+
}) {
|
|
13
|
+
await super.init(options);
|
|
14
|
+
const { initCode, instanceId, kernelClientId } = options;
|
|
15
|
+
|
|
16
|
+
const baseURL = this.buildBaseURL({
|
|
17
|
+
instanceId,
|
|
18
|
+
kernelClientId,
|
|
19
|
+
framework: JupyterPackFramework.DASH
|
|
20
|
+
});
|
|
21
|
+
await this.executeCode({ code: patch });
|
|
22
|
+
await this.executeCode({
|
|
23
|
+
code: bootstrap.replaceAll('{{base_url}}', baseURL)
|
|
24
|
+
});
|
|
25
|
+
if (initCode) {
|
|
26
|
+
await this.executeCode({ code: initCode });
|
|
27
|
+
}
|
|
28
|
+
await this.executeCode({ code: dashLoader });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getResponseFunctionFactory(options: {
|
|
32
|
+
urlPath: string;
|
|
33
|
+
method: string;
|
|
34
|
+
headers: IDict;
|
|
35
|
+
params?: string;
|
|
36
|
+
content?: string;
|
|
37
|
+
}) {
|
|
38
|
+
const { method, urlPath, headers, params, content } = options;
|
|
39
|
+
const code = `${this.DASH_GET_RESPONSE_FUNCTION}("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
|
|
40
|
+
return code;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async disposePythonServer(): Promise<void> {
|
|
44
|
+
//no-op
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async openWebsocket(options: {
|
|
48
|
+
instanceId: string;
|
|
49
|
+
kernelId: string;
|
|
50
|
+
wsUrl: string;
|
|
51
|
+
protocol?: string;
|
|
52
|
+
}): Promise<void> {
|
|
53
|
+
//no-op
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private DASH_GET_RESPONSE_FUNCTION = '__jupyterpack_dash_get_response';
|
|
57
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Auto-generated TypeScript file from Python files
|
|
2
|
+
|
|
3
|
+
export const bootstrap = `
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
os.environ["DASH_URL_BASE_PATHNAME"] = "{{base_url}}"
|
|
7
|
+
`;
|
|
8
|
+
export const dashLoader = `
|
|
9
|
+
import httpx, json, base64
|
|
10
|
+
__jupyterpack_dash_transport = httpx.WSGITransport(app=app.server) # noqa
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __jupyterpack_dash_get_response(method, url, headers, content=None, params=None):
|
|
14
|
+
decoded_content = None
|
|
15
|
+
if content is not None:
|
|
16
|
+
decoded_content = base64.b64decode(content)
|
|
17
|
+
# decoded_content = content.decode()
|
|
18
|
+
with httpx.Client(
|
|
19
|
+
transport=__jupyterpack_dash_transport, base_url="http://testserver"
|
|
20
|
+
) as client:
|
|
21
|
+
r = client.request(
|
|
22
|
+
method, url, headers=headers, content=decoded_content, params=params
|
|
23
|
+
)
|
|
24
|
+
reply_headers = json.dumps(dict(r.headers)).encode("utf-8")
|
|
25
|
+
response = {
|
|
26
|
+
"headers": base64.b64encode(reply_headers).decode("ascii"),
|
|
27
|
+
"content": base64.b64encode(r.content).decode("ascii"),
|
|
28
|
+
"status_code": r.status_code,
|
|
29
|
+
}
|
|
30
|
+
json_str = json.dumps(response)
|
|
31
|
+
return json_str
|
|
32
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IKernelExecutor, JupyterPackFramework } from '../type';
|
|
2
|
+
import { DashServer } from './dash/dashServer';
|
|
3
|
+
import { KernelExecutor } from './kernelExecutor';
|
|
4
|
+
import { StreamlitServer } from './streamlit/streamlitServer';
|
|
5
|
+
import { TornadoServer } from './tornado/tornadoServer';
|
|
6
|
+
|
|
7
|
+
type KernelExecutorConstructor = new (
|
|
8
|
+
options: KernelExecutor.IOptions
|
|
9
|
+
) => IKernelExecutor;
|
|
10
|
+
|
|
11
|
+
export const PYTHON_SERVER = new Map<
|
|
12
|
+
JupyterPackFramework,
|
|
13
|
+
KernelExecutorConstructor
|
|
14
|
+
>([
|
|
15
|
+
[JupyterPackFramework.DASH, DashServer],
|
|
16
|
+
[JupyterPackFramework.STREAMLIT, StreamlitServer],
|
|
17
|
+
[JupyterPackFramework.TORNADO, TornadoServer]
|
|
18
|
+
]);
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
2
|
+
import { KernelMessage, Session } from '@jupyterlab/services';
|
|
3
|
+
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
|
+
import { patch } from './common/generatedPythonFiles';
|
|
13
|
+
|
|
14
|
+
export abstract class KernelExecutor implements IKernelExecutor {
|
|
15
|
+
constructor(options: KernelExecutor.IOptions) {
|
|
16
|
+
this._sessionConnection = options.sessionConnection;
|
|
17
|
+
this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get isDisposed(): boolean {
|
|
21
|
+
return this._isDisposed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
abstract disposePythonServer(): Promise<void>;
|
|
25
|
+
|
|
26
|
+
abstract getResponseFunctionFactory(options: {
|
|
27
|
+
urlPath: string;
|
|
28
|
+
method: string;
|
|
29
|
+
headers: IDict;
|
|
30
|
+
params?: string;
|
|
31
|
+
content?: string;
|
|
32
|
+
}): string;
|
|
33
|
+
|
|
34
|
+
async init(options: {
|
|
35
|
+
entryPath?: string;
|
|
36
|
+
initCode?: string;
|
|
37
|
+
instanceId: string;
|
|
38
|
+
kernelClientId: string;
|
|
39
|
+
}): Promise<void> {
|
|
40
|
+
await this.executeCode({ code: patch });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
openWebsocketFunctionFactory(options: {
|
|
44
|
+
instanceId: string;
|
|
45
|
+
kernelId: string;
|
|
46
|
+
wsUrl: string;
|
|
47
|
+
protocol?: string;
|
|
48
|
+
}): string | undefined {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sendWebsocketMessageFunctionFactory(options: {
|
|
53
|
+
instanceId: string;
|
|
54
|
+
kernelId: string;
|
|
55
|
+
wsUrl: string;
|
|
56
|
+
message: string;
|
|
57
|
+
}): string | undefined {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async openWebsocket(options: {
|
|
62
|
+
instanceId: string;
|
|
63
|
+
kernelId: string;
|
|
64
|
+
wsUrl: string;
|
|
65
|
+
protocol?: string;
|
|
66
|
+
}): Promise<void> {
|
|
67
|
+
const code = this.openWebsocketFunctionFactory(options);
|
|
68
|
+
if (code) {
|
|
69
|
+
await this.executeCode({ code });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async sendWebsocketMessage(options: {
|
|
74
|
+
instanceId: string;
|
|
75
|
+
kernelId: string;
|
|
76
|
+
wsUrl: string;
|
|
77
|
+
message: string;
|
|
78
|
+
}): Promise<void> {
|
|
79
|
+
const code = this.sendWebsocketMessageFunctionFactory(options);
|
|
80
|
+
if (code) {
|
|
81
|
+
await this.executeCode({ code });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getResponse(options: {
|
|
86
|
+
method: string;
|
|
87
|
+
urlPath: string;
|
|
88
|
+
headers: IDict;
|
|
89
|
+
requestBody?: ArrayBuffer;
|
|
90
|
+
params?: string;
|
|
91
|
+
}): Promise<IDict> {
|
|
92
|
+
const { method, urlPath, requestBody, params, headers } = options;
|
|
93
|
+
const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
|
|
94
|
+
const code = this.getResponseFunctionFactory({
|
|
95
|
+
method,
|
|
96
|
+
urlPath,
|
|
97
|
+
headers,
|
|
98
|
+
params,
|
|
99
|
+
content
|
|
100
|
+
});
|
|
101
|
+
const raw = await this.executeCode({ code }, true);
|
|
102
|
+
if (!raw) {
|
|
103
|
+
throw new Error(`Missing response for ${urlPath}`);
|
|
104
|
+
}
|
|
105
|
+
const jsonStr = raw.replaceAll("'", '');
|
|
106
|
+
const obj: {
|
|
107
|
+
headers: string;
|
|
108
|
+
status_code: number;
|
|
109
|
+
content: string;
|
|
110
|
+
} = JSON.parse(jsonStr);
|
|
111
|
+
const responseHeaders: IDict<string> = JSON.parse(atob(obj.headers));
|
|
112
|
+
const contentType: string | undefined =
|
|
113
|
+
responseHeaders?.['Content-Type'] ?? responseHeaders?.['content-type'];
|
|
114
|
+
let responseContent: string | Uint8Array;
|
|
115
|
+
|
|
116
|
+
if (isBinaryContentType(contentType)) {
|
|
117
|
+
responseContent = base64ToArrayBuffer(obj.content);
|
|
118
|
+
} else {
|
|
119
|
+
responseContent = base64ToString(obj.content);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (contentType && contentType.toLowerCase() === 'text/html') {
|
|
123
|
+
responseContent = (responseContent as string).replace(
|
|
124
|
+
'<head>',
|
|
125
|
+
`<head>\n<script>\n${this._wsPatch}\n</script>\n`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const decodedObj = {
|
|
130
|
+
status_code: obj.status_code,
|
|
131
|
+
headers: responseHeaders,
|
|
132
|
+
content: responseContent
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return decodedObj;
|
|
136
|
+
}
|
|
137
|
+
async executeCode(
|
|
138
|
+
code: KernelMessage.IExecuteRequestMsg['content'],
|
|
139
|
+
waitForResult?: boolean
|
|
140
|
+
): Promise<string | null> {
|
|
141
|
+
const kernel = this._sessionConnection?.kernel;
|
|
142
|
+
if (!kernel) {
|
|
143
|
+
throw new Error('Session has no kernel.');
|
|
144
|
+
}
|
|
145
|
+
return new Promise<string | null>((resolve, reject) => {
|
|
146
|
+
const future = kernel.requestExecute(code, false, undefined);
|
|
147
|
+
let executeResult = '';
|
|
148
|
+
future.onIOPub = (msg: KernelMessage.IIOPubMessage): void => {
|
|
149
|
+
const msgType = msg.header.msg_type;
|
|
150
|
+
|
|
151
|
+
switch (msgType) {
|
|
152
|
+
case 'execute_result': {
|
|
153
|
+
if (waitForResult) {
|
|
154
|
+
const content = (msg as KernelMessage.IExecuteResultMsg).content
|
|
155
|
+
.data['text/plain'] as string;
|
|
156
|
+
executeResult += content;
|
|
157
|
+
resolve(executeResult);
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'stream': {
|
|
162
|
+
const content = (msg as KernelMessage.IStreamMsg).content;
|
|
163
|
+
if (content.name === 'stderr') {
|
|
164
|
+
console.error('Kernel stream', content.text);
|
|
165
|
+
} else {
|
|
166
|
+
console.log('Kernel stream', content.text);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'error': {
|
|
171
|
+
console.error(
|
|
172
|
+
'Kernel operation failed',
|
|
173
|
+
code.code,
|
|
174
|
+
(msg.content as any).traceback
|
|
175
|
+
.map((it: string) => stripAnsi(it))
|
|
176
|
+
.join('\n')
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
reject(msg.content);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
default:
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
if (!waitForResult) {
|
|
187
|
+
resolve(null);
|
|
188
|
+
// future.dispose() # TODO
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
dispose(): void {
|
|
194
|
+
if (this._isDisposed) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this._isDisposed = true;
|
|
198
|
+
this._sessionConnection.dispose();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected buildBaseURL(options: {
|
|
202
|
+
instanceId: string;
|
|
203
|
+
kernelClientId: string;
|
|
204
|
+
framework: JupyterPackFramework;
|
|
205
|
+
}) {
|
|
206
|
+
const { instanceId, kernelClientId, framework } = options;
|
|
207
|
+
const labBaseUrl = PageConfig.getOption('baseUrl');
|
|
208
|
+
const baseURL = URLExt.join(
|
|
209
|
+
labBaseUrl,
|
|
210
|
+
'extensions/jupyterpack/static',
|
|
211
|
+
instanceId,
|
|
212
|
+
framework,
|
|
213
|
+
kernelClientId,
|
|
214
|
+
'/'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return baseURL;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private _isDisposed: boolean = false;
|
|
221
|
+
private _sessionConnection: Session.ISessionConnection;
|
|
222
|
+
private _wsPatch: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export namespace KernelExecutor {
|
|
226
|
+
export interface IOptions {
|
|
227
|
+
sessionConnection: Session.ISessionConnection;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Auto-generated TypeScript file from Python files
|
|
2
|
+
|
|
3
|
+
export const bootstrap = `
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
import streamlit.watcher.path_watcher
|
|
8
|
+
import contextlib
|
|
9
|
+
import streamlit.elements.spinner
|
|
10
|
+
|
|
11
|
+
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MockedThread(threading.Thread):
|
|
15
|
+
def start(self):
|
|
16
|
+
threading.current_thread = lambda: self
|
|
17
|
+
try:
|
|
18
|
+
self.run()
|
|
19
|
+
except Exception as e:
|
|
20
|
+
raise e
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
threading.Thread = MockedThread
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WatcherMock:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
path,
|
|
30
|
+
callback,
|
|
31
|
+
glob_pattern: str | None = None,
|
|
32
|
+
allow_nonexistent: bool = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def close(self) -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
streamlit.watcher.path_watcher.watchdog_available = False
|
|
41
|
+
streamlit.watcher.path_watcher.EventBasedPathWatcher = WatcherMock
|
|
42
|
+
streamlit.watcher.path_watcher._is_watchdog_available = lambda: False
|
|
43
|
+
streamlit.watcher.path_watcher.get_path_watcher_class = lambda x: WatcherMock
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MockThreading:
|
|
47
|
+
class Timer:
|
|
48
|
+
def __init__(self, delay, cb):
|
|
49
|
+
cb()
|
|
50
|
+
|
|
51
|
+
def start(self):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
Lock = contextlib.nullcontext
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
streamlit.elements.spinner.threading = MockThreading
|
|
58
|
+
`;
|
|
59
|
+
export const streamlitLoader = `
|
|
60
|
+
import json
|
|
61
|
+
from streamlit import config
|
|
62
|
+
import streamlit.web.server.server as st_server
|
|
63
|
+
from streamlit.runtime.runtime import Runtime
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Check if __jupyterpack_streamlit_instance defined from previous run
|
|
67
|
+
__jupyterpack_streamlit_instance
|
|
68
|
+
except NameError:
|
|
69
|
+
__jupyterpack_streamlit_instance = {
|
|
70
|
+
"tornado_bridge": None,
|
|
71
|
+
"streamlit_server": None,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def __jupyterpack_create_streamlit_app(base_url, script_path):
|
|
76
|
+
if Runtime._instance is not None:
|
|
77
|
+
Runtime._instance.stop()
|
|
78
|
+
Runtime._instance = None
|
|
79
|
+
config.set_option("server.baseUrlPath", base_url)
|
|
80
|
+
|
|
81
|
+
config.set_option("server.port", 6789)
|
|
82
|
+
config.set_option("server.enableCORS", False)
|
|
83
|
+
config.set_option("server.enableXsrfProtection", False)
|
|
84
|
+
|
|
85
|
+
streamlit_server = st_server.Server(script_path, True)
|
|
86
|
+
return streamlit_server
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def __jupyterpack_streamlit_dispose():
|
|
90
|
+
global __jupyterpack_streamlit_instance
|
|
91
|
+
streamlit_server = __jupyterpack_streamlit_instance.get("streamlit_server", None)
|
|
92
|
+
if streamlit_server:
|
|
93
|
+
streamlit_server._runtime.stop()
|
|
94
|
+
|
|
95
|
+
__jupyterpack_streamlit_instance = {
|
|
96
|
+
"tornado_bridge": None,
|
|
97
|
+
"streamlit_server": None,
|
|
98
|
+
}
|
|
99
|
+
del streamlit_server
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def __jupyterpack_streamlit_open_ws(
|
|
103
|
+
instance_id: str, kernel_client_id: str, ws_url: str, protocols_str: str | None
|
|
104
|
+
):
|
|
105
|
+
tornado_bridge = __jupyterpack_streamlit_instance["tornado_bridge"]
|
|
106
|
+
if tornado_bridge is None:
|
|
107
|
+
raise Exception("Missing tornado instance")
|
|
108
|
+
await tornado_bridge.open_ws(instance_id, kernel_client_id, ws_url, protocols_str)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def __jupyterpack_streamlit_receive_ws_message(
|
|
112
|
+
instance_id: str, kernel_client_id: str, ws_url: str, payload_message: str
|
|
113
|
+
):
|
|
114
|
+
tornado_bridge = __jupyterpack_streamlit_instance["tornado_bridge"]
|
|
115
|
+
if tornado_bridge is None:
|
|
116
|
+
raise Exception("Missing tornado instance")
|
|
117
|
+
await tornado_bridge.receive_ws_message_from_js(
|
|
118
|
+
instance_id, kernel_client_id, ws_url, payload_message
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def __jupyterpack_streamlit_get_response(
|
|
123
|
+
method, url, headers, content=None, params=None
|
|
124
|
+
):
|
|
125
|
+
global __jupyterpack_streamlit_instance
|
|
126
|
+
if not __jupyterpack_streamlit_instance["streamlit_server"]:
|
|
127
|
+
streamlit_server = __jupyterpack_create_streamlit_app(
|
|
128
|
+
"{{base_url}}", "{{script_path}}"
|
|
129
|
+
) # noqa
|
|
130
|
+
app = streamlit_server._create_app()
|
|
131
|
+
await streamlit_server._runtime.start()
|
|
132
|
+
__jupyterpack_streamlit_instance["streamlit_server"] = streamlit_server
|
|
133
|
+
__jupyterpack_streamlit_instance["tornado_bridge"] = TornadoBridge(
|
|
134
|
+
app, "{{base_url}}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
tornado_bridge = __jupyterpack_streamlit_instance["tornado_bridge"]
|
|
138
|
+
req_dict = {
|
|
139
|
+
"method": method,
|
|
140
|
+
"url": url,
|
|
141
|
+
"headers": list(headers.items()),
|
|
142
|
+
"body": content,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
response = await tornado_bridge.fetch(req_dict)
|
|
146
|
+
json_str = json.dumps(response)
|
|
147
|
+
return json_str
|
|
148
|
+
`;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { stringOrNone } from '../../tools';
|
|
2
|
+
import { IDict, JupyterPackFramework } from '../../type';
|
|
3
|
+
import { patch, tools } from '../common/generatedPythonFiles';
|
|
4
|
+
import { KernelExecutor } from '../kernelExecutor';
|
|
5
|
+
import {
|
|
6
|
+
bootstrap as tornadoBootstrap,
|
|
7
|
+
tornadoBridge
|
|
8
|
+
} from '../tornado/generatedPythonFiles';
|
|
9
|
+
import { bootstrap, streamlitLoader } from './generatedPythonFiles';
|
|
10
|
+
|
|
11
|
+
export class StreamlitServer extends KernelExecutor {
|
|
12
|
+
async init(options: {
|
|
13
|
+
entryPath?: string;
|
|
14
|
+
initCode?: string;
|
|
15
|
+
instanceId: string;
|
|
16
|
+
kernelClientId: string;
|
|
17
|
+
}) {
|
|
18
|
+
await super.init(options);
|
|
19
|
+
const { instanceId, kernelClientId, entryPath } = options;
|
|
20
|
+
if (!entryPath) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'Missing streamlit entry path, please check your SPK file'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const baseURL = this.buildBaseURL({
|
|
26
|
+
instanceId,
|
|
27
|
+
kernelClientId,
|
|
28
|
+
framework: JupyterPackFramework.STREAMLIT
|
|
29
|
+
});
|
|
30
|
+
await this.executeCode({ code: patch });
|
|
31
|
+
await this.executeCode({ code: tools.replaceAll('{{base_url}}', baseURL) });
|
|
32
|
+
await this.executeCode({ code: tornadoBootstrap });
|
|
33
|
+
await this.executeCode({ code: tornadoBridge });
|
|
34
|
+
await this.executeCode({ code: bootstrap });
|
|
35
|
+
|
|
36
|
+
const stCode = streamlitLoader
|
|
37
|
+
.replaceAll('{{base_url}}', baseURL)
|
|
38
|
+
.replaceAll('{{script_path}}', entryPath);
|
|
39
|
+
await this.executeCode({ code: stCode });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getResponseFunctionFactory(options: {
|
|
43
|
+
urlPath: string;
|
|
44
|
+
method: string;
|
|
45
|
+
headers: IDict;
|
|
46
|
+
params?: string;
|
|
47
|
+
content?: string;
|
|
48
|
+
}) {
|
|
49
|
+
const { method, urlPath, headers, params, content } = options;
|
|
50
|
+
const code = `await ${this._GET_RESPONSE_FUNCTION}("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
|
|
51
|
+
return code;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
openWebsocketFunctionFactory(options: {
|
|
55
|
+
instanceId: string;
|
|
56
|
+
kernelId: string;
|
|
57
|
+
wsUrl: string;
|
|
58
|
+
protocol?: string;
|
|
59
|
+
}): string {
|
|
60
|
+
const { instanceId, kernelId, wsUrl, protocol } = options;
|
|
61
|
+
|
|
62
|
+
const code = `await ${this._OPEN_WEBSOCKET_FUNCTION}("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
|
|
63
|
+
return code;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
sendWebsocketMessageFunctionFactory(options: {
|
|
67
|
+
instanceId: string;
|
|
68
|
+
kernelId: string;
|
|
69
|
+
wsUrl: string;
|
|
70
|
+
message: string;
|
|
71
|
+
}): string {
|
|
72
|
+
const { instanceId, kernelId, wsUrl, message } = options;
|
|
73
|
+
const code = `await ${this._SEND_WEBSOCKET_FUNCTION}("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
|
|
74
|
+
return code;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async disposePythonServer(): Promise<void> {
|
|
78
|
+
await this.executeCode({
|
|
79
|
+
code: '__jupyterpack_streamlit_dispose()'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private _GET_RESPONSE_FUNCTION = '__jupyterpack_streamlit_get_response';
|
|
84
|
+
private _OPEN_WEBSOCKET_FUNCTION = '__jupyterpack_streamlit_open_ws';
|
|
85
|
+
private _SEND_WEBSOCKET_FUNCTION =
|
|
86
|
+
'__jupyterpack_streamlit_receive_ws_message';
|
|
87
|
+
}
|