jupyterpack 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +1 -1
  2. package/lib/document/commands.d.ts +8 -0
  3. package/lib/document/commands.js +76 -0
  4. package/lib/document/iframePanel.d.ts +4 -1
  5. package/lib/document/plugin.d.ts +2 -1
  6. package/lib/document/plugin.js +22 -4
  7. package/lib/document/toolbar.d.ts +9 -0
  8. package/lib/document/toolbar.js +20 -0
  9. package/lib/document/widgetFactory.d.ts +2 -1
  10. package/lib/document/widgetFactory.js +14 -6
  11. package/lib/index.d.ts +1 -1
  12. package/lib/pythonServer/baseServer.d.ts +82 -0
  13. package/lib/pythonServer/baseServer.js +141 -0
  14. package/lib/pythonServer/dash/dashServer.d.ts +5 -17
  15. package/lib/pythonServer/dash/dashServer.js +19 -24
  16. package/lib/pythonServer/index.d.ts +3 -3
  17. package/lib/pythonServer/index.js +5 -1
  18. package/lib/pythonServer/kernelExecutor.d.ts +2 -53
  19. package/lib/pythonServer/kernelExecutor.js +5 -70
  20. package/lib/pythonServer/shiny/shinyServer.d.ts +14 -0
  21. package/lib/pythonServer/shiny/shinyServer.js +49 -0
  22. package/lib/pythonServer/starlette/starletteServer.d.ts +13 -0
  23. package/lib/pythonServer/starlette/starletteServer.js +49 -0
  24. package/lib/pythonServer/streamlit/streamlitServer.d.ts +6 -26
  25. package/lib/pythonServer/streamlit/streamlitServer.js +33 -41
  26. package/lib/pythonServer/tornado/tornadoServer.d.ts +6 -26
  27. package/lib/pythonServer/tornado/tornadoServer.js +28 -36
  28. package/lib/pythonWidget/comm.d.ts +11 -0
  29. package/lib/pythonWidget/comm.js +52 -0
  30. package/lib/pythonWidget/pythonWidget.d.ts +5 -0
  31. package/lib/pythonWidget/pythonWidget.js +19 -0
  32. package/lib/pythonWidget/pythonWidgetModel.d.ts +16 -4
  33. package/lib/pythonWidget/pythonWidgetModel.js +77 -13
  34. package/lib/sandpackWidget/sandpackFilesModel.d.ts +6 -2
  35. package/lib/sandpackWidget/sandpackFilesModel.js +13 -2
  36. package/lib/sandpackWidget/sandpackPanel.d.ts +10 -1
  37. package/lib/sandpackWidget/sandpackPanel.js +38 -3
  38. package/lib/swConnection/mainConnectionManager.d.ts +4 -4
  39. package/lib/swConnection/mainConnectionManager.js +23 -12
  40. package/lib/token.d.ts +2 -1
  41. package/lib/token.js +1 -0
  42. package/lib/tools.d.ts +6 -0
  43. package/lib/tools.js +25 -0
  44. package/lib/type.d.ts +39 -4
  45. package/lib/type.js +2 -0
  46. package/lib/websocket/websocket.js +5 -1
  47. package/package.json +6 -6
  48. package/src/document/commands.ts +91 -0
  49. package/src/document/iframePanel.ts +4 -1
  50. package/src/document/plugin.ts +28 -7
  51. package/src/document/toolbar.ts +39 -0
  52. package/src/document/widgetFactory.ts +16 -6
  53. package/src/global.d.ts +5 -0
  54. package/src/pythonServer/baseServer.ts +245 -0
  55. package/src/pythonServer/dash/dashServer.ts +25 -35
  56. package/src/pythonServer/index.ts +9 -5
  57. package/src/pythonServer/kernelExecutor.ts +8 -147
  58. package/src/pythonServer/shiny/shinyServer.ts +62 -0
  59. package/src/pythonServer/starlette/starletteServer.ts +59 -0
  60. package/src/pythonServer/streamlit/streamlitServer.ts +40 -62
  61. package/src/pythonServer/tornado/tornadoServer.ts +33 -60
  62. package/src/pythonWidget/comm.ts +65 -0
  63. package/src/pythonWidget/pythonWidget.ts +19 -1
  64. package/src/pythonWidget/pythonWidgetModel.ts +107 -20
  65. package/src/sandpackWidget/sandpackFilesModel.ts +17 -3
  66. package/src/sandpackWidget/sandpackPanel.ts +45 -4
  67. package/src/swConnection/mainConnectionManager.ts +28 -20
  68. package/src/token.ts +5 -1
  69. package/src/tools.ts +31 -0
  70. package/src/type.ts +46 -7
  71. package/src/websocket/websocket.ts +9 -1
  72. package/style/base.css +7 -0
  73. package/style/icons/autoreload.svg +16 -0
  74. package/style/icons/box.svg +12 -0
  75. package/style/icons/externallink.svg +10 -0
  76. package/lib/pythonServer/common/generatedPythonFiles.d.ts +0 -2
  77. package/lib/pythonServer/common/generatedPythonFiles.js +0 -72
  78. package/lib/pythonServer/dash/generatedPythonFiles.d.ts +0 -2
  79. package/lib/pythonServer/dash/generatedPythonFiles.js +0 -31
  80. package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +0 -2
  81. package/lib/pythonServer/streamlit/generatedPythonFiles.js +0 -147
  82. package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +0 -3
  83. package/lib/pythonServer/tornado/generatedPythonFiles.js +0 -456
  84. package/src/pythonServer/common/generatedPythonFiles.ts +0 -73
  85. package/src/pythonServer/dash/generatedPythonFiles.ts +0 -32
  86. package/src/pythonServer/streamlit/generatedPythonFiles.ts +0 -148
  87. package/src/pythonServer/tornado/generatedPythonFiles.ts +0 -457
@@ -1,139 +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
- import { patch } from './common/generatedPythonFiles';
13
3
 
14
- export abstract class KernelExecutor implements IKernelExecutor {
4
+ import { IKernelExecutor } from '../type';
5
+
6
+ export class KernelExecutor implements IKernelExecutor {
15
7
  constructor(options: KernelExecutor.IOptions) {
16
8
  this._sessionConnection = options.sessionConnection;
17
- this._wsPatch = websocketPatch.replaceAll('"use strict";', '');
18
9
  }
19
10
 
20
11
  get isDisposed(): boolean {
21
12
  return this._isDisposed;
22
13
  }
23
14
 
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
15
  async executeCode(
138
16
  code: KernelMessage.IExecuteRequestMsg['content'],
139
17
  waitForResult?: boolean
@@ -160,10 +38,13 @@ export abstract class KernelExecutor implements IKernelExecutor {
160
38
  }
161
39
  case 'stream': {
162
40
  const content = (msg as KernelMessage.IStreamMsg).content;
41
+ if (content.text.length === 0) {
42
+ break;
43
+ }
163
44
  if (content.name === 'stderr') {
164
- console.error('Kernel stream', content.text);
45
+ console.error('Kernel stream:', content.text);
165
46
  } else {
166
- console.log('Kernel stream', content.text);
47
+ console.log('Kernel stream:', content.text);
167
48
  }
168
49
  break;
169
50
  }
@@ -198,28 +79,8 @@ export abstract class KernelExecutor implements IKernelExecutor {
198
79
  this._sessionConnection.dispose();
199
80
  }
200
81
 
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
82
  private _isDisposed: boolean = false;
221
83
  private _sessionConnection: Session.ISessionConnection;
222
- private _wsPatch: string;
223
84
  }
224
85
 
225
86
  export namespace KernelExecutor {
@@ -0,0 +1,62 @@
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+
4
+ export class ShinyServer extends BasePythonServer {
5
+ async init(options: {
6
+ entryPath?: string;
7
+ initCode?: string;
8
+ instanceId: string;
9
+ kernelClientId: string;
10
+ }) {
11
+ await super.init(options);
12
+ const { instanceId, kernelClientId, entryPath } = options;
13
+ const baseURL = this.buildBaseURL({
14
+ instanceId,
15
+ kernelClientId,
16
+ framework: JupyterPackFramework.SHINY
17
+ });
18
+ const bootstrapCode = `
19
+ from jupyterpack.common import set_base_url_env
20
+ set_base_url_env("${baseURL}")
21
+ from jupyterpack.shiny import patch_shiny
22
+ patch_shiny()
23
+ `;
24
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
25
+ if (entryPath) {
26
+ const loaderCode = `
27
+ from jupyterpack.shiny import ShinyServer, get_shiny_app
28
+
29
+
30
+ ${this._server_var} = ShinyServer(get_shiny_app("${entryPath}"), "${baseURL}")
31
+ `;
32
+
33
+ await this.kernelExecutor.executeCode({ code: loaderCode });
34
+ }
35
+ }
36
+
37
+ async disposePythonServer(): Promise<void> {
38
+ await this.kernelExecutor.executeCode({
39
+ code: `${this._server_var}.dispose()`
40
+ });
41
+ for (const element of this._openedWebsockets) {
42
+ await this.closeWebsocket(element);
43
+ }
44
+ }
45
+
46
+ async reloadPythonServer(options: {
47
+ entryPath?: string;
48
+ initCode?: string;
49
+ }): Promise<void> {
50
+ const { entryPath } = options;
51
+ if (entryPath) {
52
+ const reloadCode = `
53
+ from jupyterpack.shiny import get_shiny_app
54
+
55
+ await ${this._server_var}.dispose()
56
+ ${this._server_var}.reload(get_shiny_app("${entryPath}"))
57
+ `;
58
+
59
+ await this.kernelExecutor.executeCode({ code: reloadCode }, true);
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,59 @@
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+
4
+ export class StarletteServer extends BasePythonServer {
5
+ async init(options: {
6
+ initCode?: string;
7
+ instanceId: string;
8
+ kernelClientId: string;
9
+ }) {
10
+ await super.init(options);
11
+ const { initCode, instanceId, kernelClientId } = options;
12
+ const baseURL = this.buildBaseURL({
13
+ instanceId,
14
+ kernelClientId,
15
+ framework: JupyterPackFramework.STARLETTE
16
+ });
17
+ const bootstrapCode = `
18
+ from jupyterpack.common import set_base_url_env
19
+ set_base_url_env("${baseURL}")
20
+ `;
21
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
22
+ if (initCode) {
23
+ const initCodeWithUrl = initCode.replaceAll('{{base_url}}', baseURL);
24
+ await this.kernelExecutor.executeCode({ code: initCodeWithUrl });
25
+ const loaderCode = `
26
+ from jupyterpack.asgi import AsgiServer
27
+ ${this._server_var} = AsgiServer(app, "${baseURL}")
28
+ `;
29
+
30
+ await this.kernelExecutor.executeCode({ code: loaderCode });
31
+ }
32
+ }
33
+
34
+ async disposePythonServer(): Promise<void> {
35
+ await this.kernelExecutor.executeCode({
36
+ code: `${this._server_var}.dispose()`
37
+ });
38
+ for (const element of this._openedWebsockets) {
39
+ await this.closeWebsocket(element);
40
+ }
41
+ }
42
+
43
+ async reloadPythonServer(options: {
44
+ entryPath?: string;
45
+ initCode?: string;
46
+ }): Promise<void> {
47
+ const { initCode } = options;
48
+ if (initCode) {
49
+ await this.kernelExecutor.executeCode({
50
+ code: initCode.replaceAll('{{base_url}}', this._baseUrl ?? '')
51
+ });
52
+ const reloadCode = `
53
+ await ${this._server_var}.dispose()
54
+ ${this._server_var}.reload(app)
55
+ `;
56
+ await this.kernelExecutor.executeCode({ code: reloadCode }, true);
57
+ }
58
+ }
59
+ }
@@ -1,21 +1,13 @@
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';
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { TornadoServer } from '../tornado/tornadoServer';
10
3
 
11
- export class StreamlitServer extends KernelExecutor {
4
+ export class StreamlitServer extends TornadoServer {
12
5
  async init(options: {
13
6
  entryPath?: string;
14
7
  initCode?: string;
15
8
  instanceId: string;
16
9
  kernelClientId: string;
17
10
  }) {
18
- await super.init(options);
19
11
  const { instanceId, kernelClientId, entryPath } = options;
20
12
  if (!entryPath) {
21
13
  throw new Error(
@@ -27,61 +19,47 @@ export class StreamlitServer extends KernelExecutor {
27
19
  kernelClientId,
28
20
  framework: JupyterPackFramework.STREAMLIT
29
21
  });
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
22
 
36
- const stCode = streamlitLoader
37
- .replaceAll('{{base_url}}', baseURL)
38
- .replaceAll('{{script_path}}', entryPath);
39
- await this.executeCode({ code: stCode });
40
- }
23
+ const patchCode = `
24
+ from jupyterpack.common import set_base_url_env, patch_tornado, patch_all
25
+ patch_all()
26
+ patch_tornado()
27
+ set_base_url_env("${baseURL}")
28
+ `;
29
+ await this.kernelExecutor.executeCode({ code: patchCode });
41
30
 
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
- }
31
+ const bootstrapCode = `
32
+ from jupyterpack.streamlit import patch_streamlit
33
+ patch_streamlit()
34
+ `;
35
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
53
36
 
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;
37
+ const stCode = `
38
+ from jupyterpack.streamlit import StreamlitServer, create_streamlit_app
39
+ __jupyterpack_st_server, __jupyterpack_tor_app = await create_streamlit_app("${entryPath}", "${baseURL}")
40
+ ${this._server_var} = StreamlitServer(__jupyterpack_tor_app, "${baseURL}", __jupyterpack_st_server)
41
+ `;
42
+ await this.kernelExecutor.executeCode({ code: stCode });
64
43
  }
65
44
 
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
- });
45
+ async reloadPythonServer(options: {
46
+ entryPath?: string;
47
+ initCode?: string;
48
+ }): Promise<void> {
49
+ const { entryPath } = options;
50
+ if (!entryPath || !this._baseUrl) {
51
+ return;
52
+ }
53
+ const reloadCode = `
54
+ ${this._server_var}.dispose()
55
+ __jupyterpack_st_server, __jupyterpack_tor_app = await create_streamlit_app("${entryPath}", "${this._baseUrl}")
56
+ ${this._server_var}.reload(__jupyterpack_tor_app, __jupyterpack_st_server)
57
+ `;
58
+ await this.kernelExecutor.executeCode(
59
+ {
60
+ code: reloadCode
61
+ },
62
+ true
63
+ );
81
64
  }
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
65
  }
@@ -1,13 +1,7 @@
1
- import { stringOrNone } from '../../tools';
2
- import { IDict, JupyterPackFramework } from '../../type';
3
- import { tools } from '../common/generatedPythonFiles';
4
- import { KernelExecutor } from '../kernelExecutor';
5
- import {
6
- bootstrap,
7
- tornadoBridge,
8
- tornadoLoader
9
- } from './generatedPythonFiles';
10
- export class TornadoServer extends KernelExecutor {
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+
4
+ export class TornadoServer extends BasePythonServer {
11
5
  async init(options: {
12
6
  initCode?: string;
13
7
  instanceId: string;
@@ -21,60 +15,39 @@ export class TornadoServer extends KernelExecutor {
21
15
  kernelClientId,
22
16
  framework: JupyterPackFramework.TORNADO
23
17
  });
24
- await this.executeCode({ code: tools.replaceAll('{{base_url}}', baseURL) });
25
- await this.executeCode({ code: bootstrap });
26
- await this.executeCode({ code: tornadoBridge });
18
+ const bootstrapCode = `
19
+ from jupyterpack.common import set_base_url_env, patch_tornado
20
+ set_base_url_env("${baseURL}")
21
+ patch_tornado()
22
+
23
+ `;
24
+ await this.kernelExecutor.executeCode({ code: bootstrapCode });
27
25
  if (initCode) {
28
26
  const initCodeWithUrl = initCode.replaceAll('{{base_url}}', baseURL);
29
- await this.executeCode({ code: initCodeWithUrl });
30
- const torCode = tornadoLoader.replaceAll('{{base_url}}', baseURL);
31
- await this.executeCode({ code: torCode });
32
- }
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 = `await ${this._GET_RESPONSE_FUNCTION}("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${stringOrNone(content)}, params=${stringOrNone(params)})`;
44
- return code;
45
- }
27
+ await this.kernelExecutor.executeCode({ code: initCodeWithUrl });
28
+ const loaderCode = `
29
+ from jupyterpack.tornado import TornadoServer
30
+ ${this._server_var} = TornadoServer(app, "${baseURL}")
31
+ `;
46
32
 
47
- openWebsocketFunctionFactory(options: {
48
- instanceId: string;
49
- kernelId: string;
50
- wsUrl: string;
51
- protocol?: string;
52
- }): string {
53
- const { instanceId, kernelId, wsUrl, protocol } = options;
54
-
55
- const code = `await ${this._OPEN_WEBSOCKET_FUNCTION}("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
56
- return code;
57
- }
58
-
59
- sendWebsocketMessageFunctionFactory(options: {
60
- instanceId: string;
61
- kernelId: string;
62
- wsUrl: string;
63
- message: string;
64
- }): string {
65
- const { instanceId, kernelId, wsUrl, message } = options;
66
- const code = `await ${this._SEND_WEBSOCKET_FUNCTION}("${instanceId}", "${kernelId}", "${wsUrl}", '''${message}''')`;
67
- return code;
33
+ await this.kernelExecutor.executeCode({ code: loaderCode });
34
+ }
68
35
  }
69
36
 
70
- async disposePythonServer(): Promise<void> {
71
- await this.executeCode({
72
- code: '__jupyterpack_tornado_dispose()'
73
- });
37
+ async reloadPythonServer(options: {
38
+ entryPath?: string;
39
+ initCode?: string;
40
+ }): Promise<void> {
41
+ const { initCode } = options;
42
+ if (initCode) {
43
+ await this.kernelExecutor.executeCode({
44
+ code: initCode.replaceAll('{{base_url}}', this._baseUrl ?? '')
45
+ });
46
+ const reloadCode = `
47
+ ${this._server_var}.dispose()
48
+ ${this._server_var}.reload(app)
49
+ `;
50
+ await this.kernelExecutor.executeCode({ code: reloadCode }, true);
51
+ }
74
52
  }
75
-
76
- private _GET_RESPONSE_FUNCTION = '__jupyterpack_tornado_get_response';
77
- private _OPEN_WEBSOCKET_FUNCTION = '__jupyterpack_tornado_open_ws';
78
-
79
- private _SEND_WEBSOCKET_FUNCTION = '__jupyterpack_tornado_receive_ws_message';
80
53
  }
@@ -0,0 +1,65 @@
1
+ import { Kernel, KernelMessage } from '@jupyterlab/services';
2
+
3
+ const COMM_NAME = 'jupyterpack:broadcast:comm';
4
+
5
+ export class CommBroadcastManager {
6
+ constructor() {
7
+ this._kernels = new Map();
8
+ this._comms = new Map();
9
+ }
10
+
11
+ registerKernel(kernel: Kernel.IKernelConnection) {
12
+ this._kernels.set(kernel.id, kernel);
13
+ kernel.registerCommTarget(COMM_NAME, (comm, msg) =>
14
+ this._handle_comm_open(comm, msg, kernel.id)
15
+ );
16
+ }
17
+ unregisterKernel(kernelId?: string) {
18
+ if (kernelId) {
19
+ this._kernels.delete(kernelId);
20
+ const comms = this._comms.get(kernelId) ?? [];
21
+ comms.forEach(comm => comm.dispose());
22
+ this._comms.delete(kernelId);
23
+ }
24
+ }
25
+
26
+ private _handle_comm_open = async (
27
+ comm: Kernel.IComm,
28
+ msg: KernelMessage.ICommOpenMsg,
29
+ kernelId: string
30
+ ): Promise<void> => {
31
+ if (this._comms.has(kernelId)) {
32
+ this._comms.get(kernelId)?.push(comm);
33
+ } else {
34
+ this._comms.set(kernelId, [comm]);
35
+ }
36
+ const channelName = msg.metadata.channel_name as string | undefined;
37
+ if (!channelName) {
38
+ return;
39
+ }
40
+ if (!this._broadcastChannels.has(channelName)) {
41
+ this._broadcastChannels.set(
42
+ channelName,
43
+ new BroadcastChannel(channelName)
44
+ );
45
+ }
46
+ const broadcastChannel = this._broadcastChannels.get(channelName)!;
47
+ comm.onMsg = commMsg => {
48
+ const { data } = commMsg.content;
49
+ broadcastChannel.postMessage(data);
50
+ };
51
+ };
52
+
53
+ dispose() {
54
+ this._kernels.clear();
55
+ this._comms.clear();
56
+ this._broadcastChannels.forEach(it => {
57
+ it.close();
58
+ });
59
+ this._broadcastChannels.clear();
60
+ }
61
+
62
+ private _kernels: Map<string, Kernel.IKernelConnection> = new Map();
63
+ private _comms: Map<string, Kernel.IComm[]> = new Map();
64
+ private _broadcastChannels: Map<string, BroadcastChannel> = new Map();
65
+ }
@@ -1,6 +1,7 @@
1
1
  import { PythonWidgetModel } from './pythonWidgetModel';
2
2
  import { PageConfig, URLExt } from '@jupyterlab/coreutils';
3
3
  import { IFramePanel } from '../document/iframePanel';
4
+ import { PromiseDelegate } from '@lumino/coreutils';
4
5
 
5
6
  export class PythonWidget extends IFramePanel {
6
7
  constructor(options: PythonWidget.IOptions) {
@@ -12,6 +13,7 @@ export class PythonWidget extends IFramePanel {
12
13
  this._iframe.contentDocument!.body.innerText = `Failed to start server: ${connectionData.error}`;
13
14
  return;
14
15
  }
16
+ this._isReady.resolve();
15
17
  const iframe = this._iframe;
16
18
  const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
17
19
 
@@ -23,24 +25,40 @@ export class PythonWidget extends IFramePanel {
23
25
  connectionData.kernelClientId,
24
26
  connectionData.rootUrl
25
27
  );
26
-
27
28
  iframe.src = iframeUrl;
28
29
 
29
30
  iframe.addEventListener('load', () => {
30
31
  this.toggleSpinner(false);
31
32
  });
33
+ this._model.serverReloaded.connect(() => {
34
+ this._iframe?.contentWindow?.location?.reload();
35
+ });
32
36
  });
33
37
  }
34
38
 
39
+ get autoreload() {
40
+ return this._model.autoreload;
41
+ }
42
+ set autoreload(value: boolean) {
43
+ this._model.autoreload = value;
44
+ }
45
+ get isReady(): Promise<void> {
46
+ return this._isReady.promise;
47
+ }
35
48
  get model(): PythonWidgetModel {
36
49
  return this._model;
37
50
  }
38
51
 
52
+ async reload(): Promise<void> {
53
+ await this._model.reload();
54
+ }
55
+
39
56
  dispose(): void {
40
57
  this._model.dispose();
41
58
  }
42
59
 
43
60
  private _model: PythonWidgetModel;
61
+ private _isReady = new PromiseDelegate<void>();
44
62
  }
45
63
 
46
64
  export namespace PythonWidget {