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.
Files changed (50) hide show
  1. package/README.md +107 -3
  2. package/lib/document/widgetFactory.js +3 -1
  3. package/lib/pythonServer/baseServer.d.ts +78 -0
  4. package/lib/pythonServer/baseServer.js +175 -0
  5. package/lib/pythonServer/dash/dashServer.d.ts +4 -17
  6. package/lib/pythonServer/dash/dashServer.js +14 -22
  7. package/lib/pythonServer/dash/deps.d.ts +2 -0
  8. package/lib/pythonServer/dash/deps.js +4 -0
  9. package/lib/pythonServer/index.d.ts +3 -3
  10. package/lib/pythonServer/index.js +5 -1
  11. package/lib/pythonServer/kernelExecutor.d.ts +2 -58
  12. package/lib/pythonServer/kernelExecutor.js +0 -72
  13. package/lib/pythonServer/shiny/deps.d.ts +2 -0
  14. package/lib/pythonServer/shiny/deps.js +4 -0
  15. package/lib/pythonServer/shiny/shinyServer.d.ts +10 -0
  16. package/lib/pythonServer/shiny/shinyServer.js +54 -0
  17. package/lib/pythonServer/starlette/starletteServer.d.ts +13 -0
  18. package/lib/pythonServer/starlette/starletteServer.js +49 -0
  19. package/lib/pythonServer/streamlit/deps.d.ts +2 -0
  20. package/lib/pythonServer/streamlit/deps.js +10 -0
  21. package/lib/pythonServer/streamlit/streamlitServer.d.ts +4 -9
  22. package/lib/pythonServer/streamlit/streamlitServer.js +16 -15
  23. package/lib/pythonServer/tornado/tornadoServer.d.ts +2 -24
  24. package/lib/pythonServer/tornado/tornadoServer.js +10 -35
  25. package/lib/pythonWidget/pythonWidgetModel.js +2 -0
  26. package/lib/swConnection/mainConnectionManager.d.ts +4 -4
  27. package/lib/swConnection/mainConnectionManager.js +23 -12
  28. package/lib/tools.d.ts +1 -0
  29. package/lib/tools.js +8 -0
  30. package/lib/type.d.ts +27 -9
  31. package/lib/type.js +2 -0
  32. package/lib/websocket/websocket.js +5 -1
  33. package/package.json +1 -1
  34. package/src/document/widgetFactory.ts +3 -1
  35. package/src/pythonServer/baseServer.ts +285 -0
  36. package/src/pythonServer/dash/dashServer.ts +18 -35
  37. package/src/pythonServer/dash/deps.ts +6 -0
  38. package/src/pythonServer/index.ts +9 -5
  39. package/src/pythonServer/kernelExecutor.ts +3 -156
  40. package/src/pythonServer/shiny/deps.ts +6 -0
  41. package/src/pythonServer/shiny/shinyServer.ts +63 -0
  42. package/src/pythonServer/starlette/starletteServer.ts +59 -0
  43. package/src/pythonServer/streamlit/deps.ts +12 -0
  44. package/src/pythonServer/streamlit/streamlitServer.ts +19 -20
  45. package/src/pythonServer/tornado/tornadoServer.ts +11 -55
  46. package/src/pythonWidget/pythonWidgetModel.ts +5 -2
  47. package/src/swConnection/mainConnectionManager.ts +28 -20
  48. package/src/tools.ts +9 -0
  49. package/src/type.ts +34 -12
  50. package/src/websocket/websocket.ts +9 -1
package/README.md CHANGED
@@ -7,7 +7,8 @@
7
7
 
8
8
  ## Features
9
9
 
10
- - **Python Web Apps**: Serve Python web applications directly in the browser using JupyterLite's in-browser Python kernel. `jupyterpack` currently supports Dash.
10
+ - **Python Web Apps**: Serve Python web applications directly in the browser using JupyterLite's in-browser Python kernel. `jupyterpack` currently supports Dash, Streamlit and Shiny for Python. You can also use `jupyterpack` to serve any `asgi` or `wsgi` Python web application.
11
+ .
11
12
  - **JavaScript Web Apps**: Bundle and serve JavaScript web applications using in-browser bundlers.
12
13
 
13
14
  ![Image](https://github.com/user-attachments/assets/22849fe8-199f-4d9f-ad45-055bccf88bad)
@@ -46,7 +47,90 @@ the `app.spk` is the entry point of your React app, it should contain the follow
46
47
  }
47
48
  ```
48
49
 
49
- Double clicking the `spk` file to open the web app as a tab of JupyterLab.
50
+ Double-clicking the `spk` file to open the web app as a tab of JupyterLab.
51
+
52
+ ## `.spk` — Jupyter Pack File Format
53
+
54
+ A **`.spk`** file describes how an application should be loaded, executed, and rendered in JupyterLite and JupyterLab.
55
+ It defines the **entry point**, **framework**, optional **dependencies**, and runtime **metadata**, allowing reproducible execution across environments.
56
+
57
+ The file is expressed in **JSON**.
58
+
59
+ ### Basic Structure
60
+
61
+ ```typescript
62
+ interface IJupyterPackFileFormat {
63
+ entry: string;
64
+ framework: JupyterPackFramework;
65
+ name?: string;
66
+ metadata?: {
67
+ autoreload?: boolean;
68
+ };
69
+ rootUrl?: string;
70
+ dependencies?: IDependencies;
71
+ disableDependencies?: boolean;
72
+ }
73
+ ```
74
+
75
+ - `entry` (required): Path to the main entry file of the application. For examples:
76
+ - _"app.py"_
77
+ - _"/index.html"_
78
+ - _"dashboard/index.py"_
79
+
80
+ The path is resolved relative to the .spk file location.
81
+
82
+ - `framework` (required): The framework used to run the application. Supported frameworks are:
83
+
84
+ | Value | Description |
85
+ | ----------- | -------------------------------- |
86
+ | `react` | React-based frontend application |
87
+ | `dash` | Plotly Dash application |
88
+ | `streamlit` | Streamlit application |
89
+ | `tornado` | Tornado web application |
90
+ | `shiny` | Shiny application (Python) |
91
+ | `starlette` | Starlette ASGI application |
92
+
93
+ - `name` (optional): The name of the application. If not provided, the name will be the name of the .spk file.
94
+
95
+ - `metadata` (optional): Additional metadata for the application.
96
+ - `autoreload`: Enables automatic reload when source files change.
97
+
98
+ - `rootUrl` (optional): The root URL of the web application. Default is `/`
99
+
100
+ - `dependencies` (optional): The dependencies of the web application. It will be merged with the default dependencies of the selected framework
101
+ - `mamba`: Emscripten-forge packages
102
+ - `pip`: PYPI packages
103
+ Example:
104
+ ```typescript
105
+ dependencies: {
106
+ mamba: ['numpy, scipy'];
107
+ pip: ['plotly'];
108
+ }
109
+ ```
110
+
111
+ You only need to specify the dependencies of the application, the required dependencies of the framework will be automatically added.
112
+
113
+ - `disableDependencies` (optional): Disable entirely the dependency installation. This is useful when dependencies are already provided by the environment. Default is `false`.
114
+
115
+ ### Full example
116
+
117
+ ```json
118
+ {
119
+ "name": "Sales Dashboard",
120
+ "entry": "app.py",
121
+ "framework": "streamlit",
122
+ "rootUrl": "/",
123
+ "metadata": {
124
+ "autoreload": true
125
+ },
126
+ "dependencies": {
127
+ "mamba": ["numpy", "pandas"],
128
+ "pip": []
129
+ }
130
+ }
131
+ ```
132
+
133
+ ## Framework-specific configurations
50
134
 
51
135
  ### Dash application
52
136
 
@@ -68,7 +152,27 @@ the `app.spk` is the entry point of your Dash app, it should contain the followi
68
152
  }
69
153
  ```
70
154
 
71
- For the Dash code, you need to define your Dash app variable as `app` and do not call `app.run_server` directly, `jupyterpack` will handle the server for you. Just as the case of React app, double clicking the spk file will open the Dash app in a new JupyterLab tab.
155
+ For Dash applications, you must define your Dash instance as a variable named `app`.
156
+ Do **not** call `app.run_server()` yourself — `jupyterpack` is responsible for starting and managing the server lifecycle.
157
+
158
+ As with React applications, double-clicking the `.spk` file will open the Dash app in a new JupyterLab tab.
159
+
160
+ ### Streamlit application
161
+
162
+ Streamlit applications follow a similar structure to Dash apps.
163
+ Write your code as a standard Streamlit application and do **not** start the server manually — `jupyterpack` will handle execution and serving automatically.
164
+
165
+ Opening the `.spk` file will launch the Streamlit app in a new JupyterLab tab.
166
+
167
+ ### Shiny application
168
+
169
+ Shiny applications also follow a structure similar to Dash apps.
170
+ `jupyterpack` supports both **Shiny Express** and **Shiny Core** applications.
171
+
172
+ - **Shiny Express**: no special requirements.
173
+ - **Shiny Core**: the application instance must be assigned to a variable named `app`.
174
+
175
+ In both cases, the server is managed by `jupyterpack`, and opening the `.spk` file will launch the app in JupyterLab.
72
176
 
73
177
  ## Try it online!
74
178
 
@@ -35,7 +35,9 @@ export class JupyterPackWidgetFactory extends ABCWidgetFactory {
35
35
  }
36
36
  case JupyterPackFramework.DASH:
37
37
  case JupyterPackFramework.STREAMLIT:
38
- case JupyterPackFramework.TORNADO: {
38
+ case JupyterPackFramework.TORNADO:
39
+ case JupyterPackFramework.STARLETTE:
40
+ case JupyterPackFramework.SHINY: {
39
41
  const model = new PythonWidgetModel({
40
42
  jpackModel,
41
43
  context,
@@ -0,0 +1,78 @@
1
+ import { IBasePythonServer, IDependencies, IDict, IKernelExecutor, IPythonServerInitOptions, JupyterPackFramework } from '../type';
2
+ import { KernelExecutor } from './kernelExecutor';
3
+ export declare abstract class BasePythonServer implements IBasePythonServer {
4
+ constructor(options: KernelExecutor.IOptions);
5
+ abstract reloadPythonServer(options: {
6
+ entryPath?: string;
7
+ initCode?: string;
8
+ }): Promise<void>;
9
+ get isDisposed(): boolean;
10
+ get kernelExecutor(): IKernelExecutor;
11
+ dispose(): void;
12
+ init(options: IPythonServerInitOptions): Promise<void>;
13
+ disposePythonServer(): Promise<void>;
14
+ getResponseFunctionFactory(options: {
15
+ urlPath: string;
16
+ method: string;
17
+ headers: IDict;
18
+ params?: string;
19
+ content?: string;
20
+ }): string;
21
+ openWebsocketFunctionFactory(options: {
22
+ instanceId: string;
23
+ kernelId: string;
24
+ wsUrl: string;
25
+ protocol?: string;
26
+ }): string;
27
+ closeWebsocketFunctionFactory(options: {
28
+ instanceId: string;
29
+ kernelId: string;
30
+ wsUrl: string;
31
+ }): string;
32
+ sendWebsocketMessageFunctionFactory(options: {
33
+ instanceId: string;
34
+ kernelId: string;
35
+ wsUrl: string;
36
+ message: string;
37
+ }): string;
38
+ openWebsocket(options: {
39
+ instanceId: string;
40
+ kernelId: string;
41
+ wsUrl: string;
42
+ protocol?: string;
43
+ }): Promise<void>;
44
+ closeWebsocket(options: {
45
+ instanceId: string;
46
+ kernelId: string;
47
+ wsUrl: string;
48
+ }): Promise<void>;
49
+ sendWebsocketMessage(options: {
50
+ instanceId: string;
51
+ kernelId: string;
52
+ wsUrl: string;
53
+ message: string;
54
+ }): Promise<void>;
55
+ getResponse(options: {
56
+ method: string;
57
+ urlPath: string;
58
+ headers: IDict;
59
+ requestBody?: ArrayBuffer;
60
+ params?: string;
61
+ }): Promise<IDict>;
62
+ protected buildBaseURL(options: {
63
+ instanceId: string;
64
+ kernelClientId: string;
65
+ framework: JupyterPackFramework;
66
+ }): string;
67
+ protected mergeDependencies(spkDeps?: IDependencies, defaultDeps?: IDependencies): IDependencies | undefined;
68
+ protected _baseUrl: string | undefined;
69
+ protected readonly _openedWebsockets: {
70
+ instanceId: string;
71
+ kernelId: string;
72
+ wsUrl: string;
73
+ }[];
74
+ protected readonly _server_var = "__jupyterpack_python_server";
75
+ private _kernelExecutor;
76
+ private _isDisposed;
77
+ private _wsPatch;
78
+ }
@@ -0,0 +1,175 @@
1
+ import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
+ import { arrayBufferToBase64, base64ToArrayBuffer, base64ToString, isBinaryContentType, stringOrNone } from '../tools';
3
+ import websocketPatch from '../websocket/websocket.js?raw';
4
+ import { KernelExecutor } from './kernelExecutor';
5
+ export class BasePythonServer {
6
+ constructor(options) {
7
+ this._openedWebsockets = [];
8
+ this._server_var = '__jupyterpack_python_server';
9
+ this._isDisposed = false;
10
+ this._kernelExecutor = new KernelExecutor(options);
11
+ this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
12
+ }
13
+ get isDisposed() {
14
+ return this._isDisposed;
15
+ }
16
+ get kernelExecutor() {
17
+ return this._kernelExecutor;
18
+ }
19
+ dispose() {
20
+ if (this._isDisposed) {
21
+ return;
22
+ }
23
+ this._isDisposed = true;
24
+ this.disposePythonServer()
25
+ .then(() => {
26
+ this._kernelExecutor.dispose();
27
+ })
28
+ .catch(console.error);
29
+ }
30
+ async init(options) {
31
+ const patchCode = `
32
+ from jupyterpack.common import patch_all
33
+ patch_all()
34
+ `;
35
+ await this._kernelExecutor.executeCode({ code: patchCode });
36
+ if (!options.disableDependencies) {
37
+ const { dependencies } = options;
38
+ if (dependencies === null || dependencies === void 0 ? void 0 : dependencies.mamba) {
39
+ const mambaDeps = `
40
+ %mamba install ${dependencies.mamba.join(' ')}
41
+ True
42
+ `;
43
+ await this.kernelExecutor.executeCode({
44
+ code: mambaDeps
45
+ }, true);
46
+ }
47
+ if (dependencies === null || dependencies === void 0 ? void 0 : dependencies.pip) {
48
+ const pipDeps = `
49
+ %pip install ${dependencies.pip.join(' ')}
50
+ True
51
+ `;
52
+ await this.kernelExecutor.executeCode({
53
+ code: pipDeps
54
+ }, true);
55
+ }
56
+ }
57
+ }
58
+ async disposePythonServer() {
59
+ await this.kernelExecutor.executeCode({
60
+ code: `${this._server_var}.dispose()`
61
+ });
62
+ for (const element of this._openedWebsockets) {
63
+ await this.closeWebsocket(element);
64
+ }
65
+ }
66
+ getResponseFunctionFactory(options) {
67
+ const { method, urlPath, headers, params, content } = options;
68
+ const code = `await ${this._server_var}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
69
+ return code;
70
+ }
71
+ openWebsocketFunctionFactory(options) {
72
+ const { instanceId, kernelId, wsUrl, protocol } = options;
73
+ const code = `await ${this._server_var}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
74
+ return code;
75
+ }
76
+ closeWebsocketFunctionFactory(options) {
77
+ const { instanceId, kernelId, wsUrl } = options;
78
+ const code = `await ${this._server_var}.close_ws("${instanceId}", "${kernelId}", "${wsUrl}")`;
79
+ return code;
80
+ }
81
+ sendWebsocketMessageFunctionFactory(options) {
82
+ const { instanceId, kernelId, wsUrl, message } = options;
83
+ const code = `await ${this._server_var}.receive_ws_message("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
84
+ return code;
85
+ }
86
+ async openWebsocket(options) {
87
+ const code = this.openWebsocketFunctionFactory(options);
88
+ if (code) {
89
+ try {
90
+ await this._kernelExecutor.executeCode({ code });
91
+ this._openedWebsockets.push(options);
92
+ }
93
+ catch (e) {
94
+ console.error('Failed to open websocket', e);
95
+ }
96
+ }
97
+ else {
98
+ throw new Error('Missing websocket open code');
99
+ }
100
+ }
101
+ async closeWebsocket(options) {
102
+ const code = this.closeWebsocketFunctionFactory(options);
103
+ if (code) {
104
+ await this._kernelExecutor.executeCode({ code });
105
+ }
106
+ else {
107
+ throw new Error('Missing websocket close code');
108
+ }
109
+ }
110
+ async sendWebsocketMessage(options) {
111
+ const code = this.sendWebsocketMessageFunctionFactory(options);
112
+ if (code) {
113
+ await this._kernelExecutor.executeCode({ code });
114
+ }
115
+ else {
116
+ throw new Error('Missing websocket send code');
117
+ }
118
+ }
119
+ async getResponse(options) {
120
+ var _a;
121
+ const { method, urlPath, requestBody, params, headers } = options;
122
+ const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
123
+ const code = this.getResponseFunctionFactory({
124
+ method,
125
+ urlPath,
126
+ headers,
127
+ params,
128
+ content
129
+ });
130
+ const raw = await this._kernelExecutor.executeCode({ code }, true);
131
+ if (!raw) {
132
+ throw new Error(`Missing response for ${urlPath}`);
133
+ }
134
+ const jsonStr = raw.replaceAll("'", '');
135
+ const obj = JSON.parse(jsonStr);
136
+ const responseHeaders = JSON.parse(atob(obj.headers));
137
+ 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'];
138
+ let responseContent;
139
+ if (isBinaryContentType(contentType)) {
140
+ responseContent = base64ToArrayBuffer(obj.content);
141
+ }
142
+ else {
143
+ responseContent = base64ToString(obj.content);
144
+ }
145
+ if (contentType && contentType.toLowerCase().includes('text/html')) {
146
+ responseContent = responseContent.replace('<head>', `<head>\n<script>\n${this._wsPatch}\n</script>\n`);
147
+ }
148
+ const decodedObj = {
149
+ status_code: obj.status_code,
150
+ headers: responseHeaders,
151
+ content: responseContent
152
+ };
153
+ return decodedObj;
154
+ }
155
+ buildBaseURL(options) {
156
+ const { instanceId, kernelClientId, framework } = options;
157
+ const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
158
+ const baseURL = URLExt.join(fullLabextensionsUrl, 'jupyterpack/static', instanceId, framework, kernelClientId, '/');
159
+ this._baseUrl = baseURL;
160
+ return baseURL;
161
+ }
162
+ mergeDependencies(spkDeps, defaultDeps) {
163
+ var _a, _b, _c, _d;
164
+ if (!spkDeps) {
165
+ return defaultDeps;
166
+ }
167
+ if (!defaultDeps) {
168
+ return spkDeps;
169
+ }
170
+ const merged = {};
171
+ merged.mamba = [...((_a = spkDeps.mamba) !== null && _a !== void 0 ? _a : []), ...((_b = defaultDeps.mamba) !== null && _b !== void 0 ? _b : [])];
172
+ merged.pip = [...((_c = spkDeps.pip) !== null && _c !== void 0 ? _c : []), ...((_d = defaultDeps.pip) !== null && _d !== void 0 ? _d : [])];
173
+ return merged;
174
+ }
175
+ }
@@ -1,22 +1,9 @@
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>;
1
+ import { IPythonServerInitOptions } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+ export declare class DashServer extends BasePythonServer {
4
+ init(options: IPythonServerInitOptions): Promise<void>;
17
5
  reloadPythonServer(options: {
18
6
  entryPath?: string;
19
7
  initCode?: string;
20
8
  }): Promise<void>;
21
- private _DASH_SERVER_VAR;
22
9
  }
@@ -1,47 +1,39 @@
1
- import { stringOrNone } from '../../tools';
2
1
  import { JupyterPackFramework } from '../../type';
3
- import { KernelExecutor } from '../kernelExecutor';
4
- export class DashServer extends KernelExecutor {
5
- constructor() {
6
- super(...arguments);
7
- this._DASH_SERVER_VAR = '__jupyterpack_dash_server';
8
- }
2
+ import { BasePythonServer } from '../baseServer';
3
+ import { DEPENDENCIES } from './deps';
4
+ export class DashServer extends BasePythonServer {
9
5
  async init(options) {
10
- await super.init(options);
6
+ const mergedOptions = {
7
+ ...options,
8
+ dependencies: this.mergeDependencies(options.dependencies, DEPENDENCIES)
9
+ };
10
+ await super.init(mergedOptions);
11
11
  const { initCode, instanceId, kernelClientId } = options;
12
12
  const baseURL = this.buildBaseURL({
13
13
  instanceId,
14
14
  kernelClientId,
15
15
  framework: JupyterPackFramework.DASH
16
16
  });
17
- await this.executeCode({
17
+ await this.kernelExecutor.executeCode({
18
18
  code: `
19
19
  import os
20
20
  os.environ["DASH_URL_BASE_PATHNAME"] = "${baseURL}"
21
21
  `
22
22
  });
23
23
  if (initCode) {
24
- await this.executeCode({ code: initCode });
24
+ await this.kernelExecutor.executeCode({ code: initCode });
25
25
  }
26
26
  const loaderCode = `
27
27
  from jupyterpack.dash import DashServer
28
- ${this._DASH_SERVER_VAR} = DashServer(app, "${baseURL}")
28
+ ${this._server_var} = DashServer(app, "${baseURL}")
29
29
  `;
30
- await this.executeCode({ code: loaderCode });
31
- }
32
- getResponseFunctionFactory(options) {
33
- const { method, urlPath, headers, params, content } = options;
34
- const code = `${this._DASH_SERVER_VAR}.get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
35
- return code;
36
- }
37
- async disposePythonServer() {
38
- await this.executeCode({ code: `${this._DASH_SERVER_VAR}.dispose()` });
30
+ await this.kernelExecutor.executeCode({ code: loaderCode });
39
31
  }
40
32
  async reloadPythonServer(options) {
41
33
  const { initCode } = options;
42
34
  if (initCode) {
43
- await this.executeCode({ code: initCode });
35
+ await this.kernelExecutor.executeCode({ code: initCode });
44
36
  }
45
- await this.executeCode({ code: `${this._DASH_SERVER_VAR}.reload(app)` }, true);
37
+ await this.kernelExecutor.executeCode({ code: `${this._server_var}.reload(app)` }, true);
46
38
  }
47
39
  }
@@ -0,0 +1,2 @@
1
+ import { IDependencies } from '../../type';
2
+ export declare const DEPENDENCIES: IDependencies;
@@ -0,0 +1,4 @@
1
+ export const DEPENDENCIES = {
2
+ mamba: ['dash', 'werkzeug>=2.2,<3.0'],
3
+ pip: []
4
+ };
@@ -1,5 +1,5 @@
1
- import { IKernelExecutor, JupyterPackFramework } from '../type';
1
+ import { IBasePythonServer, JupyterPackFramework } from '../type';
2
2
  import { KernelExecutor } from './kernelExecutor';
3
- type KernelExecutorConstructor = new (options: KernelExecutor.IOptions) => IKernelExecutor;
4
- export declare const PYTHON_SERVER: Map<JupyterPackFramework, KernelExecutorConstructor>;
3
+ type BasePythonServerConstructor = new (options: KernelExecutor.IOptions) => IBasePythonServer;
4
+ export declare const PYTHON_SERVER: Map<JupyterPackFramework, BasePythonServerConstructor>;
5
5
  export {};
@@ -1,9 +1,13 @@
1
1
  import { JupyterPackFramework } from '../type';
2
2
  import { DashServer } from './dash/dashServer';
3
+ import { ShinyServer } from './shiny/shinyServer';
4
+ import { StarletteServer } from './starlette/starletteServer';
3
5
  import { StreamlitServer } from './streamlit/streamlitServer';
4
6
  import { TornadoServer } from './tornado/tornadoServer';
5
7
  export const PYTHON_SERVER = new Map([
6
8
  [JupyterPackFramework.DASH, DashServer],
7
9
  [JupyterPackFramework.STREAMLIT, StreamlitServer],
8
- [JupyterPackFramework.TORNADO, TornadoServer]
10
+ [JupyterPackFramework.TORNADO, TornadoServer],
11
+ [JupyterPackFramework.SHINY, ShinyServer],
12
+ [JupyterPackFramework.STARLETTE, StarletteServer]
9
13
  ]);
@@ -1,68 +1,12 @@
1
1
  import { KernelMessage, Session } from '@jupyterlab/services';
2
- import { IDict, IKernelExecutor, JupyterPackFramework } from '../type';
3
- export declare abstract class KernelExecutor implements IKernelExecutor {
2
+ import { IKernelExecutor } from '../type';
3
+ export declare class KernelExecutor implements IKernelExecutor {
4
4
  constructor(options: KernelExecutor.IOptions);
5
5
  get isDisposed(): boolean;
6
- abstract disposePythonServer(): Promise<void>;
7
- abstract reloadPythonServer(options: {
8
- entryPath?: string;
9
- initCode?: string;
10
- }): Promise<void>;
11
- abstract getResponseFunctionFactory(options: {
12
- urlPath: string;
13
- method: string;
14
- headers: IDict;
15
- params?: string;
16
- content?: string;
17
- }): string;
18
- init(options: {
19
- entryPath?: string;
20
- initCode?: string;
21
- instanceId: string;
22
- kernelClientId: string;
23
- }): Promise<void>;
24
- openWebsocketFunctionFactory(options: {
25
- instanceId: string;
26
- kernelId: string;
27
- wsUrl: string;
28
- protocol?: string;
29
- }): string | undefined;
30
- sendWebsocketMessageFunctionFactory(options: {
31
- instanceId: string;
32
- kernelId: string;
33
- wsUrl: string;
34
- message: string;
35
- }): string | undefined;
36
- openWebsocket(options: {
37
- instanceId: string;
38
- kernelId: string;
39
- wsUrl: string;
40
- protocol?: string;
41
- }): Promise<void>;
42
- sendWebsocketMessage(options: {
43
- instanceId: string;
44
- kernelId: string;
45
- wsUrl: string;
46
- message: string;
47
- }): Promise<void>;
48
- getResponse(options: {
49
- method: string;
50
- urlPath: string;
51
- headers: IDict;
52
- requestBody?: ArrayBuffer;
53
- params?: string;
54
- }): Promise<IDict>;
55
6
  executeCode(code: KernelMessage.IExecuteRequestMsg['content'], waitForResult?: boolean): Promise<string | null>;
56
7
  dispose(): void;
57
- protected buildBaseURL(options: {
58
- instanceId: string;
59
- kernelClientId: string;
60
- framework: JupyterPackFramework;
61
- }): string;
62
- protected _baseUrl: string | undefined;
63
8
  private _isDisposed;
64
9
  private _sessionConnection;
65
- private _wsPatch;
66
10
  }
67
11
  export declare namespace KernelExecutor {
68
12
  interface IOptions {
@@ -1,77 +1,12 @@
1
- import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2
1
  import stripAnsi from 'strip-ansi';
3
- import { arrayBufferToBase64, base64ToArrayBuffer, base64ToString, isBinaryContentType } from '../tools';
4
- import websocketPatch from '../websocket/websocket.js?raw';
5
2
  export class KernelExecutor {
6
3
  constructor(options) {
7
4
  this._isDisposed = false;
8
5
  this._sessionConnection = options.sessionConnection;
9
- this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
10
6
  }
11
7
  get isDisposed() {
12
8
  return this._isDisposed;
13
9
  }
14
- async init(options) {
15
- const patchCode = `
16
- from jupyterpack.common import patch_all
17
- patch_all()
18
- `;
19
- await this.executeCode({ code: patchCode });
20
- }
21
- openWebsocketFunctionFactory(options) {
22
- return undefined;
23
- }
24
- sendWebsocketMessageFunctionFactory(options) {
25
- return undefined;
26
- }
27
- async openWebsocket(options) {
28
- const code = this.openWebsocketFunctionFactory(options);
29
- if (code) {
30
- await this.executeCode({ code });
31
- }
32
- }
33
- async sendWebsocketMessage(options) {
34
- const code = this.sendWebsocketMessageFunctionFactory(options);
35
- if (code) {
36
- await this.executeCode({ code });
37
- }
38
- }
39
- async getResponse(options) {
40
- var _a;
41
- const { method, urlPath, requestBody, params, headers } = options;
42
- const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
43
- const code = this.getResponseFunctionFactory({
44
- method,
45
- urlPath,
46
- headers,
47
- params,
48
- content
49
- });
50
- const raw = await this.executeCode({ code }, true);
51
- if (!raw) {
52
- throw new Error(`Missing response for ${urlPath}`);
53
- }
54
- const jsonStr = raw.replaceAll("'", '');
55
- const obj = JSON.parse(jsonStr);
56
- const responseHeaders = JSON.parse(atob(obj.headers));
57
- 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'];
58
- let responseContent;
59
- if (isBinaryContentType(contentType)) {
60
- responseContent = base64ToArrayBuffer(obj.content);
61
- }
62
- else {
63
- responseContent = base64ToString(obj.content);
64
- }
65
- if (contentType && contentType.toLowerCase() === 'text/html') {
66
- responseContent = responseContent.replace('<head>', `<head>\n<script>\n${this._wsPatch}\n</script>\n`);
67
- }
68
- const decodedObj = {
69
- status_code: obj.status_code,
70
- headers: responseHeaders,
71
- content: responseContent
72
- };
73
- return decodedObj;
74
- }
75
10
  async executeCode(code, waitForResult) {
76
11
  var _a;
77
12
  const kernel = (_a = this._sessionConnection) === null || _a === void 0 ? void 0 : _a.kernel;
@@ -130,11 +65,4 @@ export class KernelExecutor {
130
65
  this._isDisposed = true;
131
66
  this._sessionConnection.dispose();
132
67
  }
133
- buildBaseURL(options) {
134
- const { instanceId, kernelClientId, framework } = options;
135
- const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
136
- const baseURL = URLExt.join(fullLabextensionsUrl, 'jupyterpack/static', instanceId, framework, kernelClientId, '/');
137
- this._baseUrl = baseURL;
138
- return baseURL;
139
- }
140
68
  }