jupyterpack 0.5.3 → 0.5.5

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 (52) hide show
  1. package/README.md +83 -2
  2. package/lib/document/plugin.d.ts +1 -0
  3. package/lib/document/plugin.js +135 -3
  4. package/lib/document/templates/dash.d.ts +1 -0
  5. package/lib/document/templates/dash.js +30 -0
  6. package/lib/document/templates/index.d.ts +7 -0
  7. package/lib/document/templates/index.js +70 -0
  8. package/lib/document/templates/panel.d.ts +1 -0
  9. package/lib/document/templates/panel.js +18 -0
  10. package/lib/document/templates/shiny.d.ts +1 -0
  11. package/lib/document/templates/shiny.js +49 -0
  12. package/lib/document/templates/streamlit.d.ts +1 -0
  13. package/lib/document/templates/streamlit.js +26 -0
  14. package/lib/document/widgetFactory.js +2 -1
  15. package/lib/index.d.ts +1 -1
  16. package/lib/index.js +2 -2
  17. package/lib/pythonServer/baseServer.d.ts +5 -0
  18. package/lib/pythonServer/baseServer.js +6 -6
  19. package/lib/pythonServer/index.js +3 -1
  20. package/lib/pythonServer/panel/deps.d.ts +2 -0
  21. package/lib/pythonServer/panel/deps.js +4 -0
  22. package/lib/pythonServer/panel/panelServer.d.ts +9 -0
  23. package/lib/pythonServer/panel/panelServer.js +46 -0
  24. package/lib/swConnection/mainConnectionManager.js +2 -1
  25. package/lib/tools.d.ts +10 -0
  26. package/lib/tools.js +44 -0
  27. package/lib/type.d.ts +3 -1
  28. package/lib/type.js +1 -0
  29. package/lib/websocket/websocket.js +22 -3
  30. package/package.json +2 -1
  31. package/src/document/plugin.ts +150 -5
  32. package/src/document/templates/dash.ts +30 -0
  33. package/src/document/templates/index.ts +86 -0
  34. package/src/document/templates/panel.ts +18 -0
  35. package/src/document/templates/shiny.ts +49 -0
  36. package/src/document/templates/streamlit.ts +26 -0
  37. package/src/document/widgetFactory.ts +2 -1
  38. package/src/index.ts +2 -2
  39. package/src/pythonServer/baseServer.ts +13 -6
  40. package/src/pythonServer/index.ts +3 -1
  41. package/src/pythonServer/kernelExecutor.ts +1 -0
  42. package/src/pythonServer/panel/deps.ts +6 -0
  43. package/src/pythonServer/panel/panelServer.ts +57 -0
  44. package/src/swConnection/mainConnectionManager.ts +2 -1
  45. package/src/tools.ts +59 -0
  46. package/src/type.ts +3 -1
  47. package/src/websocket/websocket.ts +37 -6
  48. package/style/base.css +10 -0
  49. package/style/icons/Plotly-Logo-Black.svg +60 -0
  50. package/style/icons/panel_logo_stacked.svg +73 -0
  51. package/style/icons/shiny-for-python.svg +82 -0
  52. package/style/icons/streamlit-logo-primary.svg +14 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@
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, Streamlit and Shiny for Python. You can also use `jupyterpack` to serve any `asgi` or `wsgi` Python web application.
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 `Starlette` or `Tornado` application.
11
11
  .
12
12
  - **JavaScript Web Apps**: Bundle and serve JavaScript web applications using in-browser bundlers.
13
13
 
@@ -25,6 +25,86 @@ pip install jupyterpack
25
25
  conda install -c conda-forge jupyterpack
26
26
  ```
27
27
 
28
+ ## Set up JupyterLite deployment
29
+
30
+ `jupyterpack` currently supports only `xeus-python` kernel and does **not** support `pyodide-kernel`. You can refer to the `xeus-python` [official documentation](https://jupyterlite-xeus.readthedocs.io/en/stable/deploy.html) for the base setup of JupyterLite with `xeus-python` kernel.
31
+
32
+ ### Framework-specific setup
33
+
34
+ At runtime, `jupyterpack` can automatically install the dependencies required by the supported frameworks.
35
+ Alternatively, you can preinstall framework dependencies directly into your JupyterLite build. When dependencies are preinstalled, runtime installation can be skipped by setting `disableDependencies` to `true` in your `.spk` file.
36
+
37
+ Below are example environment.yml files for each supported framework.
38
+
39
+ - **Dash**
40
+
41
+ ```yaml
42
+ name: xeus-kernels
43
+ channels:
44
+ - https://repo.prefix.dev/emscripten-forge-dev
45
+ - https://repo.prefix.dev/conda-forge
46
+ dependencies:
47
+ - xeus-python
48
+ - jupyterpack
49
+ - dash
50
+ - werkzeug>=2.2,<3.0
51
+ - blinker>=1.5.0,<2
52
+ - cachetools>=4.0,<7
53
+ - pip:
54
+ - pyodide_http
55
+ ```
56
+
57
+ - **Streamlit**
58
+
59
+ ```yaml
60
+ name: xeus-kernels
61
+ channels:
62
+ - https://repo.prefix.dev/emscripten-forge-dev
63
+ - https://repo.prefix.dev/conda-forge
64
+ dependencies:
65
+ - xeus-python
66
+ - jupyterpack
67
+ - blinker>=1.5.0,<2
68
+ - cachetools>=4.0,<7
69
+ - protobuf
70
+ - altair
71
+ - pyarrow
72
+ - pip:
73
+ - streamlit>=1.50.0
74
+ - pyodide_http
75
+ ```
76
+
77
+ - **Shiny**
78
+
79
+ ```yaml
80
+ name: xeus-kernels
81
+ channels:
82
+ - https://repo.prefix.dev/emscripten-forge-dev
83
+ - https://repo.prefix.dev/conda-forge
84
+ dependencies:
85
+ - xeus-python
86
+ - jupyterpack
87
+ - pip:
88
+ - shiny
89
+ - shinychat
90
+ - pyodide_http
91
+ ```
92
+
93
+ - **Panel**
94
+
95
+ ```yaml
96
+ name: xeus-kernels
97
+ channels:
98
+ - https://repo.prefix.dev/emscripten-forge-dev
99
+ - https://repo.prefix.dev/conda-forge
100
+ dependencies:
101
+ - xeus-python
102
+ - jupyterpack
103
+ - panel
104
+ - pip:
105
+ - pyodide_http
106
+ ```
107
+
28
108
  ## Usage
29
109
 
30
110
  To use `jupyterpack`, you need to create a `.spk` file that defines your web application. Here's an example structure of a React application:
@@ -126,7 +206,8 @@ interface IJupyterPackFileFormat {
126
206
  "dependencies": {
127
207
  "mamba": ["numpy", "pandas"],
128
208
  "pip": []
129
- }
209
+ },
210
+ "disableDependencies": false
130
211
  }
131
212
  ```
132
213
 
@@ -1,3 +1,4 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  import { IJupyterpackDocTracker } from '../type';
3
3
  export declare const spkPlugin: JupyterFrontEndPlugin<IJupyterpackDocTracker>;
4
+ export declare const launcherPlugin: JupyterFrontEndPlugin<void>;
@@ -1,8 +1,11 @@
1
- import { JupyterPackWidgetFactory } from './widgetFactory';
2
- import { IConnectionManagerToken, IJupyterpackDocTrackerToken } from '../token';
1
+ import { JupyterPackFramework } from './../type';
3
2
  import { WidgetTracker } from '@jupyterlab/apputils';
4
- import { logoIcon } from '../tools';
3
+ import { ILauncher } from '@jupyterlab/launcher';
4
+ import { IConnectionManagerToken, IJupyterpackDocTrackerToken } from '../token';
5
+ import { dashIcon, logoIcon, panelIcon, shinyIcon, streamlitIcon } from '../tools';
5
6
  import { addCommands } from './commands';
7
+ import { JupyterPackWidgetFactory } from './widgetFactory';
8
+ import { generateAppFiles } from './templates';
6
9
  const FACTORY = 'jupyterpack';
7
10
  const CONTENT_TYPE = 'jupyterpack';
8
11
  export const spkPlugin = {
@@ -47,3 +50,132 @@ export const spkPlugin = {
47
50
  return tracker;
48
51
  }
49
52
  };
53
+ export const launcherPlugin = {
54
+ id: 'jupyterpack:spklauncher',
55
+ optional: [ILauncher],
56
+ autoStart: true,
57
+ provides: IJupyterpackDocTrackerToken,
58
+ activate: (app, launcher) => {
59
+ if (!launcher) {
60
+ return;
61
+ }
62
+ const { commands } = app;
63
+ const commandId = 'jupyterpack:create-new-file';
64
+ const dashCommandId = 'jupyterpack:create-dash-app';
65
+ const streamlitCommandId = 'jupyterpack:create-streamlit-app';
66
+ const shinyCommandId = 'jupyterpack:create-shiny-app';
67
+ const panelCommandId = 'jupyterpack:create-panel-app';
68
+ commands.addCommand(commandId, {
69
+ label: 'New SPK File',
70
+ icon: logoIcon,
71
+ caption: 'Create a new SPK fike',
72
+ execute: async (args) => {
73
+ const cwd = args['cwd'];
74
+ const contentsManager = app.serviceManager.contents;
75
+ let model = await contentsManager.newUntitled({
76
+ path: cwd,
77
+ type: 'file',
78
+ ext: '.spk'
79
+ });
80
+ const spkContent = `
81
+ {
82
+ "name": "${model.name}",
83
+ "entry": "",
84
+ "framework": "",
85
+ "rootUrl": "/",
86
+ "metadata": {
87
+ "autoreload": true
88
+ },
89
+ "dependencies": {
90
+ "mamba": [],
91
+ "pip": []
92
+ }
93
+ }
94
+ `;
95
+ model = await contentsManager.save(model.path, {
96
+ ...model,
97
+ format: 'text',
98
+ size: undefined,
99
+ content: spkContent
100
+ });
101
+ }
102
+ });
103
+ commands.addCommand(dashCommandId, {
104
+ label: 'Dash App',
105
+ icon: dashIcon,
106
+ caption: 'Create a new Dash Application',
107
+ execute: async (args) => {
108
+ const cwd = args['cwd'];
109
+ await generateAppFiles({
110
+ contentsManager: app.serviceManager.contents,
111
+ cwd,
112
+ framework: JupyterPackFramework.DASH
113
+ });
114
+ }
115
+ });
116
+ commands.addCommand(streamlitCommandId, {
117
+ label: 'Streamlit App',
118
+ icon: streamlitIcon,
119
+ caption: 'Create a new Streamlit Application',
120
+ execute: async (args) => {
121
+ const cwd = args['cwd'];
122
+ await generateAppFiles({
123
+ contentsManager: app.serviceManager.contents,
124
+ cwd,
125
+ framework: JupyterPackFramework.STREAMLIT
126
+ });
127
+ }
128
+ });
129
+ commands.addCommand(shinyCommandId, {
130
+ label: 'Shiny App',
131
+ icon: shinyIcon,
132
+ caption: 'Create a new Shiny Application',
133
+ execute: async (args) => {
134
+ const cwd = args['cwd'];
135
+ await generateAppFiles({
136
+ contentsManager: app.serviceManager.contents,
137
+ cwd,
138
+ framework: JupyterPackFramework.SHINY
139
+ });
140
+ }
141
+ });
142
+ commands.addCommand(panelCommandId, {
143
+ label: 'Panel App',
144
+ icon: panelIcon,
145
+ caption: 'Create a new Panel Application',
146
+ execute: async (args) => {
147
+ const cwd = args['cwd'];
148
+ await generateAppFiles({
149
+ contentsManager: app.serviceManager.contents,
150
+ cwd,
151
+ framework: JupyterPackFramework.PANEL
152
+ });
153
+ }
154
+ });
155
+ launcher.add({
156
+ command: commandId,
157
+ category: 'JupyterPack',
158
+ rank: 1
159
+ });
160
+ launcher.add({
161
+ command: dashCommandId,
162
+ category: 'JupyterPack',
163
+ rank: 2
164
+ });
165
+ launcher.add({
166
+ command: streamlitCommandId,
167
+ category: 'JupyterPack',
168
+ rank: 3
169
+ });
170
+ launcher.add({
171
+ command: shinyCommandId,
172
+ category: 'JupyterPack',
173
+ rank: 4
174
+ });
175
+ launcher.add({
176
+ command: panelCommandId,
177
+ category: 'JupyterPack',
178
+ rank: 5
179
+ });
180
+ }
181
+ };
@@ -0,0 +1 @@
1
+ export declare const DASH_APP = "\n# This example is taken from the Dash official documentation (https://dash.plotly.com/layout)\n\nimport plotly.express as px\nimport pandas as pd\nfrom dash import Dash, html, dcc\napp = Dash()\n\n# assume you have a \"long-form\" data frame\n# see https://plotly.com/python/px-arguments/ for more options\ndf = pd.DataFrame({\n \"Fruit\": [\"Apples\", \"Oranges\", \"Bananas\", \"Apples\", \"Oranges\", \"Bananas\"],\n \"Amount\": [4, 1, 2, 2, 4, 5],\n \"City\": [\"SF\", \"SF\", \"SF\", \"Montreal\", \"Montreal\", \"Montreal\"]\n})\n\nfig = px.bar(df, x=\"Fruit\", y=\"Amount\", color=\"City\", barmode=\"group\")\n\napp.layout = html.Div(children=[\n html.H1(children='Hello Dash'),\n\n html.Div(children='''\n Dash: A web application framework for your data.\n '''),\n\n dcc.Graph(\n id='example-graph',\n figure=fig\n )\n])";
@@ -0,0 +1,30 @@
1
+ export const DASH_APP = `
2
+ # This example is taken from the Dash official documentation (https://dash.plotly.com/layout)
3
+
4
+ import plotly.express as px
5
+ import pandas as pd
6
+ from dash import Dash, html, dcc
7
+ app = Dash()
8
+
9
+ # assume you have a "long-form" data frame
10
+ # see https://plotly.com/python/px-arguments/ for more options
11
+ df = pd.DataFrame({
12
+ "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
13
+ "Amount": [4, 1, 2, 2, 4, 5],
14
+ "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
15
+ })
16
+
17
+ fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
18
+
19
+ app.layout = html.Div(children=[
20
+ html.H1(children='Hello Dash'),
21
+
22
+ html.Div(children='''
23
+ Dash: A web application framework for your data.
24
+ '''),
25
+
26
+ dcc.Graph(
27
+ id='example-graph',
28
+ figure=fig
29
+ )
30
+ ])`;
@@ -0,0 +1,7 @@
1
+ import { Contents } from '@jupyterlab/services';
2
+ import { JupyterPackFramework } from '../../type';
3
+ export declare function generateAppFiles(options: {
4
+ contentsManager: Contents.IManager;
5
+ framework: JupyterPackFramework;
6
+ cwd: string;
7
+ }): Promise<void>;
@@ -0,0 +1,70 @@
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { DASH_APP } from './dash';
3
+ import { STREAMLIT_APP } from './streamlit';
4
+ import { SHINY_APP } from './shiny';
5
+ import { newDirectory } from '../../tools';
6
+ import { PathExt } from '@jupyterlab/coreutils';
7
+ import { PANEL_APP } from './panel';
8
+ export async function generateAppFiles(options) {
9
+ const { contentsManager, framework, cwd } = options;
10
+ const newPath = await newDirectory({
11
+ dirName: `${framework} app`,
12
+ contentsManager,
13
+ cwd
14
+ });
15
+ let pyModel = await contentsManager.newUntitled({
16
+ path: newPath,
17
+ type: 'file',
18
+ ext: '.py'
19
+ });
20
+ let appContent = '';
21
+ switch (framework) {
22
+ case JupyterPackFramework.DASH: {
23
+ appContent = DASH_APP;
24
+ break;
25
+ }
26
+ case JupyterPackFramework.STREAMLIT: {
27
+ appContent = STREAMLIT_APP;
28
+ break;
29
+ }
30
+ case JupyterPackFramework.SHINY: {
31
+ appContent = SHINY_APP;
32
+ break;
33
+ }
34
+ case JupyterPackFramework.PANEL: {
35
+ appContent = PANEL_APP;
36
+ break;
37
+ }
38
+ default:
39
+ break;
40
+ }
41
+ await contentsManager.save(pyModel.path, {
42
+ ...pyModel,
43
+ format: 'text',
44
+ size: undefined,
45
+ content: appContent
46
+ });
47
+ pyModel = await contentsManager.rename(pyModel.path, PathExt.join(newPath, 'app.py'));
48
+ let model = await contentsManager.newUntitled({
49
+ path: newPath,
50
+ type: 'file',
51
+ ext: '.spk'
52
+ });
53
+ const spkContent = `
54
+ {
55
+ "name": "entrypoint.spk",
56
+ "entry": "${pyModel.name}",
57
+ "framework": "${framework}",
58
+ "dependencies": {
59
+ "mamba": []
60
+ }
61
+ }
62
+ `;
63
+ model = await contentsManager.save(model.path, {
64
+ ...model,
65
+ format: 'text',
66
+ size: undefined,
67
+ content: spkContent
68
+ });
69
+ await contentsManager.rename(model.path, PathExt.join(newPath, 'entrypoint.spk'));
70
+ }
@@ -0,0 +1 @@
1
+ export declare const PANEL_APP = "\n# This example is taken from the Panel official documentation (https://github.com/holoviz/panel?tab=readme-ov-file#interactive-data-apps)\n\nimport panel as pn\n\n\ndef model(n=5):\n return \"\u2B50\" * n\n\n\npn.extension(template=\"fast\")\n\nslider = pn.widgets.IntSlider(value=5, start=1, end=5)\n\ninteractive_model = pn.bind(model, n=slider)\n\nlayout = pn.Column(slider, interactive_model).servable()\n";
@@ -0,0 +1,18 @@
1
+ export const PANEL_APP = `
2
+ # This example is taken from the Panel official documentation (https://github.com/holoviz/panel?tab=readme-ov-file#interactive-data-apps)
3
+
4
+ import panel as pn
5
+
6
+
7
+ def model(n=5):
8
+ return "⭐" * n
9
+
10
+
11
+ pn.extension(template="fast")
12
+
13
+ slider = pn.widgets.IntSlider(value=5, start=1, end=5)
14
+
15
+ interactive_model = pn.bind(model, n=slider)
16
+
17
+ layout = pn.Column(slider, interactive_model).servable()
18
+ `;
@@ -0,0 +1 @@
1
+ export declare const SHINY_APP = "\n# This example is taken from the shinylive official playground (https://shinylive.io/py/examples/#modules)\n\nfrom shiny import App, module, reactive, render, ui\n\n\n# ============================================================\n# Counter module\n# ============================================================\n@module.ui\ndef counter_ui(label: str = \"Increment counter\") -> ui.TagChild:\n return ui.card(\n ui.h2(\"This is \" + label),\n ui.input_action_button(id=\"button\", label=label),\n ui.output_code(id=\"out\"),\n )\n\n\n@module.server\ndef counter_server(input, output, session, starting_value: int = 0):\n count: reactive.value[int] = reactive.value(starting_value)\n\n @reactive.effect\n @reactive.event(input.button)\n def _():\n count.set(count() + 1)\n\n @render.code\n def out() -> str:\n return f\"Click count is {count()}\"\n\n\n# =============================================================================\n# App that uses module\n# =============================================================================\napp_ui = ui.page_fluid(\n counter_ui(\"counter1\", \"Counter 1\"),\n counter_ui(\"counter2\", \"Counter 2\"),\n)\n\n\ndef server(input, output, session):\n counter_server(\"counter1\")\n counter_server(\"counter2\")\n\n\napp = App(app_ui, server)\n\n";
@@ -0,0 +1,49 @@
1
+ export const SHINY_APP = `
2
+ # This example is taken from the shinylive official playground (https://shinylive.io/py/examples/#modules)
3
+
4
+ from shiny import App, module, reactive, render, ui
5
+
6
+
7
+ # ============================================================
8
+ # Counter module
9
+ # ============================================================
10
+ @module.ui
11
+ def counter_ui(label: str = "Increment counter") -> ui.TagChild:
12
+ return ui.card(
13
+ ui.h2("This is " + label),
14
+ ui.input_action_button(id="button", label=label),
15
+ ui.output_code(id="out"),
16
+ )
17
+
18
+
19
+ @module.server
20
+ def counter_server(input, output, session, starting_value: int = 0):
21
+ count: reactive.value[int] = reactive.value(starting_value)
22
+
23
+ @reactive.effect
24
+ @reactive.event(input.button)
25
+ def _():
26
+ count.set(count() + 1)
27
+
28
+ @render.code
29
+ def out() -> str:
30
+ return f"Click count is {count()}"
31
+
32
+
33
+ # =============================================================================
34
+ # App that uses module
35
+ # =============================================================================
36
+ app_ui = ui.page_fluid(
37
+ counter_ui("counter1", "Counter 1"),
38
+ counter_ui("counter2", "Counter 2"),
39
+ )
40
+
41
+
42
+ def server(input, output, session):
43
+ counter_server("counter1")
44
+ counter_server("counter2")
45
+
46
+
47
+ app = App(app_ui, server)
48
+
49
+ `;
@@ -0,0 +1 @@
1
+ export declare const STREAMLIT_APP = "\n# This example is taken from the streamlit official playground (https://streamlit.io/playground?example=charts)\n\nimport streamlit as st\nimport pandas as pd\nimport numpy as np\n\nst.write(\n \"Streamlit supports a wide range of data visualizations, including [Plotly, Altair, and Bokeh charts](https://docs.streamlit.io/develop/api-reference/charts). \uD83D\uDCCA And with over 20 input widgets, you can easily make your data interactive!\"\n)\n\nall_users = [\"Alice\", \"Bob\", \"Charly\"]\nwith st.container(border=True):\n users = st.multiselect(\"Users\", all_users, default=all_users)\n rolling_average = st.toggle(\"Rolling average\")\n\nnp.random.seed(42)\ndata = pd.DataFrame(np.random.randn(20, len(users)), columns=users)\nif rolling_average:\n data = data.rolling(7).mean().dropna()\n\ntab1, tab2 = st.tabs([\"Chart\", \"Dataframe\"])\ntab1.line_chart(data, height=250)\ntab2.dataframe(data, height=250, width=True)\n\n";
@@ -0,0 +1,26 @@
1
+ export const STREAMLIT_APP = `
2
+ # This example is taken from the streamlit official playground (https://streamlit.io/playground?example=charts)
3
+
4
+ import streamlit as st
5
+ import pandas as pd
6
+ import numpy as np
7
+
8
+ st.write(
9
+ "Streamlit supports a wide range of data visualizations, including [Plotly, Altair, and Bokeh charts](https://docs.streamlit.io/develop/api-reference/charts). 📊 And with over 20 input widgets, you can easily make your data interactive!"
10
+ )
11
+
12
+ all_users = ["Alice", "Bob", "Charly"]
13
+ with st.container(border=True):
14
+ users = st.multiselect("Users", all_users, default=all_users)
15
+ rolling_average = st.toggle("Rolling average")
16
+
17
+ np.random.seed(42)
18
+ data = pd.DataFrame(np.random.randn(20, len(users)), columns=users)
19
+ if rolling_average:
20
+ data = data.rolling(7).mean().dropna()
21
+
22
+ tab1, tab2 = st.tabs(["Chart", "Dataframe"])
23
+ tab1.line_chart(data, height=250)
24
+ tab2.dataframe(data, height=250, width=True)
25
+
26
+ `;
@@ -37,7 +37,8 @@ export class JupyterPackWidgetFactory extends ABCWidgetFactory {
37
37
  case JupyterPackFramework.STREAMLIT:
38
38
  case JupyterPackFramework.TORNADO:
39
39
  case JupyterPackFramework.STARLETTE:
40
- case JupyterPackFramework.SHINY: {
40
+ case JupyterPackFramework.SHINY:
41
+ case JupyterPackFramework.PANEL: {
41
42
  const model = new PythonWidgetModel({
42
43
  jpackModel,
43
44
  context,
package/lib/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- declare const _default: (import("@jupyterlab/application").JupyterFrontEndPlugin<import("./type").IJupyterpackDocTracker> | import("@jupyterlab/application").JupyterFrontEndPlugin<import("./type").IConnectionManager>)[];
1
+ declare const _default: (import("@jupyterlab/application").JupyterFrontEndPlugin<import("./type").IJupyterpackDocTracker> | import("@jupyterlab/application").JupyterFrontEndPlugin<void> | import("@jupyterlab/application").JupyterFrontEndPlugin<import("./type").IConnectionManager>)[];
2
2
  export default _default;
package/lib/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { spkPlugin } from './document/plugin';
1
+ import { launcherPlugin, spkPlugin } from './document/plugin';
2
2
  import { swPlugin } from './swConnection';
3
- export default [swPlugin, spkPlugin];
3
+ export default [swPlugin, spkPlugin, launcherPlugin];
@@ -23,11 +23,13 @@ export declare abstract class BasePythonServer implements IBasePythonServer {
23
23
  kernelId: string;
24
24
  wsUrl: string;
25
25
  protocol?: string;
26
+ broadcastChannelSuffix?: string;
26
27
  }): string;
27
28
  closeWebsocketFunctionFactory(options: {
28
29
  instanceId: string;
29
30
  kernelId: string;
30
31
  wsUrl: string;
32
+ broadcastChannelSuffix?: string;
31
33
  }): string;
32
34
  sendWebsocketMessageFunctionFactory(options: {
33
35
  instanceId: string;
@@ -40,11 +42,13 @@ export declare abstract class BasePythonServer implements IBasePythonServer {
40
42
  kernelId: string;
41
43
  wsUrl: string;
42
44
  protocol?: string;
45
+ broadcastChannelSuffix?: string;
43
46
  }): Promise<void>;
44
47
  closeWebsocket(options: {
45
48
  instanceId: string;
46
49
  kernelId: string;
47
50
  wsUrl: string;
51
+ broadcastChannelSuffix?: string;
48
52
  }): Promise<void>;
49
53
  sendWebsocketMessage(options: {
50
54
  instanceId: string;
@@ -70,6 +74,7 @@ export declare abstract class BasePythonServer implements IBasePythonServer {
70
74
  instanceId: string;
71
75
  kernelId: string;
72
76
  wsUrl: string;
77
+ broadcastChannelSuffix?: string;
73
78
  }[];
74
79
  protected readonly _server_var = "__jupyterpack_python_server";
75
80
  private _kernelExecutor;
@@ -35,7 +35,7 @@ export class BasePythonServer {
35
35
  await this._kernelExecutor.executeCode({ code: patchCode });
36
36
  if (!options.disableDependencies) {
37
37
  const { dependencies } = options;
38
- if (dependencies === null || dependencies === void 0 ? void 0 : dependencies.mamba) {
38
+ if ((dependencies === null || dependencies === void 0 ? void 0 : dependencies.mamba) && dependencies.mamba.length > 0) {
39
39
  const mambaDeps = `
40
40
  %mamba install ${dependencies.mamba.join(' ')}
41
41
  True
@@ -44,7 +44,7 @@ export class BasePythonServer {
44
44
  code: mambaDeps
45
45
  }, true);
46
46
  }
47
- if (dependencies === null || dependencies === void 0 ? void 0 : dependencies.pip) {
47
+ if ((dependencies === null || dependencies === void 0 ? void 0 : dependencies.pip) && dependencies.pip.length > 0) {
48
48
  const pipDeps = `
49
49
  %pip install ${dependencies.pip.join(' ')}
50
50
  True
@@ -69,13 +69,13 @@ export class BasePythonServer {
69
69
  return code;
70
70
  }
71
71
  openWebsocketFunctionFactory(options) {
72
- const { instanceId, kernelId, wsUrl, protocol } = options;
73
- const code = `await ${this._server_var}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)})`;
72
+ const { instanceId, kernelId, wsUrl, protocol, broadcastChannelSuffix } = options;
73
+ const code = `await ${this._server_var}.open_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(protocol)}, ${stringOrNone(broadcastChannelSuffix)})`;
74
74
  return code;
75
75
  }
76
76
  closeWebsocketFunctionFactory(options) {
77
- const { instanceId, kernelId, wsUrl } = options;
78
- const code = `await ${this._server_var}.close_ws("${instanceId}", "${kernelId}", "${wsUrl}")`;
77
+ const { instanceId, kernelId, wsUrl, broadcastChannelSuffix } = options;
78
+ const code = `await ${this._server_var}.close_ws("${instanceId}", "${kernelId}", "${wsUrl}", ${stringOrNone(broadcastChannelSuffix)})`;
79
79
  return code;
80
80
  }
81
81
  sendWebsocketMessageFunctionFactory(options) {
@@ -1,5 +1,6 @@
1
1
  import { JupyterPackFramework } from '../type';
2
2
  import { DashServer } from './dash/dashServer';
3
+ import { PanelServer } from './panel/panelServer';
3
4
  import { ShinyServer } from './shiny/shinyServer';
4
5
  import { StarletteServer } from './starlette/starletteServer';
5
6
  import { StreamlitServer } from './streamlit/streamlitServer';
@@ -9,5 +10,6 @@ export const PYTHON_SERVER = new Map([
9
10
  [JupyterPackFramework.STREAMLIT, StreamlitServer],
10
11
  [JupyterPackFramework.TORNADO, TornadoServer],
11
12
  [JupyterPackFramework.SHINY, ShinyServer],
12
- [JupyterPackFramework.STARLETTE, StarletteServer]
13
+ [JupyterPackFramework.STARLETTE, StarletteServer],
14
+ [JupyterPackFramework.PANEL, PanelServer]
13
15
  ]);
@@ -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: ['panel'],
3
+ pip: []
4
+ };
@@ -0,0 +1,9 @@
1
+ import { IPythonServerInitOptions } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+ export declare class PanelServer extends BasePythonServer {
4
+ init(options: IPythonServerInitOptions): Promise<void>;
5
+ reloadPythonServer(options: {
6
+ entryPath?: string;
7
+ initCode?: string;
8
+ }): Promise<void>;
9
+ }
@@ -0,0 +1,46 @@
1
+ import { JupyterPackFramework } from '../../type';
2
+ import { BasePythonServer } from '../baseServer';
3
+ import { DEPENDENCIES } from './deps';
4
+ export class PanelServer extends BasePythonServer {
5
+ async init(options) {
6
+ const mergedOptions = {
7
+ ...options,
8
+ dependencies: this.mergeDependencies(options.dependencies, DEPENDENCIES)
9
+ };
10
+ await super.init(mergedOptions);
11
+ const { instanceId, kernelClientId, entryPath } = options;
12
+ if (!entryPath) {
13
+ throw new Error('Missing Panel entry path, please check your SPK file');
14
+ }
15
+ const baseURL = this.buildBaseURL({
16
+ instanceId,
17
+ kernelClientId,
18
+ framework: JupyterPackFramework.PANEL
19
+ });
20
+ const patchCode = `
21
+ from jupyterpack.common import set_base_url_env, patch_tornado
22
+ patch_tornado()
23
+ set_base_url_env("${baseURL}")
24
+ `;
25
+ await this.kernelExecutor.executeCode({ code: patchCode });
26
+ const stCode = `
27
+ from jupyterpack.panel import PanelServer, patch_panel
28
+ patch_panel()
29
+ ${this._server_var} = PanelServer("${entryPath}", "${baseURL}")
30
+ `;
31
+ await this.kernelExecutor.executeCode({ code: stCode });
32
+ }
33
+ async reloadPythonServer(options) {
34
+ const { entryPath } = options;
35
+ if (!entryPath || !this._baseUrl) {
36
+ return;
37
+ }
38
+ const reloadCode = `
39
+ ${this._server_var}.dispose()
40
+ ${this._server_var}.reload("${entryPath}")
41
+ `;
42
+ await this.kernelExecutor.executeCode({
43
+ code: reloadCode
44
+ }, true);
45
+ }
46
+ }