jupyterpack 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +79 -0
- package/lib/document/iframePanel.d.ts +7 -0
- package/lib/document/iframePanel.js +20 -0
- package/lib/document/jupyterpackDocWidget.d.ts +9 -0
- package/lib/document/jupyterpackDocWidget.js +16 -0
- package/lib/document/plugin.d.ts +2 -0
- package/lib/document/plugin.js +31 -0
- package/lib/document/widgetFactory.d.ts +23 -0
- package/lib/document/widgetFactory.js +60 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -0
- package/lib/pythonWidget/connectionManager.d.ts +18 -0
- package/lib/pythonWidget/connectionManager.js +27 -0
- package/lib/pythonWidget/kernelExecutor.d.ts +27 -0
- package/lib/pythonWidget/kernelExecutor.js +104 -0
- package/lib/pythonWidget/pythonWidget.d.ts +13 -0
- package/lib/pythonWidget/pythonWidget.js +22 -0
- package/lib/pythonWidget/pythonWidgetModel.d.ts +29 -0
- package/lib/pythonWidget/pythonWidgetModel.js +75 -0
- package/lib/sandpackWidget/sandpackFilesModel.d.ts +27 -0
- package/lib/sandpackWidget/sandpackFilesModel.js +114 -0
- package/lib/sandpackWidget/sandpackPanel.d.ts +16 -0
- package/lib/sandpackWidget/sandpackPanel.js +52 -0
- package/lib/swConnection/comm_manager.d.ts +6 -0
- package/lib/swConnection/comm_manager.js +46 -0
- package/lib/swConnection/connection_manager.d.ts +18 -0
- package/lib/swConnection/connection_manager.js +27 -0
- package/lib/swConnection/index.d.ts +3 -0
- package/lib/swConnection/index.js +68 -0
- package/lib/swConnection/sw.d.ts +1 -0
- package/lib/swConnection/sw.js +49 -0
- package/lib/token.d.ts +3 -0
- package/lib/token.js +2 -0
- package/lib/tools.d.ts +2 -0
- package/lib/tools.js +17 -0
- package/lib/type.d.ts +38 -0
- package/lib/type.js +9 -0
- package/package.json +199 -0
- package/src/document/iframePanel.ts +25 -0
- package/src/document/jupyterpackDocWidget.ts +19 -0
- package/src/document/plugin.ts +44 -0
- package/src/document/widgetFactory.ts +79 -0
- package/src/index.ts +4 -0
- package/src/pythonWidget/connectionManager.ts +43 -0
- package/src/pythonWidget/kernelExecutor.ts +140 -0
- package/src/pythonWidget/pythonWidget.ts +34 -0
- package/src/pythonWidget/pythonWidgetModel.ts +107 -0
- package/src/sandpackWidget/sandpackFilesModel.ts +141 -0
- package/src/sandpackWidget/sandpackPanel.ts +82 -0
- package/src/swConnection/comm_manager.ts +53 -0
- package/src/swConnection/connection_manager.ts +43 -0
- package/src/swConnection/index.ts +93 -0
- package/src/swConnection/sw.ts +57 -0
- package/src/token.ts +6 -0
- package/src/tools.ts +18 -0
- package/src/type.ts +44 -0
- package/style/base.css +51 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Trung Le
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<h1 align="center">jupyterpack</h1>
|
|
2
|
+
|
|
3
|
+
[](https://github.com/trungleduc/specta/actions/workflows/build.yml)
|
|
4
|
+
[](https://trungleduc.github.io/jupyterpack/lab/)
|
|
5
|
+
|
|
6
|
+
<h2 align="center"> A JupyterLite extension to serve in-browser Python and Javascript web application</h2>
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Python Web Apps**: Serve Python web applications directly in the browser using JupyterLite's in-browser Python kernel. `jupyterpack` currently supports Dash.
|
|
11
|
+
- **JavaScript Web Apps**: Bundle and serve JavaScript web applications using in-browser bundlers.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
You can install `jupyterpack` using `pip` or `conda`
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install using pip
|
|
19
|
+
pip install jupyterpack
|
|
20
|
+
|
|
21
|
+
# Install using conda
|
|
22
|
+
conda install -c conda-forge jupyterpack
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
To use `jupyterpack`, you need to create a `.spk` file that defines your web application. Here's an example structure of a React application:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
my_app/
|
|
31
|
+
├── app.spk
|
|
32
|
+
├── App.js # Your JS code
|
|
33
|
+
├── package.json # Your JS dependencies
|
|
34
|
+
└── index.html # HTML entry for JS apps
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
the `app.spk` is the entry point of your React app, it should contain the following content:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"name": "React Example",
|
|
42
|
+
"entry": "/index.html",
|
|
43
|
+
"framework": "react"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Double clicking the `spk` file to open the web app as a tab of JupyterLab.
|
|
48
|
+
|
|
49
|
+
### Dash application
|
|
50
|
+
|
|
51
|
+
Same as the React application, here is the structure of a Dash application:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
my_app/
|
|
55
|
+
├── app.spk
|
|
56
|
+
├── server.py # Your Dash code
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
the `app.spk` is the entry point of your Dash app, it should contain the following content:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"name": "Dash Example",
|
|
64
|
+
"entry": "server.py",
|
|
65
|
+
"framework": "dash"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
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.
|
|
70
|
+
|
|
71
|
+
## Try it online!
|
|
72
|
+
|
|
73
|
+
You can try it online by clicking on this badge:
|
|
74
|
+
|
|
75
|
+
[](https://trungleduc.github.io/jupyterpack/lab/)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
jupyterpack is licensed under the BSD-3-Clause license.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Widget } from '@lumino/widgets';
|
|
2
|
+
export class IFramePanel extends Widget {
|
|
3
|
+
constructor() {
|
|
4
|
+
super();
|
|
5
|
+
this.addClass('jupyterpack-iframe-panel');
|
|
6
|
+
this._iframe = document.createElement('iframe');
|
|
7
|
+
this._spinner = document.createElement('div');
|
|
8
|
+
this._spinner.classList.add('jupyterpack-spinner');
|
|
9
|
+
this.node.appendChild(this._spinner);
|
|
10
|
+
this.node.appendChild(this._iframe);
|
|
11
|
+
}
|
|
12
|
+
toggleSpinner(show) {
|
|
13
|
+
if (show) {
|
|
14
|
+
this._spinner.style.display = 'unset';
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this._spinner.style.display = 'none';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
2
|
+
export declare class JupyterPackDocWidget extends DocumentWidget {
|
|
3
|
+
constructor(options: DocumentWidget.IOptions);
|
|
4
|
+
/**
|
|
5
|
+
* Dispose of the resources held by the widget.
|
|
6
|
+
*/
|
|
7
|
+
dispose(): void;
|
|
8
|
+
onResize: (msg: any) => void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
2
|
+
export class JupyterPackDocWidget extends DocumentWidget {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super(options);
|
|
5
|
+
this.onResize = (msg) => {
|
|
6
|
+
window.dispatchEvent(new Event('resize'));
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Dispose of the resources held by the widget.
|
|
11
|
+
*/
|
|
12
|
+
dispose() {
|
|
13
|
+
this.content.dispose();
|
|
14
|
+
super.dispose();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { JupyterPackWidgetFactory } from './widgetFactory';
|
|
2
|
+
import { IConnectionManagerToken } from '../token';
|
|
3
|
+
const FACTORY = 'jupyterpack';
|
|
4
|
+
const CONTENT_TYPE = 'jupyterpack';
|
|
5
|
+
export const spkPlugin = {
|
|
6
|
+
id: 'jupyterpack:spkplugin',
|
|
7
|
+
requires: [IConnectionManagerToken],
|
|
8
|
+
autoStart: true,
|
|
9
|
+
activate: (app, connectionManager) => {
|
|
10
|
+
const widgetFactory = new JupyterPackWidgetFactory({
|
|
11
|
+
name: FACTORY,
|
|
12
|
+
modelName: 'text',
|
|
13
|
+
fileTypes: [CONTENT_TYPE],
|
|
14
|
+
defaultFor: [CONTENT_TYPE],
|
|
15
|
+
commands: app.commands,
|
|
16
|
+
manager: app.serviceManager,
|
|
17
|
+
connectionManager
|
|
18
|
+
});
|
|
19
|
+
// Registering the widget factory
|
|
20
|
+
app.docRegistry.addWidgetFactory(widgetFactory);
|
|
21
|
+
// register the filetype
|
|
22
|
+
app.docRegistry.addFileType({
|
|
23
|
+
name: CONTENT_TYPE,
|
|
24
|
+
displayName: 'SPK',
|
|
25
|
+
mimeTypes: ['text/json'],
|
|
26
|
+
extensions: ['.spk', '.SPK'],
|
|
27
|
+
fileFormat: 'json',
|
|
28
|
+
contentType: CONTENT_TYPE
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry';
|
|
2
|
+
import { ServiceManager } from '@jupyterlab/services';
|
|
3
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
4
|
+
import { IConnectionManager } from '../type';
|
|
5
|
+
import { JupyterPackDocWidget } from './jupyterpackDocWidget';
|
|
6
|
+
interface IOptions extends DocumentRegistry.IWidgetFactoryOptions {
|
|
7
|
+
commands: CommandRegistry;
|
|
8
|
+
manager: ServiceManager.IManager;
|
|
9
|
+
connectionManager: IConnectionManager;
|
|
10
|
+
}
|
|
11
|
+
export declare class JupyterPackWidgetFactory extends ABCWidgetFactory<JupyterPackDocWidget> {
|
|
12
|
+
private options;
|
|
13
|
+
constructor(options: IOptions);
|
|
14
|
+
/**
|
|
15
|
+
* Create a new widget given a context.
|
|
16
|
+
*
|
|
17
|
+
* @param context Contains the information of the file
|
|
18
|
+
* @returns The widget
|
|
19
|
+
*/
|
|
20
|
+
protected createNewWidget(context: DocumentRegistry.IContext<DocumentRegistry.IModel>): JupyterPackDocWidget;
|
|
21
|
+
private _contentsManager;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ABCWidgetFactory } from '@jupyterlab/docregistry';
|
|
2
|
+
import { Panel } from '@lumino/widgets';
|
|
3
|
+
import { JupyterPackFramework } from '../type';
|
|
4
|
+
import { SandpackPanel } from '../sandpackWidget/sandpackPanel';
|
|
5
|
+
import { JupyterPackDocWidget } from './jupyterpackDocWidget';
|
|
6
|
+
import { PythonWidgetModel } from '../pythonWidget/pythonWidgetModel';
|
|
7
|
+
import { UUID } from '@lumino/coreutils';
|
|
8
|
+
import { PythonWidget } from '../pythonWidget/pythonWidget';
|
|
9
|
+
export class JupyterPackWidgetFactory extends ABCWidgetFactory {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
this.options = options;
|
|
13
|
+
this._contentsManager = options.manager.contents;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a new widget given a context.
|
|
17
|
+
*
|
|
18
|
+
* @param context Contains the information of the file
|
|
19
|
+
* @returns The widget
|
|
20
|
+
*/
|
|
21
|
+
createNewWidget(context) {
|
|
22
|
+
const content = new Panel();
|
|
23
|
+
content.addClass('jp-jupyterpack-document-panel');
|
|
24
|
+
context.ready.then(() => {
|
|
25
|
+
const jpackModel = context.model.toJSON();
|
|
26
|
+
switch (jpackModel.framework) {
|
|
27
|
+
case JupyterPackFramework.REACT: {
|
|
28
|
+
const jpContent = new SandpackPanel({
|
|
29
|
+
context,
|
|
30
|
+
contentsManager: this._contentsManager
|
|
31
|
+
});
|
|
32
|
+
content.addWidget(jpContent);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case JupyterPackFramework.DASH: {
|
|
36
|
+
const model = new PythonWidgetModel({
|
|
37
|
+
context,
|
|
38
|
+
manager: this.options.manager,
|
|
39
|
+
contentsManager: this._contentsManager,
|
|
40
|
+
connectionManager: this.options.connectionManager
|
|
41
|
+
});
|
|
42
|
+
const pythonWidget = new PythonWidget({
|
|
43
|
+
model,
|
|
44
|
+
id: UUID.uuid4()
|
|
45
|
+
});
|
|
46
|
+
content.addWidget(pythonWidget);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
default: {
|
|
50
|
+
console.error('Unsupported framework');
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return new JupyterPackDocWidget({
|
|
56
|
+
context,
|
|
57
|
+
content
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IConnectionManager, IDict, IKernelExecutor } from '../type';
|
|
2
|
+
export declare class ConnectionManager implements IConnectionManager {
|
|
3
|
+
instanceId: string;
|
|
4
|
+
constructor(instanceId: string);
|
|
5
|
+
registerConnection(kernelExecutor: IKernelExecutor): Promise<{
|
|
6
|
+
instanceId: string;
|
|
7
|
+
kernelClientId: string;
|
|
8
|
+
}>;
|
|
9
|
+
generateResponse(options: {
|
|
10
|
+
kernelClientId: string;
|
|
11
|
+
urlPath: string;
|
|
12
|
+
method: string;
|
|
13
|
+
headers: IDict;
|
|
14
|
+
requestBody?: ArrayBuffer;
|
|
15
|
+
params?: string;
|
|
16
|
+
}): Promise<IDict | null>;
|
|
17
|
+
private _kernelExecutors;
|
|
18
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UUID } from '@lumino/coreutils';
|
|
2
|
+
export class ConnectionManager {
|
|
3
|
+
constructor(instanceId) {
|
|
4
|
+
this.instanceId = instanceId;
|
|
5
|
+
this._kernelExecutors = new Map();
|
|
6
|
+
}
|
|
7
|
+
async registerConnection(kernelExecutor) {
|
|
8
|
+
const uuid = UUID.uuid4();
|
|
9
|
+
this._kernelExecutors.set(uuid, kernelExecutor);
|
|
10
|
+
return { instanceId: this.instanceId, kernelClientId: uuid };
|
|
11
|
+
}
|
|
12
|
+
async generateResponse(options) {
|
|
13
|
+
const { urlPath, kernelClientId, method, params, requestBody, headers } = options;
|
|
14
|
+
const executor = this._kernelExecutors.get(kernelClientId);
|
|
15
|
+
if (!executor) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const response = await executor.getResponse({
|
|
19
|
+
urlPath,
|
|
20
|
+
method,
|
|
21
|
+
params,
|
|
22
|
+
headers,
|
|
23
|
+
requestBody
|
|
24
|
+
});
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { KernelMessage, Session } from '@jupyterlab/services';
|
|
2
|
+
import { IDict, IKernelExecutor } from '../type';
|
|
3
|
+
export declare class KernelExecutor implements IKernelExecutor {
|
|
4
|
+
constructor(options: KernelExecutor.IOptions);
|
|
5
|
+
get isDisposed(): boolean;
|
|
6
|
+
init(options: {
|
|
7
|
+
initCode?: string;
|
|
8
|
+
instanceId: string;
|
|
9
|
+
kernelClientId: string;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
getResponse(options: {
|
|
12
|
+
method: string;
|
|
13
|
+
urlPath: string;
|
|
14
|
+
headers: IDict;
|
|
15
|
+
requestBody?: ArrayBuffer;
|
|
16
|
+
params?: string;
|
|
17
|
+
}): Promise<IDict>;
|
|
18
|
+
executeCode(code: KernelMessage.IExecuteRequestMsg['content']): Promise<string>;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
private _isDisposed;
|
|
21
|
+
private _sessionConnection;
|
|
22
|
+
}
|
|
23
|
+
export declare namespace KernelExecutor {
|
|
24
|
+
interface IOptions {
|
|
25
|
+
sessionConnection: Session.ISessionConnection;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
|
|
2
|
+
import { arrayBufferToBase64 } from '../tools';
|
|
3
|
+
export class KernelExecutor {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this._isDisposed = false;
|
|
6
|
+
this._sessionConnection = options.sessionConnection;
|
|
7
|
+
}
|
|
8
|
+
get isDisposed() {
|
|
9
|
+
return this._isDisposed;
|
|
10
|
+
}
|
|
11
|
+
async init(options) {
|
|
12
|
+
const { initCode, instanceId, kernelClientId } = options;
|
|
13
|
+
const labBaseUrl = PageConfig.getOption('baseUrl');
|
|
14
|
+
const baseURL = URLExt.join(labBaseUrl, 'extensions/jupyterpack/static', instanceId, 'dash', kernelClientId, '/');
|
|
15
|
+
const osCode = `
|
|
16
|
+
import os
|
|
17
|
+
os.environ['DASH_URL_BASE_PATHNAME'] = '${baseURL}'
|
|
18
|
+
`;
|
|
19
|
+
await this.executeCode({ code: osCode });
|
|
20
|
+
if (initCode) {
|
|
21
|
+
await this.executeCode({ code: initCode });
|
|
22
|
+
}
|
|
23
|
+
const serverCode = `
|
|
24
|
+
import httpx, json, base64
|
|
25
|
+
__monstra_transport = httpx.WSGITransport(app=app.server)
|
|
26
|
+
def __monstra_get_response(method, url, headers, content=None, params=None):
|
|
27
|
+
decoded_content = None
|
|
28
|
+
if content is not None:
|
|
29
|
+
content = base64.b64decode(content)
|
|
30
|
+
decoded_content = content.decode()
|
|
31
|
+
with httpx.Client(transport=__monstra_transport, base_url="http://testserver") as client:
|
|
32
|
+
r = client.request(method, url, headers=headers, content=content, params=params)
|
|
33
|
+
response = {
|
|
34
|
+
"headers": dict(r.headers),
|
|
35
|
+
"content": r.text,
|
|
36
|
+
"status_code": r.status_code,
|
|
37
|
+
"original_request": {"method": method, "url": url, "content": decoded_content, "params": params, "headers": headers},
|
|
38
|
+
}
|
|
39
|
+
json_str = json.dumps(response)
|
|
40
|
+
b64_str = base64.b64encode(json_str.encode("utf-8")).decode("utf-8")
|
|
41
|
+
return b64_str
|
|
42
|
+
`;
|
|
43
|
+
await this.executeCode({ code: serverCode });
|
|
44
|
+
}
|
|
45
|
+
async getResponse(options) {
|
|
46
|
+
const { method, urlPath, requestBody, params, headers } = options;
|
|
47
|
+
const content = requestBody ? arrayBufferToBase64(requestBody) : undefined;
|
|
48
|
+
const code = `__monstra_get_response("${method}", "${urlPath}", headers=${JSON.stringify(headers)} , content=${content ? `"${content}"` : 'None'}, params=${params ? `"${params}"` : 'None'})`;
|
|
49
|
+
const raw = await this.executeCode({ code });
|
|
50
|
+
const jsonStr = atob(raw.slice(1, -1));
|
|
51
|
+
const obj = JSON.parse(jsonStr);
|
|
52
|
+
return obj;
|
|
53
|
+
}
|
|
54
|
+
async executeCode(code) {
|
|
55
|
+
var _a;
|
|
56
|
+
const kernel = (_a = this._sessionConnection) === null || _a === void 0 ? void 0 : _a.kernel;
|
|
57
|
+
if (!kernel) {
|
|
58
|
+
throw new Error('Session has no kernel.');
|
|
59
|
+
}
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const future = kernel.requestExecute(code, false, undefined);
|
|
62
|
+
const parentMsgid = future.msg.header.msg_id;
|
|
63
|
+
let executeResult = '';
|
|
64
|
+
future.onIOPub = (msg) => {
|
|
65
|
+
const msgType = msg.header.msg_type;
|
|
66
|
+
switch (msgType) {
|
|
67
|
+
case 'execute_result': {
|
|
68
|
+
const content = msg.content
|
|
69
|
+
.data['text/plain'];
|
|
70
|
+
executeResult += content;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'status': {
|
|
74
|
+
if (msg.content.execution_state ===
|
|
75
|
+
'idle' &&
|
|
76
|
+
msg.parent_header.msg_id === parentMsgid) {
|
|
77
|
+
resolve(executeResult);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'stream': {
|
|
82
|
+
const content = msg.content.text;
|
|
83
|
+
executeResult += content;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'error': {
|
|
87
|
+
console.error('Kernel operation failed', msg.content);
|
|
88
|
+
reject(msg.content);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
default:
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
dispose() {
|
|
98
|
+
if (this._isDisposed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this._isDisposed = true;
|
|
102
|
+
this._sessionConnection.dispose();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PythonWidgetModel } from './pythonWidgetModel';
|
|
2
|
+
import { IFramePanel } from '../document/iframePanel';
|
|
3
|
+
export declare class PythonWidget extends IFramePanel {
|
|
4
|
+
constructor(options: PythonWidget.IOptions);
|
|
5
|
+
get model(): PythonWidgetModel;
|
|
6
|
+
private _model;
|
|
7
|
+
}
|
|
8
|
+
export declare namespace PythonWidget {
|
|
9
|
+
interface IOptions {
|
|
10
|
+
id: string;
|
|
11
|
+
model: PythonWidgetModel;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PageConfig } from '@jupyterlab/coreutils';
|
|
2
|
+
import { IFramePanel } from '../document/iframePanel';
|
|
3
|
+
export class PythonWidget extends IFramePanel {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super();
|
|
6
|
+
this._model = options.model;
|
|
7
|
+
this._model.initialize().then(connectionData => {
|
|
8
|
+
if (!connectionData) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const iframe = this._iframe;
|
|
12
|
+
const fullLabextensionsUrl = PageConfig.getOption('fullLabextensionsUrl');
|
|
13
|
+
iframe.src = `${fullLabextensionsUrl}/jupyterpack/static/${connectionData.instanceId}/dash/${connectionData.kernelClientId}/`;
|
|
14
|
+
iframe.addEventListener('load', () => {
|
|
15
|
+
this.toggleSpinner(false);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
get model() {
|
|
20
|
+
return this._model;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
2
|
+
import { IDisposable } from '@lumino/disposable';
|
|
3
|
+
import { ServiceManager, Contents } from '@jupyterlab/services';
|
|
4
|
+
import { IConnectionManager } from '../type';
|
|
5
|
+
export declare class PythonWidgetModel implements IDisposable {
|
|
6
|
+
constructor(options: PythonWidgetModel.IOptions);
|
|
7
|
+
get isDisposed(): boolean;
|
|
8
|
+
get connectionManager(): IConnectionManager;
|
|
9
|
+
initialize(): Promise<{
|
|
10
|
+
instanceId: string;
|
|
11
|
+
kernelClientId: string;
|
|
12
|
+
} | null>;
|
|
13
|
+
dispose(): void;
|
|
14
|
+
private _isDisposed;
|
|
15
|
+
private _kernelStarted;
|
|
16
|
+
private _sessionConnection;
|
|
17
|
+
private _manager;
|
|
18
|
+
private _context;
|
|
19
|
+
private _connectionManager;
|
|
20
|
+
private _contentsManager;
|
|
21
|
+
}
|
|
22
|
+
export declare namespace PythonWidgetModel {
|
|
23
|
+
interface IOptions {
|
|
24
|
+
context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
|
|
25
|
+
manager: ServiceManager.IManager;
|
|
26
|
+
connectionManager: IConnectionManager;
|
|
27
|
+
contentsManager: Contents.IManager;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
2
|
+
import { KernelExecutor } from './kernelExecutor';
|
|
3
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
4
|
+
export class PythonWidgetModel {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._isDisposed = false;
|
|
7
|
+
this._kernelStarted = false;
|
|
8
|
+
this._context = options.context;
|
|
9
|
+
this._manager = options.manager;
|
|
10
|
+
this._connectionManager = options.connectionManager;
|
|
11
|
+
this._contentsManager = options.contentsManager;
|
|
12
|
+
}
|
|
13
|
+
get isDisposed() {
|
|
14
|
+
return this._isDisposed;
|
|
15
|
+
}
|
|
16
|
+
get connectionManager() {
|
|
17
|
+
return this._connectionManager;
|
|
18
|
+
}
|
|
19
|
+
async initialize() {
|
|
20
|
+
var _a;
|
|
21
|
+
if (this._kernelStarted) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const filePath = this._context.localPath;
|
|
25
|
+
const spkContent = this._context.model.toJSON();
|
|
26
|
+
const entryPath = PathExt.join(PathExt.dirname(filePath), spkContent.entry);
|
|
27
|
+
const entryContent = await this._contentsManager.get(entryPath, {
|
|
28
|
+
content: true,
|
|
29
|
+
format: 'text'
|
|
30
|
+
});
|
|
31
|
+
const sessionManager = this._manager.sessions;
|
|
32
|
+
await sessionManager.ready;
|
|
33
|
+
await this._manager.kernelspecs.ready;
|
|
34
|
+
const specs = this._manager.kernelspecs.specs;
|
|
35
|
+
if (!specs) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const { kernelspecs } = specs;
|
|
39
|
+
let kernelName = Object.keys(kernelspecs)[0];
|
|
40
|
+
if (kernelspecs[specs.default]) {
|
|
41
|
+
kernelName = specs.default;
|
|
42
|
+
}
|
|
43
|
+
this._sessionConnection = await sessionManager.startNew({
|
|
44
|
+
name: filePath,
|
|
45
|
+
path: filePath,
|
|
46
|
+
kernel: {
|
|
47
|
+
name: kernelName
|
|
48
|
+
},
|
|
49
|
+
type: 'notebook'
|
|
50
|
+
});
|
|
51
|
+
const executor = new KernelExecutor({
|
|
52
|
+
sessionConnection: this._sessionConnection
|
|
53
|
+
});
|
|
54
|
+
const data = await this._connectionManager.registerConnection(executor);
|
|
55
|
+
await executor.init({
|
|
56
|
+
initCode: entryContent.content,
|
|
57
|
+
...data
|
|
58
|
+
});
|
|
59
|
+
const finish = new PromiseDelegate();
|
|
60
|
+
const cb = (_, status) => {
|
|
61
|
+
var _a;
|
|
62
|
+
if (status === 'idle') {
|
|
63
|
+
(_a = this._sessionConnection.kernel) === null || _a === void 0 ? void 0 : _a.statusChanged.disconnect(cb);
|
|
64
|
+
finish.resolve();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
(_a = this._sessionConnection.kernel) === null || _a === void 0 ? void 0 : _a.statusChanged.connect(cb);
|
|
68
|
+
await finish.promise;
|
|
69
|
+
this._kernelStarted = true;
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
dispose() {
|
|
73
|
+
this._isDisposed = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Contents } from '@jupyterlab/services';
|
|
2
|
+
import { ISignal } from '@lumino/signaling';
|
|
3
|
+
import { IDict } from '../type';
|
|
4
|
+
export declare class SandpackFilesModel {
|
|
5
|
+
constructor(options: {
|
|
6
|
+
contentsManager: Contents.IManager;
|
|
7
|
+
path: string;
|
|
8
|
+
});
|
|
9
|
+
getAllFiles(): Promise<IDict<{
|
|
10
|
+
code: string;
|
|
11
|
+
}>>;
|
|
12
|
+
get fileChanged(): ISignal<SandpackFilesModel, {
|
|
13
|
+
allFiles: IDict<{
|
|
14
|
+
code: string;
|
|
15
|
+
}>;
|
|
16
|
+
}>;
|
|
17
|
+
flattenDirectory(dirContent: Contents.IModel): Promise<IDict<{
|
|
18
|
+
code: string;
|
|
19
|
+
}>>;
|
|
20
|
+
private _onFileChanged;
|
|
21
|
+
private _removeRoot;
|
|
22
|
+
private _getContent;
|
|
23
|
+
private _path;
|
|
24
|
+
private _fileChanged;
|
|
25
|
+
private _contentManager;
|
|
26
|
+
private _allFiles?;
|
|
27
|
+
}
|