jupyterpack 0.2.1 → 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.
Files changed (66) hide show
  1. package/README.md +2 -0
  2. package/lib/document/widgetFactory.js +4 -1
  3. package/lib/pythonServer/common/generatedPythonFiles.d.ts +2 -0
  4. package/lib/pythonServer/common/generatedPythonFiles.js +72 -0
  5. package/lib/pythonServer/dash/dashServer.d.ts +24 -0
  6. package/lib/pythonServer/dash/dashServer.js +39 -0
  7. package/lib/pythonServer/dash/generatedPythonFiles.d.ts +2 -0
  8. package/lib/pythonServer/dash/generatedPythonFiles.js +31 -0
  9. package/lib/pythonServer/index.d.ts +5 -0
  10. package/lib/pythonServer/index.js +9 -0
  11. package/lib/pythonServer/kernelExecutor.d.ts +66 -0
  12. package/lib/pythonServer/kernelExecutor.js +133 -0
  13. package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +2 -0
  14. package/lib/pythonServer/streamlit/generatedPythonFiles.js +147 -0
  15. package/lib/pythonServer/streamlit/streamlitServer.d.ts +33 -0
  16. package/lib/pythonServer/streamlit/streamlitServer.js +55 -0
  17. package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +3 -0
  18. package/lib/pythonServer/tornado/generatedPythonFiles.js +456 -0
  19. package/lib/pythonServer/tornado/tornadoServer.d.ts +32 -0
  20. package/lib/pythonServer/tornado/tornadoServer.js +51 -0
  21. package/lib/pythonWidget/pythonWidget.d.ts +1 -0
  22. package/lib/pythonWidget/pythonWidget.js +9 -3
  23. package/lib/pythonWidget/pythonWidgetModel.d.ts +12 -3
  24. package/lib/pythonWidget/pythonWidgetModel.js +32 -10
  25. package/lib/swConnection/index.js +2 -2
  26. package/lib/{pythonWidget/connectionManager.d.ts → swConnection/mainConnectionManager.d.ts} +10 -0
  27. package/lib/swConnection/mainConnectionManager.js +93 -0
  28. package/lib/swConnection/sw.js +1 -1
  29. package/lib/swConnection/swCommManager.d.ts +11 -0
  30. package/lib/swConnection/{comm_manager.js → swCommManager.js} +5 -0
  31. package/lib/tools.d.ts +4 -0
  32. package/lib/tools.js +58 -0
  33. package/lib/type.d.ts +37 -2
  34. package/lib/type.js +2 -0
  35. package/lib/websocket/websocket.d.ts +0 -0
  36. package/lib/websocket/websocket.js +152 -0
  37. package/package.json +8 -5
  38. package/src/document/widgetFactory.ts +4 -1
  39. package/src/global.d.ts +4 -0
  40. package/src/pythonServer/common/generatedPythonFiles.ts +73 -0
  41. package/src/pythonServer/dash/dashServer.ts +57 -0
  42. package/src/pythonServer/dash/generatedPythonFiles.ts +32 -0
  43. package/src/pythonServer/index.ts +18 -0
  44. package/src/pythonServer/kernelExecutor.ts +229 -0
  45. package/src/pythonServer/streamlit/generatedPythonFiles.ts +148 -0
  46. package/src/pythonServer/streamlit/streamlitServer.ts +87 -0
  47. package/src/pythonServer/tornado/generatedPythonFiles.ts +457 -0
  48. package/src/pythonServer/tornado/tornadoServer.ts +80 -0
  49. package/src/pythonWidget/pythonWidget.ts +20 -3
  50. package/src/pythonWidget/pythonWidgetModel.ts +53 -19
  51. package/src/swConnection/index.ts +5 -2
  52. package/src/swConnection/mainConnectionManager.ts +121 -0
  53. package/src/swConnection/sw.ts +1 -1
  54. package/src/swConnection/{comm_manager.ts → swCommManager.ts} +6 -0
  55. package/src/tools.ts +69 -0
  56. package/src/type.ts +47 -3
  57. package/src/websocket/websocket.ts +216 -0
  58. package/lib/pythonWidget/connectionManager.js +0 -27
  59. package/lib/pythonWidget/kernelExecutor.d.ts +0 -27
  60. package/lib/pythonWidget/kernelExecutor.js +0 -104
  61. package/lib/swConnection/comm_manager.d.ts +0 -6
  62. package/lib/swConnection/connection_manager.d.ts +0 -18
  63. package/lib/swConnection/connection_manager.js +0 -27
  64. package/src/pythonWidget/connectionManager.ts +0 -43
  65. package/src/pythonWidget/kernelExecutor.ts +0 -140
  66. package/src/swConnection/connection_manager.ts +0 -43
package/README.md CHANGED
@@ -10,6 +10,8 @@
10
10
  - **Python Web Apps**: Serve Python web applications directly in the browser using JupyterLite's in-browser Python kernel. `jupyterpack` currently supports Dash.
11
11
  - **JavaScript Web Apps**: Bundle and serve JavaScript web applications using in-browser bundlers.
12
12
 
13
+ ![Image](https://github.com/user-attachments/assets/22849fe8-199f-4d9f-ad45-055bccf88bad)
14
+
13
15
  ## Installation
14
16
 
15
17
  You can install `jupyterpack` using `pip` or `conda`
@@ -32,8 +32,11 @@ export class JupyterPackWidgetFactory extends ABCWidgetFactory {
32
32
  content.addWidget(jpContent);
33
33
  break;
34
34
  }
35
- case JupyterPackFramework.DASH: {
35
+ case JupyterPackFramework.DASH:
36
+ case JupyterPackFramework.STREAMLIT:
37
+ case JupyterPackFramework.TORNADO: {
36
38
  const model = new PythonWidgetModel({
39
+ jpackModel,
37
40
  context,
38
41
  manager: this.options.manager,
39
42
  contentsManager: this._contentsManager,
@@ -0,0 +1,2 @@
1
+ export declare const patch = "\nimport pyodide_http\nimport collections\n\nif not hasattr(collections, \"MutableSet\"):\n import collections.abc\n\n collections.MutableSet = collections.abc.MutableSet\n\npyodide_http.patch_all()";
2
+ export declare const tools = "\nimport importlib.util\nimport sys\nfrom types import ModuleType\n\nimport tempfile\nfrom pathlib import Path\nfrom typing import List\nimport os\n\nos.environ.setdefault(\"JUPYTERPACK_BASE_URL\", \"{{base_url}}\")\n\n\ndef __jupyterpack_import_from_path(module_name: str, path: str) -> ModuleType:\n \"\"\"\n Import a Python module from a given file path.\n Always reloads (does not use sys.modules cache).\n \"\"\"\n # Remove from sys.modules if already loaded\n if module_name in sys.modules:\n del sys.modules[module_name]\n\n spec = importlib.util.spec_from_file_location(module_name, path)\n if spec is None or spec.loader is None:\n raise ImportError(f\"Cannot import module {module_name} from {path}\")\n\n module = importlib.util.module_from_spec(spec)\n sys.modules[module_name] = module\n spec.loader.exec_module(module)\n return module\n\n\ndef __jupyterpack_create_mock_module(\n module_names: List[str], mock_content: str, patch_parent=True\n):\n tmpdir = tempfile.TemporaryDirectory()\n package_dir = Path(tmpdir.name) / \"__jupyterpack_mock_module\"\n package_dir.mkdir()\n (package_dir / \"__init__.py\").write_text(mock_content)\n\n sys.path.insert(0, tmpdir.name)\n mock_module = importlib.import_module(\"__jupyterpack_mock_module\")\n sys.path.pop(0)\n for module_name in module_names:\n if patch_parent:\n parts = module_name.split(\".\")\n for i in range(1, len(parts) + 1):\n subpath = \".\".join(parts[:i])\n sys.modules[subpath] = mock_module\n\n for i in range(1, len(parts)):\n parent_name = \".\".join(parts[:i])\n child_name = \".\".join(parts[: i + 1])\n parent_mod = sys.modules[parent_name]\n child_mod = sys.modules[child_name]\n setattr(parent_mod, parts[i], child_mod)\n else:\n sys.modules[module_name] = mock_module\n\n return tmpdir\n";
@@ -0,0 +1,72 @@
1
+ // Auto-generated TypeScript file from Python files
2
+ export const patch = `
3
+ import pyodide_http
4
+ import collections
5
+
6
+ if not hasattr(collections, "MutableSet"):
7
+ import collections.abc
8
+
9
+ collections.MutableSet = collections.abc.MutableSet
10
+
11
+ pyodide_http.patch_all()`;
12
+ export const tools = `
13
+ import importlib.util
14
+ import sys
15
+ from types import ModuleType
16
+
17
+ import tempfile
18
+ from pathlib import Path
19
+ from typing import List
20
+ import os
21
+
22
+ os.environ.setdefault("JUPYTERPACK_BASE_URL", "{{base_url}}")
23
+
24
+
25
+ def __jupyterpack_import_from_path(module_name: str, path: str) -> ModuleType:
26
+ """
27
+ Import a Python module from a given file path.
28
+ Always reloads (does not use sys.modules cache).
29
+ """
30
+ # Remove from sys.modules if already loaded
31
+ if module_name in sys.modules:
32
+ del sys.modules[module_name]
33
+
34
+ spec = importlib.util.spec_from_file_location(module_name, path)
35
+ if spec is None or spec.loader is None:
36
+ raise ImportError(f"Cannot import module {module_name} from {path}")
37
+
38
+ module = importlib.util.module_from_spec(spec)
39
+ sys.modules[module_name] = module
40
+ spec.loader.exec_module(module)
41
+ return module
42
+
43
+
44
+ def __jupyterpack_create_mock_module(
45
+ module_names: List[str], mock_content: str, patch_parent=True
46
+ ):
47
+ tmpdir = tempfile.TemporaryDirectory()
48
+ package_dir = Path(tmpdir.name) / "__jupyterpack_mock_module"
49
+ package_dir.mkdir()
50
+ (package_dir / "__init__.py").write_text(mock_content)
51
+
52
+ sys.path.insert(0, tmpdir.name)
53
+ mock_module = importlib.import_module("__jupyterpack_mock_module")
54
+ sys.path.pop(0)
55
+ for module_name in module_names:
56
+ if patch_parent:
57
+ parts = module_name.split(".")
58
+ for i in range(1, len(parts) + 1):
59
+ subpath = ".".join(parts[:i])
60
+ sys.modules[subpath] = mock_module
61
+
62
+ for i in range(1, len(parts)):
63
+ parent_name = ".".join(parts[:i])
64
+ child_name = ".".join(parts[: i + 1])
65
+ parent_mod = sys.modules[parent_name]
66
+ child_mod = sys.modules[child_name]
67
+ setattr(parent_mod, parts[i], child_mod)
68
+ else:
69
+ sys.modules[module_name] = mock_module
70
+
71
+ return tmpdir
72
+ `;
@@ -0,0 +1,24 @@
1
+ import { IDict } from '../../type';
2
+ import { KernelExecutor } from '../kernelExecutor';
3
+ export declare class DashServer extends KernelExecutor {
4
+ init(options: {
5
+ initCode?: string;
6
+ instanceId: string;
7
+ kernelClientId: string;
8
+ }): Promise<void>;
9
+ getResponseFunctionFactory(options: {
10
+ urlPath: string;
11
+ method: string;
12
+ headers: IDict;
13
+ params?: string;
14
+ content?: string;
15
+ }): string;
16
+ disposePythonServer(): Promise<void>;
17
+ openWebsocket(options: {
18
+ instanceId: string;
19
+ kernelId: string;
20
+ wsUrl: string;
21
+ protocol?: string;
22
+ }): Promise<void>;
23
+ private DASH_GET_RESPONSE_FUNCTION;
24
+ }
@@ -0,0 +1,39 @@
1
+ import { stringOrNone } from '../../tools';
2
+ import { JupyterPackFramework } from '../../type';
3
+ import { patch } from '../common/generatedPythonFiles';
4
+ import { KernelExecutor } from '../kernelExecutor';
5
+ import { bootstrap, dashLoader } from './generatedPythonFiles';
6
+ export class DashServer extends KernelExecutor {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.DASH_GET_RESPONSE_FUNCTION = '__jupyterpack_dash_get_response';
10
+ }
11
+ async init(options) {
12
+ await super.init(options);
13
+ const { initCode, instanceId, kernelClientId } = options;
14
+ const baseURL = this.buildBaseURL({
15
+ instanceId,
16
+ kernelClientId,
17
+ framework: JupyterPackFramework.DASH
18
+ });
19
+ await this.executeCode({ code: patch });
20
+ await this.executeCode({
21
+ code: bootstrap.replaceAll('{{base_url}}', baseURL)
22
+ });
23
+ if (initCode) {
24
+ await this.executeCode({ code: initCode });
25
+ }
26
+ await this.executeCode({ code: dashLoader });
27
+ }
28
+ getResponseFunctionFactory(options) {
29
+ const { method, urlPath, headers, params, content } = options;
30
+ const code = `${this.DASH_GET_RESPONSE_FUNCTION}("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
31
+ return code;
32
+ }
33
+ async disposePythonServer() {
34
+ //no-op
35
+ }
36
+ async openWebsocket(options) {
37
+ //no-op
38
+ }
39
+ }
@@ -0,0 +1,2 @@
1
+ export declare const bootstrap = "\nimport os\n\nos.environ[\"DASH_URL_BASE_PATHNAME\"] = \"{{base_url}}\"\n";
2
+ export declare const dashLoader = "\nimport httpx, json, base64\n__jupyterpack_dash_transport = httpx.WSGITransport(app=app.server) # noqa\n\n\ndef __jupyterpack_dash_get_response(method, url, headers, content=None, params=None):\n decoded_content = None\n if content is not None:\n decoded_content = base64.b64decode(content)\n # decoded_content = content.decode()\n with httpx.Client(\n transport=__jupyterpack_dash_transport, base_url=\"http://testserver\"\n ) as client:\n r = client.request(\n method, url, headers=headers, content=decoded_content, params=params\n )\n reply_headers = json.dumps(dict(r.headers)).encode(\"utf-8\")\n response = {\n \"headers\": base64.b64encode(reply_headers).decode(\"ascii\"),\n \"content\": base64.b64encode(r.content).decode(\"ascii\"),\n \"status_code\": r.status_code,\n }\n json_str = json.dumps(response)\n return json_str\n";
@@ -0,0 +1,31 @@
1
+ // Auto-generated TypeScript file from Python files
2
+ export const bootstrap = `
3
+ import os
4
+
5
+ os.environ["DASH_URL_BASE_PATHNAME"] = "{{base_url}}"
6
+ `;
7
+ export const dashLoader = `
8
+ import httpx, json, base64
9
+ __jupyterpack_dash_transport = httpx.WSGITransport(app=app.server) # noqa
10
+
11
+
12
+ def __jupyterpack_dash_get_response(method, url, headers, content=None, params=None):
13
+ decoded_content = None
14
+ if content is not None:
15
+ decoded_content = base64.b64decode(content)
16
+ # decoded_content = content.decode()
17
+ with httpx.Client(
18
+ transport=__jupyterpack_dash_transport, base_url="http://testserver"
19
+ ) as client:
20
+ r = client.request(
21
+ method, url, headers=headers, content=decoded_content, params=params
22
+ )
23
+ reply_headers = json.dumps(dict(r.headers)).encode("utf-8")
24
+ response = {
25
+ "headers": base64.b64encode(reply_headers).decode("ascii"),
26
+ "content": base64.b64encode(r.content).decode("ascii"),
27
+ "status_code": r.status_code,
28
+ }
29
+ json_str = json.dumps(response)
30
+ return json_str
31
+ `;
@@ -0,0 +1,5 @@
1
+ import { IKernelExecutor, JupyterPackFramework } from '../type';
2
+ import { KernelExecutor } from './kernelExecutor';
3
+ type KernelExecutorConstructor = new (options: KernelExecutor.IOptions) => IKernelExecutor;
4
+ export declare const PYTHON_SERVER: Map<JupyterPackFramework, KernelExecutorConstructor>;
5
+ export {};
@@ -0,0 +1,9 @@
1
+ import { JupyterPackFramework } from '../type';
2
+ import { DashServer } from './dash/dashServer';
3
+ import { StreamlitServer } from './streamlit/streamlitServer';
4
+ import { TornadoServer } from './tornado/tornadoServer';
5
+ export const PYTHON_SERVER = new Map([
6
+ [JupyterPackFramework.DASH, DashServer],
7
+ [JupyterPackFramework.STREAMLIT, StreamlitServer],
8
+ [JupyterPackFramework.TORNADO, TornadoServer]
9
+ ]);
@@ -0,0 +1,66 @@
1
+ import { KernelMessage, Session } from '@jupyterlab/services';
2
+ import { IDict, IKernelExecutor, JupyterPackFramework } from '../type';
3
+ export declare abstract class KernelExecutor implements IKernelExecutor {
4
+ constructor(options: KernelExecutor.IOptions);
5
+ get isDisposed(): boolean;
6
+ abstract disposePythonServer(): Promise<void>;
7
+ abstract getResponseFunctionFactory(options: {
8
+ urlPath: string;
9
+ method: string;
10
+ headers: IDict;
11
+ params?: string;
12
+ content?: string;
13
+ }): string;
14
+ init(options: {
15
+ entryPath?: string;
16
+ initCode?: string;
17
+ instanceId: string;
18
+ kernelClientId: string;
19
+ }): Promise<void>;
20
+ openWebsocketFunctionFactory(options: {
21
+ instanceId: string;
22
+ kernelId: string;
23
+ wsUrl: string;
24
+ protocol?: string;
25
+ }): string | undefined;
26
+ sendWebsocketMessageFunctionFactory(options: {
27
+ instanceId: string;
28
+ kernelId: string;
29
+ wsUrl: string;
30
+ message: string;
31
+ }): string | undefined;
32
+ openWebsocket(options: {
33
+ instanceId: string;
34
+ kernelId: string;
35
+ wsUrl: string;
36
+ protocol?: string;
37
+ }): Promise<void>;
38
+ sendWebsocketMessage(options: {
39
+ instanceId: string;
40
+ kernelId: string;
41
+ wsUrl: string;
42
+ message: string;
43
+ }): Promise<void>;
44
+ getResponse(options: {
45
+ method: string;
46
+ urlPath: string;
47
+ headers: IDict;
48
+ requestBody?: ArrayBuffer;
49
+ params?: string;
50
+ }): Promise<IDict>;
51
+ executeCode(code: KernelMessage.IExecuteRequestMsg['content'], waitForResult?: boolean): Promise<string | null>;
52
+ dispose(): void;
53
+ protected buildBaseURL(options: {
54
+ instanceId: string;
55
+ kernelClientId: string;
56
+ framework: JupyterPackFramework;
57
+ }): string;
58
+ private _isDisposed;
59
+ private _sessionConnection;
60
+ private _wsPatch;
61
+ }
62
+ export declare namespace KernelExecutor {
63
+ interface IOptions {
64
+ sessionConnection: Session.ISessionConnection;
65
+ }
66
+ }
@@ -0,0 +1,133 @@
1
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
+ import stripAnsi from 'strip-ansi';
3
+ import { arrayBufferToBase64, base64ToArrayBuffer, base64ToString, isBinaryContentType } from '../tools';
4
+ import websocketPatch from '../websocket/websocket.js?raw';
5
+ import { patch } from './common/generatedPythonFiles';
6
+ export class KernelExecutor {
7
+ constructor(options) {
8
+ this._isDisposed = false;
9
+ this._sessionConnection = options.sessionConnection;
10
+ this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
11
+ }
12
+ get isDisposed() {
13
+ return this._isDisposed;
14
+ }
15
+ async init(options) {
16
+ await this.executeCode({ code: patch });
17
+ }
18
+ openWebsocketFunctionFactory(options) {
19
+ return undefined;
20
+ }
21
+ sendWebsocketMessageFunctionFactory(options) {
22
+ return undefined;
23
+ }
24
+ async openWebsocket(options) {
25
+ const code = this.openWebsocketFunctionFactory(options);
26
+ if (code) {
27
+ await this.executeCode({ code });
28
+ }
29
+ }
30
+ async sendWebsocketMessage(options) {
31
+ const code = this.sendWebsocketMessageFunctionFactory(options);
32
+ if (code) {
33
+ await this.executeCode({ code });
34
+ }
35
+ }
36
+ async getResponse(options) {
37
+ var _a;
38
+ const { method, urlPath, requestBody, params, headers } = options;
39
+ const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
40
+ const code = this.getResponseFunctionFactory({
41
+ method,
42
+ urlPath,
43
+ headers,
44
+ params,
45
+ content
46
+ });
47
+ const raw = await this.executeCode({ code }, true);
48
+ if (!raw) {
49
+ throw new Error(`Missing response for ${urlPath}`);
50
+ }
51
+ const jsonStr = raw.replaceAll("'", '');
52
+ const obj = JSON.parse(jsonStr);
53
+ const responseHeaders = JSON.parse(atob(obj.headers));
54
+ const contentType = (_a = responseHeaders === null || responseHeaders === void 0 ? void 0 : responseHeaders['Content-Type']) !== null && _a !== void 0 ? _a : responseHeaders === null || responseHeaders === void 0 ? void 0 : responseHeaders['content-type'];
55
+ let responseContent;
56
+ if (isBinaryContentType(contentType)) {
57
+ responseContent = base64ToArrayBuffer(obj.content);
58
+ }
59
+ else {
60
+ responseContent = base64ToString(obj.content);
61
+ }
62
+ if (contentType && contentType.toLowerCase() === 'text/html') {
63
+ responseContent = responseContent.replace('<head>', `<head>\n<script>\n${this._wsPatch}\n</script>\n`);
64
+ }
65
+ const decodedObj = {
66
+ status_code: obj.status_code,
67
+ headers: responseHeaders,
68
+ content: responseContent
69
+ };
70
+ return decodedObj;
71
+ }
72
+ async executeCode(code, waitForResult) {
73
+ var _a;
74
+ const kernel = (_a = this._sessionConnection) === null || _a === void 0 ? void 0 : _a.kernel;
75
+ if (!kernel) {
76
+ throw new Error('Session has no kernel.');
77
+ }
78
+ return new Promise((resolve, reject) => {
79
+ const future = kernel.requestExecute(code, false, undefined);
80
+ let executeResult = '';
81
+ future.onIOPub = (msg) => {
82
+ const msgType = msg.header.msg_type;
83
+ switch (msgType) {
84
+ case 'execute_result': {
85
+ if (waitForResult) {
86
+ const content = msg.content
87
+ .data['text/plain'];
88
+ executeResult += content;
89
+ resolve(executeResult);
90
+ }
91
+ break;
92
+ }
93
+ case 'stream': {
94
+ const content = msg.content;
95
+ if (content.name === 'stderr') {
96
+ console.error('Kernel stream', content.text);
97
+ }
98
+ else {
99
+ console.log('Kernel stream', content.text);
100
+ }
101
+ break;
102
+ }
103
+ case 'error': {
104
+ console.error('Kernel operation failed', code.code, msg.content.traceback
105
+ .map((it) => stripAnsi(it))
106
+ .join('\n'));
107
+ reject(msg.content);
108
+ break;
109
+ }
110
+ default:
111
+ break;
112
+ }
113
+ };
114
+ if (!waitForResult) {
115
+ resolve(null);
116
+ // future.dispose() # TODO
117
+ }
118
+ });
119
+ }
120
+ dispose() {
121
+ if (this._isDisposed) {
122
+ return;
123
+ }
124
+ this._isDisposed = true;
125
+ this._sessionConnection.dispose();
126
+ }
127
+ buildBaseURL(options) {
128
+ const { instanceId, kernelClientId, framework } = options;
129
+ const labBaseUrl = PageConfig.getOption('baseUrl');
130
+ const baseURL = URLExt.join(labBaseUrl, 'extensions/jupyterpack/static', instanceId, framework, kernelClientId, '/');
131
+ return baseURL;
132
+ }
133
+ }
@@ -0,0 +1,2 @@
1
+ export declare const bootstrap = "\nimport os\n\nimport threading\nimport streamlit.watcher.path_watcher\nimport contextlib\nimport streamlit.elements.spinner\n\nos.environ[\"PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION\"] = \"python\"\n\n\nclass MockedThread(threading.Thread):\n def start(self):\n threading.current_thread = lambda: self\n try:\n self.run()\n except Exception as e:\n raise e\n\n\nthreading.Thread = MockedThread\n\n\nclass WatcherMock:\n def __init__(\n self,\n path,\n callback,\n glob_pattern: str | None = None,\n allow_nonexistent: bool = False,\n ) -> None:\n pass\n\n def close(self) -> None:\n pass\n\n\nstreamlit.watcher.path_watcher.watchdog_available = False\nstreamlit.watcher.path_watcher.EventBasedPathWatcher = WatcherMock\nstreamlit.watcher.path_watcher._is_watchdog_available = lambda: False\nstreamlit.watcher.path_watcher.get_path_watcher_class = lambda x: WatcherMock\n\n\nclass MockThreading:\n class Timer:\n def __init__(self, delay, cb):\n cb()\n\n def start(self):\n pass\n\n Lock = contextlib.nullcontext\n\n\nstreamlit.elements.spinner.threading = MockThreading\n";
2
+ export declare const streamlitLoader = "\nimport json\nfrom streamlit import config\nimport streamlit.web.server.server as st_server\nfrom streamlit.runtime.runtime import Runtime\n\ntry:\n # Check if __jupyterpack_streamlit_instance defined from previous run\n __jupyterpack_streamlit_instance\nexcept NameError:\n __jupyterpack_streamlit_instance = {\n \"tornado_bridge\": None,\n \"streamlit_server\": None,\n }\n\n\ndef __jupyterpack_create_streamlit_app(base_url, script_path):\n if Runtime._instance is not None:\n Runtime._instance.stop()\n Runtime._instance = None\n config.set_option(\"server.baseUrlPath\", base_url)\n\n config.set_option(\"server.port\", 6789)\n config.set_option(\"server.enableCORS\", False)\n config.set_option(\"server.enableXsrfProtection\", False)\n\n streamlit_server = st_server.Server(script_path, True)\n return streamlit_server\n\n\ndef __jupyterpack_streamlit_dispose():\n global __jupyterpack_streamlit_instance\n streamlit_server = __jupyterpack_streamlit_instance.get(\"streamlit_server\", None)\n if streamlit_server:\n streamlit_server._runtime.stop()\n\n __jupyterpack_streamlit_instance = {\n \"tornado_bridge\": None,\n \"streamlit_server\": None,\n }\n del streamlit_server\n\n\nasync def __jupyterpack_streamlit_open_ws(\n instance_id: str, kernel_client_id: str, ws_url: str, protocols_str: str | None\n):\n tornado_bridge = __jupyterpack_streamlit_instance[\"tornado_bridge\"]\n if tornado_bridge is None:\n raise Exception(\"Missing tornado instance\")\n await tornado_bridge.open_ws(instance_id, kernel_client_id, ws_url, protocols_str)\n\n\nasync def __jupyterpack_streamlit_receive_ws_message(\n instance_id: str, kernel_client_id: str, ws_url: str, payload_message: str\n):\n tornado_bridge = __jupyterpack_streamlit_instance[\"tornado_bridge\"]\n if tornado_bridge is None:\n raise Exception(\"Missing tornado instance\")\n await tornado_bridge.receive_ws_message_from_js(\n instance_id, kernel_client_id, ws_url, payload_message\n )\n\n\nasync def __jupyterpack_streamlit_get_response(\n method, url, headers, content=None, params=None\n):\n global __jupyterpack_streamlit_instance\n if not __jupyterpack_streamlit_instance[\"streamlit_server\"]:\n streamlit_server = __jupyterpack_create_streamlit_app(\n \"{{base_url}}\", \"{{script_path}}\"\n ) # noqa\n app = streamlit_server._create_app()\n await streamlit_server._runtime.start()\n __jupyterpack_streamlit_instance[\"streamlit_server\"] = streamlit_server\n __jupyterpack_streamlit_instance[\"tornado_bridge\"] = TornadoBridge(\n app, \"{{base_url}}\"\n )\n\n tornado_bridge = __jupyterpack_streamlit_instance[\"tornado_bridge\"]\n req_dict = {\n \"method\": method,\n \"url\": url,\n \"headers\": list(headers.items()),\n \"body\": content,\n }\n\n response = await tornado_bridge.fetch(req_dict)\n json_str = json.dumps(response)\n return json_str\n";
@@ -0,0 +1,147 @@
1
+ // Auto-generated TypeScript file from Python files
2
+ export const bootstrap = `
3
+ import os
4
+
5
+ import threading
6
+ import streamlit.watcher.path_watcher
7
+ import contextlib
8
+ import streamlit.elements.spinner
9
+
10
+ os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
11
+
12
+
13
+ class MockedThread(threading.Thread):
14
+ def start(self):
15
+ threading.current_thread = lambda: self
16
+ try:
17
+ self.run()
18
+ except Exception as e:
19
+ raise e
20
+
21
+
22
+ threading.Thread = MockedThread
23
+
24
+
25
+ class WatcherMock:
26
+ def __init__(
27
+ self,
28
+ path,
29
+ callback,
30
+ glob_pattern: str | None = None,
31
+ allow_nonexistent: bool = False,
32
+ ) -> None:
33
+ pass
34
+
35
+ def close(self) -> None:
36
+ pass
37
+
38
+
39
+ streamlit.watcher.path_watcher.watchdog_available = False
40
+ streamlit.watcher.path_watcher.EventBasedPathWatcher = WatcherMock
41
+ streamlit.watcher.path_watcher._is_watchdog_available = lambda: False
42
+ streamlit.watcher.path_watcher.get_path_watcher_class = lambda x: WatcherMock
43
+
44
+
45
+ class MockThreading:
46
+ class Timer:
47
+ def __init__(self, delay, cb):
48
+ cb()
49
+
50
+ def start(self):
51
+ pass
52
+
53
+ Lock = contextlib.nullcontext
54
+
55
+
56
+ streamlit.elements.spinner.threading = MockThreading
57
+ `;
58
+ export const streamlitLoader = `
59
+ import json
60
+ from streamlit import config
61
+ import streamlit.web.server.server as st_server
62
+ from streamlit.runtime.runtime import Runtime
63
+
64
+ try:
65
+ # Check if __jupyterpack_streamlit_instance defined from previous run
66
+ __jupyterpack_streamlit_instance
67
+ except NameError:
68
+ __jupyterpack_streamlit_instance = {
69
+ "tornado_bridge": None,
70
+ "streamlit_server": None,
71
+ }
72
+
73
+
74
+ def __jupyterpack_create_streamlit_app(base_url, script_path):
75
+ if Runtime._instance is not None:
76
+ Runtime._instance.stop()
77
+ Runtime._instance = None
78
+ config.set_option("server.baseUrlPath", base_url)
79
+
80
+ config.set_option("server.port", 6789)
81
+ config.set_option("server.enableCORS", False)
82
+ config.set_option("server.enableXsrfProtection", False)
83
+
84
+ streamlit_server = st_server.Server(script_path, True)
85
+ return streamlit_server
86
+
87
+
88
+ def __jupyterpack_streamlit_dispose():
89
+ global __jupyterpack_streamlit_instance
90
+ streamlit_server = __jupyterpack_streamlit_instance.get("streamlit_server", None)
91
+ if streamlit_server:
92
+ streamlit_server._runtime.stop()
93
+
94
+ __jupyterpack_streamlit_instance = {
95
+ "tornado_bridge": None,
96
+ "streamlit_server": None,
97
+ }
98
+ del streamlit_server
99
+
100
+
101
+ async def __jupyterpack_streamlit_open_ws(
102
+ instance_id: str, kernel_client_id: str, ws_url: str, protocols_str: str | None
103
+ ):
104
+ tornado_bridge = __jupyterpack_streamlit_instance["tornado_bridge"]
105
+ if tornado_bridge is None:
106
+ raise Exception("Missing tornado instance")
107
+ await tornado_bridge.open_ws(instance_id, kernel_client_id, ws_url, protocols_str)
108
+
109
+
110
+ async def __jupyterpack_streamlit_receive_ws_message(
111
+ instance_id: str, kernel_client_id: str, ws_url: str, payload_message: str
112
+ ):
113
+ tornado_bridge = __jupyterpack_streamlit_instance["tornado_bridge"]
114
+ if tornado_bridge is None:
115
+ raise Exception("Missing tornado instance")
116
+ await tornado_bridge.receive_ws_message_from_js(
117
+ instance_id, kernel_client_id, ws_url, payload_message
118
+ )
119
+
120
+
121
+ async def __jupyterpack_streamlit_get_response(
122
+ method, url, headers, content=None, params=None
123
+ ):
124
+ global __jupyterpack_streamlit_instance
125
+ if not __jupyterpack_streamlit_instance["streamlit_server"]:
126
+ streamlit_server = __jupyterpack_create_streamlit_app(
127
+ "{{base_url}}", "{{script_path}}"
128
+ ) # noqa
129
+ app = streamlit_server._create_app()
130
+ await streamlit_server._runtime.start()
131
+ __jupyterpack_streamlit_instance["streamlit_server"] = streamlit_server
132
+ __jupyterpack_streamlit_instance["tornado_bridge"] = TornadoBridge(
133
+ app, "{{base_url}}"
134
+ )
135
+
136
+ tornado_bridge = __jupyterpack_streamlit_instance["tornado_bridge"]
137
+ req_dict = {
138
+ "method": method,
139
+ "url": url,
140
+ "headers": list(headers.items()),
141
+ "body": content,
142
+ }
143
+
144
+ response = await tornado_bridge.fetch(req_dict)
145
+ json_str = json.dumps(response)
146
+ return json_str
147
+ `;
@@ -0,0 +1,33 @@
1
+ import { IDict } from '../../type';
2
+ import { KernelExecutor } from '../kernelExecutor';
3
+ export declare class StreamlitServer extends KernelExecutor {
4
+ init(options: {
5
+ entryPath?: string;
6
+ initCode?: string;
7
+ instanceId: string;
8
+ kernelClientId: string;
9
+ }): Promise<void>;
10
+ getResponseFunctionFactory(options: {
11
+ urlPath: string;
12
+ method: string;
13
+ headers: IDict;
14
+ params?: string;
15
+ content?: string;
16
+ }): string;
17
+ openWebsocketFunctionFactory(options: {
18
+ instanceId: string;
19
+ kernelId: string;
20
+ wsUrl: string;
21
+ protocol?: string;
22
+ }): string;
23
+ sendWebsocketMessageFunctionFactory(options: {
24
+ instanceId: string;
25
+ kernelId: string;
26
+ wsUrl: string;
27
+ message: string;
28
+ }): string;
29
+ disposePythonServer(): Promise<void>;
30
+ private _GET_RESPONSE_FUNCTION;
31
+ private _OPEN_WEBSOCKET_FUNCTION;
32
+ private _SEND_WEBSOCKET_FUNCTION;
33
+ }
@@ -0,0 +1,55 @@
1
+ import { stringOrNone } from '../../tools';
2
+ import { JupyterPackFramework } from '../../type';
3
+ import { patch, tools } from '../common/generatedPythonFiles';
4
+ import { KernelExecutor } from '../kernelExecutor';
5
+ import { bootstrap as tornadoBootstrap, tornadoBridge } from '../tornado/generatedPythonFiles';
6
+ import { bootstrap, streamlitLoader } from './generatedPythonFiles';
7
+ export class StreamlitServer extends KernelExecutor {
8
+ constructor() {
9
+ super(...arguments);
10
+ this._GET_RESPONSE_FUNCTION = '__jupyterpack_streamlit_get_response';
11
+ this._OPEN_WEBSOCKET_FUNCTION = '__jupyterpack_streamlit_open_ws';
12
+ this._SEND_WEBSOCKET_FUNCTION = '__jupyterpack_streamlit_receive_ws_message';
13
+ }
14
+ async init(options) {
15
+ await super.init(options);
16
+ const { instanceId, kernelClientId, entryPath } = options;
17
+ if (!entryPath) {
18
+ throw new Error('Missing streamlit entry path, please check your SPK file');
19
+ }
20
+ const baseURL = this.buildBaseURL({
21
+ instanceId,
22
+ kernelClientId,
23
+ framework: JupyterPackFramework.STREAMLIT
24
+ });
25
+ await this.executeCode({ code: patch });
26
+ await this.executeCode({ code: tools.replaceAll('{{base_url}}', baseURL) });
27
+ await this.executeCode({ code: tornadoBootstrap });
28
+ await this.executeCode({ code: tornadoBridge });
29
+ await this.executeCode({ code: bootstrap });
30
+ const stCode = streamlitLoader
31
+ .replaceAll('{{base_url}}', baseURL)
32
+ .replaceAll('{{script_path}}', entryPath);
33
+ await this.executeCode({ code: stCode });
34
+ }
35
+ getResponseFunctionFactory(options) {
36
+ const { method, urlPath, headers, params, content } = options;
37
+ const code = `await ${this._GET_RESPONSE_FUNCTION}("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
38
+ return code;
39
+ }
40
+ openWebsocketFunctionFactory(options) {
41
+ const { instanceId, kernelId, wsUrl, protocol } = options;
42
+ const code = `await ${this._OPEN_WEBSOCKET_FUNCTION}("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
43
+ return code;
44
+ }
45
+ sendWebsocketMessageFunctionFactory(options) {
46
+ const { instanceId, kernelId, wsUrl, message } = options;
47
+ const code = `await ${this._SEND_WEBSOCKET_FUNCTION}("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
48
+ return code;
49
+ }
50
+ async disposePythonServer() {
51
+ await this.executeCode({
52
+ code: '__jupyterpack_streamlit_dispose()'
53
+ });
54
+ }
55
+ }