jupyterpack 0.3.0 → 0.4.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 (68) hide show
  1. package/lib/document/commands.d.ts +8 -0
  2. package/lib/document/commands.js +76 -0
  3. package/lib/document/iframePanel.d.ts +4 -1
  4. package/lib/document/plugin.d.ts +2 -1
  5. package/lib/document/plugin.js +22 -4
  6. package/lib/document/toolbar.d.ts +9 -0
  7. package/lib/document/toolbar.js +20 -0
  8. package/lib/document/widgetFactory.d.ts +2 -1
  9. package/lib/document/widgetFactory.js +11 -5
  10. package/lib/index.d.ts +1 -1
  11. package/lib/pythonServer/dash/dashServer.d.ts +4 -6
  12. package/lib/pythonServer/dash/dashServer.js +18 -10
  13. package/lib/pythonServer/kernelExecutor.d.ts +5 -0
  14. package/lib/pythonServer/kernelExecutor.js +13 -6
  15. package/lib/pythonServer/streamlit/streamlitServer.d.ts +7 -26
  16. package/lib/pythonServer/streamlit/streamlitServer.js +32 -36
  17. package/lib/pythonServer/tornado/tornadoServer.d.ts +5 -3
  18. package/lib/pythonServer/tornado/tornadoServer.js +31 -14
  19. package/lib/pythonWidget/comm.d.ts +11 -0
  20. package/lib/pythonWidget/comm.js +52 -0
  21. package/lib/pythonWidget/pythonWidget.d.ts +5 -0
  22. package/lib/pythonWidget/pythonWidget.js +19 -0
  23. package/lib/pythonWidget/pythonWidgetModel.d.ts +16 -4
  24. package/lib/pythonWidget/pythonWidgetModel.js +77 -13
  25. package/lib/sandpackWidget/sandpackFilesModel.d.ts +6 -2
  26. package/lib/sandpackWidget/sandpackFilesModel.js +13 -2
  27. package/lib/sandpackWidget/sandpackPanel.d.ts +10 -1
  28. package/lib/sandpackWidget/sandpackPanel.js +38 -3
  29. package/lib/token.d.ts +2 -1
  30. package/lib/token.js +1 -0
  31. package/lib/tools.d.ts +5 -0
  32. package/lib/tools.js +17 -0
  33. package/lib/type.d.ts +27 -1
  34. package/package.json +6 -6
  35. package/src/document/commands.ts +91 -0
  36. package/src/document/iframePanel.ts +4 -1
  37. package/src/document/plugin.ts +28 -7
  38. package/src/document/toolbar.ts +39 -0
  39. package/src/document/widgetFactory.ts +13 -5
  40. package/src/global.d.ts +5 -0
  41. package/src/pythonServer/dash/dashServer.ts +23 -14
  42. package/src/pythonServer/kernelExecutor.ts +21 -7
  43. package/src/pythonServer/streamlit/streamlitServer.ts +41 -61
  44. package/src/pythonServer/tornado/tornadoServer.ts +35 -18
  45. package/src/pythonWidget/comm.ts +65 -0
  46. package/src/pythonWidget/pythonWidget.ts +19 -1
  47. package/src/pythonWidget/pythonWidgetModel.ts +105 -18
  48. package/src/sandpackWidget/sandpackFilesModel.ts +17 -3
  49. package/src/sandpackWidget/sandpackPanel.ts +45 -4
  50. package/src/token.ts +5 -1
  51. package/src/tools.ts +22 -0
  52. package/src/type.ts +29 -1
  53. package/style/base.css +7 -0
  54. package/style/icons/autoreload.svg +16 -0
  55. package/style/icons/box.svg +12 -0
  56. package/style/icons/externallink.svg +10 -0
  57. package/lib/pythonServer/common/generatedPythonFiles.d.ts +0 -2
  58. package/lib/pythonServer/common/generatedPythonFiles.js +0 -72
  59. package/lib/pythonServer/dash/generatedPythonFiles.d.ts +0 -2
  60. package/lib/pythonServer/dash/generatedPythonFiles.js +0 -31
  61. package/lib/pythonServer/streamlit/generatedPythonFiles.d.ts +0 -2
  62. package/lib/pythonServer/streamlit/generatedPythonFiles.js +0 -147
  63. package/lib/pythonServer/tornado/generatedPythonFiles.d.ts +0 -3
  64. package/lib/pythonServer/tornado/generatedPythonFiles.js +0 -456
  65. package/src/pythonServer/common/generatedPythonFiles.ts +0 -73
  66. package/src/pythonServer/dash/generatedPythonFiles.ts +0 -32
  67. package/src/pythonServer/streamlit/generatedPythonFiles.ts +0 -148
  68. package/src/pythonServer/tornado/generatedPythonFiles.ts +0 -457
@@ -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 {
@@ -7,31 +7,64 @@ import {
7
7
  Session
8
8
  } from '@jupyterlab/services';
9
9
  import { PromiseDelegate } from '@lumino/coreutils';
10
- import { IDisposable } from '@lumino/disposable';
11
10
 
12
11
  import { PYTHON_SERVER } from '../pythonServer';
12
+ import { Signal } from '@lumino/signaling';
13
13
  import {
14
14
  IConnectionManager,
15
15
  IJupyterPackFileFormat,
16
16
  IKernelExecutor,
17
+ IPythonWidgetModel,
17
18
  JupyterPackFramework
18
19
  } from '../type';
20
+ import { CommBroadcastManager } from './comm';
21
+ import { IS_LITE } from '../tools';
19
22
 
20
- export class PythonWidgetModel implements IDisposable {
23
+ export class PythonWidgetModel implements IPythonWidgetModel {
21
24
  constructor(options: PythonWidgetModel.IOptions) {
22
25
  this._context = options.context;
23
26
  this._manager = options.manager;
24
27
  this._connectionManager = options.connectionManager;
25
28
  this._contentsManager = options.contentsManager;
26
29
  this._jpackModel = options.jpackModel;
30
+ this._localPath = PathExt.dirname(this._context.localPath);
31
+ this._autoreload = Boolean(this._jpackModel?.metadata?.autoreload);
32
+
33
+ this._contentsManager.fileChanged.connect(this._onFileChanged, this);
34
+ }
35
+
36
+ get autoreload() {
37
+ return this._autoreload;
27
38
  }
28
39
 
40
+ set autoreload(val: boolean) {
41
+ this._autoreload = val;
42
+ }
29
43
  get isDisposed(): boolean {
30
44
  return this._isDisposed;
31
45
  }
32
46
  get connectionManager(): IConnectionManager {
33
47
  return this._connectionManager;
34
48
  }
49
+ get serverReloaded() {
50
+ return this._serverReloaded;
51
+ }
52
+ get kernelStatusChanged() {
53
+ return this._kernelStatusChanged;
54
+ }
55
+
56
+ async reload() {
57
+ if (!this._kernelStarted) {
58
+ return;
59
+ }
60
+ const { spkContent, entryContent } = await this._loadData();
61
+ await this._executor?.reloadPythonServer({
62
+ entryPath: spkContent.entry,
63
+ initCode: entryContent.content
64
+ });
65
+ this._serverReloaded.emit();
66
+ }
67
+
35
68
  async initialize(): Promise<
36
69
  | {
37
70
  success: true;
@@ -48,15 +81,9 @@ export class PythonWidgetModel implements IDisposable {
48
81
  error: 'Server is called twice'
49
82
  };
50
83
  }
51
- const filePath = this._context.localPath;
52
- const spkContent = this._jpackModel;
53
84
 
54
- const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
55
- const rootUrl = spkContent.rootUrl ?? '/';
56
- const entryContent = await this._contentsManager.get(entryPath, {
57
- content: true,
58
- format: 'text'
59
- });
85
+ const { filePath, spkContent, rootUrl, entryContent } =
86
+ await this._loadData();
60
87
  const sessionManager = this._manager.sessions;
61
88
  await sessionManager.ready;
62
89
  await this._manager.kernelspecs.ready;
@@ -73,14 +100,29 @@ export class PythonWidgetModel implements IDisposable {
73
100
  kernelName = specs.default;
74
101
  }
75
102
 
76
- this._sessionConnection = await sessionManager.startNew({
77
- name: filePath,
78
- path: filePath,
79
- kernel: {
80
- name: kernelName
103
+ this._sessionConnection = await sessionManager.startNew(
104
+ {
105
+ name: filePath,
106
+ path: filePath,
107
+ kernel: {
108
+ name: kernelName
109
+ },
110
+ type: 'notebook'
81
111
  },
82
- type: 'notebook'
83
- });
112
+ {
113
+ kernelConnectionOptions: { handleComms: true }
114
+ }
115
+ );
116
+ const kernel = this._sessionConnection.kernel;
117
+ if (kernel) {
118
+ this._kernelStatusChanged.emit('started');
119
+ this._commBroadcastManager.registerKernel(kernel);
120
+ kernel.disposed.connect(() => {
121
+ this._kernelStatusChanged.emit('stopped');
122
+ this._commBroadcastManager.unregisterKernel(kernel.id);
123
+ });
124
+ }
125
+
84
126
  const framework = spkContent.framework;
85
127
  const ServerClass = PYTHON_SERVER.get(framework);
86
128
  if (!ServerClass) {
@@ -111,14 +153,48 @@ export class PythonWidgetModel implements IDisposable {
111
153
  this._kernelStarted = true;
112
154
  return { ...data, rootUrl, framework, success: true };
113
155
  }
114
- dispose(): void {
156
+ async dispose(): Promise<void> {
115
157
  if (this._isDisposed) {
116
158
  return;
117
159
  }
160
+ if (!IS_LITE) {
161
+ this._sessionConnection?.kernel?.shutdown();
162
+ }
118
163
  void this._executor?.disposePythonServer();
164
+ this._contentsManager.fileChanged.disconnect(this._onFileChanged);
165
+ this._commBroadcastManager.dispose();
119
166
  this._isDisposed = true;
120
167
  }
121
168
 
169
+ private async _loadData() {
170
+ const filePath = this._context.localPath;
171
+ const spkContent = this._jpackModel;
172
+
173
+ const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
174
+ const rootUrl = spkContent.rootUrl ?? '/';
175
+ const entryContent = await this._contentsManager.get(entryPath, {
176
+ content: true,
177
+ format: 'text'
178
+ });
179
+ return { filePath, spkContent, rootUrl, entryContent };
180
+ }
181
+
182
+ private async _onFileChanged(
183
+ sender: Contents.IManager,
184
+ args: Contents.IChangedArgs
185
+ ) {
186
+ if (this._autoreload && args.type === 'save') {
187
+ if (
188
+ args.newValue?.path &&
189
+ args.newValue.path.startsWith(this._localPath)
190
+ ) {
191
+ await this.reload();
192
+ }
193
+ }
194
+ }
195
+
196
+ private _commBroadcastManager = new CommBroadcastManager();
197
+
122
198
  private _isDisposed = false;
123
199
  private _kernelStarted = false;
124
200
  private _sessionConnection: Session.ISessionConnection | undefined;
@@ -128,6 +204,17 @@ export class PythonWidgetModel implements IDisposable {
128
204
  private _contentsManager: Contents.IManager;
129
205
  private _jpackModel: IJupyterPackFileFormat;
130
206
  private _executor?: IKernelExecutor;
207
+ private _localPath: string;
208
+
209
+ private _serverReloaded: Signal<IPythonWidgetModel, void> = new Signal<
210
+ IPythonWidgetModel,
211
+ void
212
+ >(this);
213
+ private _kernelStatusChanged: Signal<
214
+ IPythonWidgetModel,
215
+ 'started' | 'stopped'
216
+ > = new Signal(this);
217
+ private _autoreload: boolean;
131
218
  }
132
219
 
133
220
  export namespace PythonWidgetModel {
@@ -3,16 +3,17 @@ import { ISignal, Signal } from '@lumino/signaling';
3
3
 
4
4
  import { IDict } from '../type';
5
5
  import { removePrefix } from '../tools';
6
+ import { IDisposable } from '@lumino/disposable';
6
7
 
7
- export class SandpackFilesModel {
8
+ export class SandpackFilesModel implements IDisposable {
8
9
  constructor(options: { contentsManager: Contents.IManager; path: string }) {
9
10
  this._contentManager = options.contentsManager;
10
11
  this._path = options.path;
11
12
  this._contentManager.fileChanged.connect(this._onFileChanged, this);
12
13
  }
13
14
 
14
- async getAllFiles(): Promise<IDict<{ code: string }>> {
15
- if (!this._allFiles) {
15
+ async getAllFiles(force = false): Promise<IDict<{ code: string }>> {
16
+ if (!this._allFiles || force) {
16
17
  const files = await this._contentManager.get(this._path, {
17
18
  content: true
18
19
  });
@@ -21,6 +22,9 @@ export class SandpackFilesModel {
21
22
 
22
23
  return this._allFiles;
23
24
  }
25
+ get isDisposed(): boolean {
26
+ return this._isDisposed;
27
+ }
24
28
 
25
29
  get fileChanged(): ISignal<
26
30
  SandpackFilesModel,
@@ -29,6 +33,14 @@ export class SandpackFilesModel {
29
33
  return this._fileChanged;
30
34
  }
31
35
 
36
+ dispose(): void {
37
+ if (this._isDisposed) {
38
+ return;
39
+ }
40
+ this._isDisposed = true;
41
+ this._contentManager.fileChanged.disconnect(this._onFileChanged);
42
+ }
43
+
32
44
  async flattenDirectory(
33
45
  dirContent: Contents.IModel
34
46
  ): Promise<IDict<{ code: string }>> {
@@ -138,4 +150,6 @@ export class SandpackFilesModel {
138
150
  private _contentManager: Contents.IManager;
139
151
 
140
152
  private _allFiles?: IDict<{ code: string }>;
153
+
154
+ private _isDisposed = false;
141
155
  }
@@ -7,8 +7,9 @@ import { DocumentRegistry } from '@jupyterlab/docregistry';
7
7
  import { Contents } from '@jupyterlab/services';
8
8
 
9
9
  import { IFramePanel } from '../document/iframePanel';
10
- import { IDict } from '../type';
10
+ import { IDict, IJupyterPackFileFormat } from '../type';
11
11
  import { SandpackFilesModel } from './sandpackFilesModel';
12
+ import { PromiseDelegate } from '@lumino/coreutils';
12
13
 
13
14
  export class SandpackPanel extends IFramePanel {
14
15
  constructor(options: {
@@ -17,17 +18,44 @@ export class SandpackPanel extends IFramePanel {
17
18
  }) {
18
19
  super();
19
20
  this._contentsManager = options.contentsManager;
21
+ const { context } = options;
20
22
  options.context.ready.then(async () => {
21
- await this.init(options.context.localPath);
23
+ const jpackModel =
24
+ context.model.toJSON() as any as IJupyterPackFileFormat;
25
+ await this.init(context.localPath, jpackModel);
22
26
  });
23
27
  }
24
28
 
25
- async init(localPath: string) {
29
+ dispose(): void {
30
+ this._fileModel?.fileChanged.disconnect(this._onFileChanged);
31
+ this._fileModel?.dispose();
32
+ this._spClient?.destroy();
33
+ super.dispose();
34
+ }
35
+
36
+ get isReady() {
37
+ return this._isReady.promise;
38
+ }
39
+
40
+ get autoreload(): boolean {
41
+ return this._autoreload;
42
+ }
43
+
44
+ set autoreload(val: boolean) {
45
+ this._autoreload = val;
46
+ }
47
+
48
+ async init(localPath: string, jpackModel: IJupyterPackFileFormat) {
49
+ if (jpackModel?.metadata?.autoreload === true) {
50
+ this._autoreload = true;
51
+ }
52
+
26
53
  const currentDir = localPath.split('/').slice(0, -1).join('/');
27
54
  const filesModel = new SandpackFilesModel({
28
55
  contentsManager: this._contentsManager,
29
56
  path: currentDir
30
57
  });
58
+ this._fileModel = filesModel;
31
59
  const allFiles = await filesModel.getAllFiles();
32
60
 
33
61
  const options: ClientOptions = {
@@ -43,6 +71,7 @@ export class SandpackPanel extends IFramePanel {
43
71
  options
44
72
  );
45
73
  await this.connectSignals(filesModel, this._spClient);
74
+ this._isReady.resolve();
46
75
  }
47
76
 
48
77
  async connectSignals(
@@ -66,11 +95,20 @@ export class SandpackPanel extends IFramePanel {
66
95
  });
67
96
  }
68
97
 
98
+ async reload(): Promise<void> {
99
+ if (this._spClient && this._fileModel) {
100
+ const allFiles = await this._fileModel.getAllFiles();
101
+ this._spClient.updateSandbox({
102
+ files: allFiles
103
+ });
104
+ }
105
+ }
106
+
69
107
  private _onFileChanged(
70
108
  sender: SandpackFilesModel,
71
109
  { allFiles }: { allFiles: IDict<{ code: string }> }
72
110
  ) {
73
- if (this._spClient) {
111
+ if (this._autoreload && this._spClient) {
74
112
  this._spClient.updateSandbox({
75
113
  files: allFiles
76
114
  });
@@ -79,4 +117,7 @@ export class SandpackPanel extends IFramePanel {
79
117
 
80
118
  private _spClient?: SandpackClient;
81
119
  private _contentsManager: Contents.IManager;
120
+ private _fileModel: SandpackFilesModel | undefined;
121
+ private _autoreload = false;
122
+ private _isReady = new PromiseDelegate<void>();
82
123
  }
package/src/token.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { Token } from '@lumino/coreutils';
2
- import { IConnectionManager } from './type';
2
+ import { IConnectionManager, IJupyterpackDocTracker } from './type';
3
3
 
4
4
  export const IConnectionManagerToken = new Token<IConnectionManager>(
5
5
  'jupyterpack:connection-manager'
6
6
  );
7
+
8
+ export const IJupyterpackDocTrackerToken = new Token<IJupyterpackDocTracker>(
9
+ 'jupyterpack:dock-tracker'
10
+ );
package/src/tools.ts CHANGED
@@ -1,3 +1,25 @@
1
+ import logoStr from '../style/icons/box.svg';
2
+ import autoReloadStr from '../style/icons/autoreload.svg';
3
+ import linkStr from '../style/icons/externallink.svg';
4
+ import { LabIcon } from '@jupyterlab/ui-components';
5
+
6
+ export const IS_LITE = !!document.getElementById('jupyter-lite-main');
7
+
8
+ export const logoIcon = new LabIcon({
9
+ name: 'jupyterpack:logo',
10
+ svgstr: logoStr
11
+ });
12
+
13
+ export const autoReloadIcon = new LabIcon({
14
+ name: 'jupyterpack:autoReload',
15
+ svgstr: autoReloadStr
16
+ });
17
+
18
+ export const linkIcon = new LabIcon({
19
+ name: 'jupyterpack:externalLink',
20
+ svgstr: linkStr
21
+ });
22
+
1
23
  export function removePrefix(path: string, prefix: string): string {
2
24
  if (path.startsWith(prefix)) {
3
25
  return path.slice(prefix.length);
package/src/type.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import { DocumentWidget } from '@jupyterlab/docregistry';
1
2
  import { KernelMessage } from '@jupyterlab/services';
2
3
  import { IDisposable } from '@lumino/disposable';
4
+ import { IWidgetTracker } from '@jupyterlab/apputils';
5
+ import { ISignal } from '@lumino/signaling';
3
6
 
4
7
  export interface IDict<T = any> {
5
8
  [key: string]: T;
@@ -29,7 +32,9 @@ export interface IJupyterPackFileFormat {
29
32
  entry: string;
30
33
  framework: JupyterPackFramework;
31
34
  name?: string;
32
- metadata?: IDict;
35
+ metadata?: {
36
+ autoreload?: boolean;
37
+ };
33
38
  rootUrl?: string;
34
39
  }
35
40
 
@@ -69,6 +74,10 @@ export interface IKernelExecutor extends IDisposable {
69
74
  kernelClientId: string;
70
75
  }): Promise<void>;
71
76
  disposePythonServer(): Promise<void>;
77
+ reloadPythonServer(options: {
78
+ entryPath?: string;
79
+ initCode?: string;
80
+ }): Promise<void>;
72
81
  getResponseFunctionFactory(options: {
73
82
  urlPath: string;
74
83
  method: string;
@@ -86,3 +95,22 @@ export interface IConnectionManager {
86
95
  option: { kernelClientId: string } & IKernelExecutorParams
87
96
  ): Promise<IDict | null>;
88
97
  }
98
+
99
+ export type IJupyterpackDocTracker = IWidgetTracker<DocumentWidget>;
100
+
101
+ export interface IPythonWidgetModel extends IDisposable {
102
+ connectionManager: IConnectionManager;
103
+ serverReloaded: ISignal<IPythonWidgetModel, void>;
104
+ kernelStatusChanged: ISignal<IPythonWidgetModel, 'started' | 'stopped'>;
105
+ reload(): Promise<void>;
106
+ initialize(): Promise<
107
+ | {
108
+ success: true;
109
+ instanceId: string;
110
+ kernelClientId: string;
111
+ rootUrl: string;
112
+ framework: JupyterPackFramework;
113
+ }
114
+ | { success: false; error: string }
115
+ >;
116
+ }
package/style/base.css CHANGED
@@ -49,3 +49,10 @@
49
49
  0 0 0 0 rgb(212 72 13 / 100%);
50
50
  }
51
51
  }
52
+
53
+ .specta-main-content-panel .jupyterpack-toolbar {
54
+ display: none;
55
+ height: 0 !important;
56
+ width: 0 !important;
57
+ min-height: 0 !important;
58
+ }
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+ <svg height="800px" width="800px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
4
+ <g>
5
+ <path d="M403.925,108.102c-27.595-27.595-62.899-47.558-102.459-56.29L304.182,0L201.946,53.867l-27.306,14.454
6
+ l-5.066,2.654l8.076,4.331l38.16,20.542l81.029,43.602l2.277-42.859c28.265,7.546,53.438,22.53,73.623,42.638
7
+ c29.94,29.939,48.358,71.119,48.358,116.776c0,23.407-4.843,45.58-13.575,65.687l40.37,17.532
8
+ c11.076-25.463,17.242-53.637,17.242-83.219C465.212,198.306,441.727,145.904,403.925,108.102z"
9
+ fill="var(--jp-inverse-layout-color3)" />
10
+ <path d="M296.256,416.151l-81.101-43.612l-2.272,42.869c-28.26-7.555-53.51-22.53-73.618-42.636
11
+ c-29.945-29.95-48.364-71.12-48.364-116.767c0-23.427,4.844-45.522,13.576-65.697l-40.37-17.531
12
+ c-11.076,25.53-17.242,53.723-17.242,83.228c0,57.679,23.407,110.157,61.21,147.893c27.595,27.594,62.899,47.548,102.453,56.202
13
+ l-2.716,51.9l102.169-53.878l27.455-14.454l4.988-2.643l-7.999-4.332L296.256,416.151z"
14
+ fill="var(--jp-inverse-layout-color3)" />
15
+ </g>
16
+ </svg>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <path
4
+ d="M17.5777 4.43152L15.5777 3.38197C13.8221 2.46066 12.9443 2 12 2C11.0557 2 10.1779 2.46066 8.42229 3.38197L6.42229 4.43152C4.64855 5.36234 3.6059 5.9095 2.95969 6.64132L12 11.1615L21.0403 6.64132C20.3941 5.9095 19.3515 5.36234 17.5777 4.43152Z"
5
+ fill="var(--jp-inverse-layout-color3)" />
6
+ <path
7
+ d="M21.7484 7.96435L12.75 12.4635V21.904C13.4679 21.7252 14.2848 21.2965 15.5777 20.618L17.5777 19.5685C19.7294 18.4393 20.8052 17.8748 21.4026 16.8603C22 15.8458 22 14.5833 22 12.0585V11.9415C22 10.0489 22 8.86558 21.7484 7.96435Z"
8
+ fill="var(--jp-inverse-layout-color3)" />
9
+ <path
10
+ d="M11.25 21.904V12.4635L2.25164 7.96434C2 8.86557 2 10.0489 2 11.9415V12.0585C2 14.5833 2 15.8458 2.5974 16.8603C3.19479 17.8748 4.27063 18.4393 6.42229 19.5685L8.42229 20.618C9.71524 21.2965 10.5321 21.7252 11.25 21.904Z"
11
+ fill="var(--jp-inverse-layout-color3)" />
12
+ </svg>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+
4
+ <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
5
+ <g>
6
+ <path id="Vector"
7
+ d="M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11"
8
+ stroke="var(--jp-inverse-layout-color3)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
9
+ </g>
10
+ </svg>
@@ -1,2 +0,0 @@
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";
@@ -1,72 +0,0 @@
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
- `;