Skip to content

Commit 8ac94d7

Browse files
davidbrochartkrassowski
authored andcommitted
Support server-side execution (jupyterlab#279)
* Support server-side execution * lint * again * Fix jest * Mention this feature in documentation * Update UI test snapshots * Use "dependency_type: minimum" for Minimum Versions check * Add 'serverSideExecution' to page config * Replace "fully recovered" with "recovered" * Add link to jupyter-server issue Co-authored-by: Michał Krassowski <[email protected]> --------- Co-authored-by: Michał Krassowski <[email protected]>
1 parent f4c8e28 commit 8ac94d7

File tree

11 files changed

+4549
-3181
lines changed

11 files changed

+4549
-3181
lines changed

.github/workflows/test.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ jobs:
152152
- name: Base Setup
153153
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
154154
with:
155-
python_version: "3.11"
155+
python_version: "3.8"
156+
dependency_type: minimum
156157

157-
- name: Install minimum versions
158-
uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1
158+
- name: Install the Python dependencies
159+
run: |
160+
pip install -e ".[test]"
159161
160162
- name: Run the unit tests
161163
run: |

docs/source/configuration.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ you happen to delete it, there shouldn't be any serious consequence either.
1010
There are a number of settings that you can change:
1111

1212
```bash
13-
# To enable or disable RTC(Real-Time Collaboration) (default: False).
13+
# To enable or disable RTC (Real-Time Collaboration) (default: False).
1414
# If True, RTC will be disabled.
1515
jupyter lab --YDocExtension.disable_rtc=True
1616

@@ -29,3 +29,21 @@ jupyter lab --YDocExtension.document_cleanup_delay=100
2929
# The YStore class to use for storing Y updates (default: JupyterSQLiteYStore).
3030
jupyter lab --YDocExtension.ystore_class=pycrdt_websocket.ystore.TempFileYStore
3131
```
32+
33+
There is an experimental feature that is currently only supported by the
34+
[Jupyverse](https://github.com/jupyter-server/jupyverse) server
35+
(not yet with [jupyter-server](https://github.com/jupyter-server/jupyter_server),
36+
see the [issue #900](https://github.com/jupyter-server/jupyter_server/issues/900)):
37+
server-side execution. With this, running notebook code cells is not done in the frontend through
38+
the low-level kernel protocol over WebSocket API, but through a high-level REST API. Communication
39+
with the kernel is then delegated to the server, and cell outputs are populated in the notebook
40+
shared document. The frontend gets these outputs changes and shows them live. What this means is
41+
that the notebook state can be recovered even if the frontend disconnects, because cell outputs are
42+
not populated frontend-side but server-side.
43+
44+
This feature is disabled by default, and can be enabled like so:
45+
```bash
46+
pip install "jupyterlab>=4.2.0b0"
47+
pip install "jupyverse[jupyterlab, auth]>=0.4.2"
48+
jupyverse --set kernels.require_yjs=true --set jupyterlab.server_side_execution=true
49+
```

jupyter_collaboration/app.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class YDocExtension(ExtensionApp):
5757
directory.""",
5858
)
5959

60+
server_side_execution = Bool(
61+
False,
62+
config=True,
63+
help="""Whether to execute notebooks in the server using the REST API, not using the kernel
64+
protocol over WebSocket. The frontend only interacts with the notebook through its shared
65+
model.""",
66+
)
67+
6068
def initialize(self):
6169
super().initialize()
6270
self.serverapp.event_logger.register_event_schema(EVENTS_SCHEMA_PATH)
@@ -74,7 +82,11 @@ def initialize_settings(self):
7482

7583
def initialize_handlers(self):
7684
self.serverapp.web_app.settings.setdefault(
77-
"page_config_data", {"disableRTC": self.disable_rtc}
85+
"page_config_data",
86+
{
87+
"disableRTC": self.disable_rtc,
88+
"serverSideExecution": self.server_side_execution,
89+
},
7890
)
7991

8092
# Set configurable parameters to YStore class

packages/collaboration-extension/package.json

+14-13
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,20 @@
5656
"@jupyter/collaboration": "^2.0.11",
5757
"@jupyter/docprovider": "^2.0.11",
5858
"@jupyter/ydoc": "^1.1.0-a0",
59-
"@jupyterlab/application": "^4.0.5",
60-
"@jupyterlab/apputils": "^4.0.5",
61-
"@jupyterlab/codemirror": "^4.0.5",
59+
"@jupyterlab/application": "^4.2.0-beta.0",
60+
"@jupyterlab/apputils": "^4.2.0-beta.0",
61+
"@jupyterlab/codemirror": "^4.2.0-beta.0",
6262
"@jupyterlab/coreutils": "^6.0.5",
63-
"@jupyterlab/docregistry": "^4.0.5",
64-
"@jupyterlab/filebrowser": "^4.0.5",
65-
"@jupyterlab/fileeditor": "^4.0.5",
66-
"@jupyterlab/logconsole": "^4.0.5",
67-
"@jupyterlab/notebook": "^4.0.5",
63+
"@jupyterlab/docregistry": "^4.2.0-beta.0",
64+
"@jupyterlab/filebrowser": "^4.2.0-beta.0",
65+
"@jupyterlab/fileeditor": "^4.2.0-beta.0",
66+
"@jupyterlab/logconsole": "^4.2.0-beta.0",
67+
"@jupyterlab/notebook": "^4.2.0-beta.0",
6868
"@jupyterlab/services": "^7.0.5",
69-
"@jupyterlab/settingregistry": "^4.0.5",
70-
"@jupyterlab/statedb": "^4.0.5",
71-
"@jupyterlab/translation": "^4.0.5",
72-
"@jupyterlab/ui-components": "^4.0.5",
69+
"@jupyterlab/settingregistry": "^4.2.0-beta.0",
70+
"@jupyterlab/statedb": "^4.2.0-beta.0",
71+
"@jupyterlab/translation": "^4.2.0-beta.0",
72+
"@jupyterlab/ui-components": "^4.2.0-beta.0",
7373
"@lumino/commands": "^2.1.0",
7474
"@lumino/widgets": "^2.1.0",
7575
"y-protocols": "^1.0.5",
@@ -97,7 +97,8 @@
9797
"schemaDir": "./schema",
9898
"outputDir": "../../jupyter_collaboration/labextension",
9999
"disabledExtensions": [
100-
"@jupyterlab/filebrowser-extension:defaultFileBrowser"
100+
"@jupyterlab/filebrowser-extension:defaultFileBrowser",
101+
"@jupyterlab/notebook-extension:cell-executor"
101102
],
102103
"sharedPackages": {
103104
"@codemirror/state": {

packages/collaboration-extension/src/collaboration.ts

+63-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import {
1414
EditorExtensionRegistry,
1515
IEditorExtensionRegistry
1616
} from '@jupyterlab/codemirror';
17+
import { type MarkdownCell } from '@jupyterlab/cells';
18+
import { INotebookCellExecutor, runCell } from '@jupyterlab/notebook';
1719
import { WebSocketAwarenessProvider } from '@jupyter/docprovider';
1820
import { SidePanel, usersIcon } from '@jupyterlab/ui-components';
19-
import { URLExt } from '@jupyterlab/coreutils';
21+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
2022
import { ServerConnection } from '@jupyterlab/services';
2123
import { IStateDB, StateDB } from '@jupyterlab/statedb';
2224
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
@@ -189,3 +191,63 @@ export const userEditorCursors: JupyterFrontEndPlugin<void> = {
189191
});
190192
}
191193
};
194+
195+
export const notebookCellExecutor: JupyterFrontEndPlugin<INotebookCellExecutor> =
196+
{
197+
id: '@jupyter/collaboration-extension:notebook-cell-executor',
198+
description:
199+
'Add notebook cell executor that uses REST API instead of kernel protocol over WebSocket.',
200+
autoStart: true,
201+
provides: INotebookCellExecutor,
202+
activate: (app: JupyterFrontEnd): INotebookCellExecutor => {
203+
if (PageConfig.getOption('serverSideExecution') === 'true') {
204+
return Object.freeze({ runCell: runCellServerSide });
205+
}
206+
return Object.freeze({ runCell });
207+
}
208+
};
209+
210+
async function runCellServerSide({
211+
cell,
212+
notebook,
213+
notebookConfig,
214+
onCellExecuted,
215+
onCellExecutionScheduled,
216+
sessionContext,
217+
sessionDialogs,
218+
translator
219+
}: INotebookCellExecutor.IRunCellOptions): Promise<boolean> {
220+
switch (cell.model.type) {
221+
case 'markdown':
222+
(cell as MarkdownCell).rendered = true;
223+
cell.inputHidden = false;
224+
onCellExecuted({ cell, success: true });
225+
break;
226+
case 'code': {
227+
const kernelId = sessionContext?.session?.kernel?.id;
228+
const settings = ServerConnection.makeSettings();
229+
const apiURL = URLExt.join(
230+
settings.baseUrl,
231+
`api/kernels/${kernelId}/execute`
232+
);
233+
const cellId = cell.model.sharedModel.getId();
234+
const documentId = `json:notebook:${notebook.sharedModel.getState(
235+
'file_id'
236+
)}`;
237+
const body = `{"cell_id":"${cellId}","document_id":"${documentId}"}`;
238+
const init = {
239+
method: 'POST',
240+
body
241+
};
242+
try {
243+
await ServerConnection.makeRequest(apiURL, init, settings);
244+
} catch (error: any) {
245+
throw new ServerConnection.NetworkError(error);
246+
}
247+
break;
248+
}
249+
default:
250+
break;
251+
}
252+
return Promise.resolve(true);
253+
}

packages/collaboration-extension/src/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
menuBarPlugin,
2020
rtcGlobalAwarenessPlugin,
2121
rtcPanelPlugin,
22-
userEditorCursors
22+
userEditorCursors,
23+
notebookCellExecutor
2324
} from './collaboration';
2425
import { sharedLink } from './sharedlink';
2526

@@ -37,7 +38,8 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
3738
rtcGlobalAwarenessPlugin,
3839
rtcPanelPlugin,
3940
sharedLink,
40-
userEditorCursors
41+
userEditorCursors,
42+
notebookCellExecutor
4143
];
4244

4345
export default plugins;

packages/docprovider/jest.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ const jestJupyterLab = require('@jupyterlab/testing/lib/jest-config');
77

88
const esModules = [
99
'@codemirror',
10+
'@microsoft',
11+
'exenv-es6',
1012
'@jupyter/ydoc',
13+
'@jupyter/react-components',
14+
'@jupyter/web-components',
1115
'@jupyterlab/',
1216
'lib0',
1317
'nanoid',

pyproject.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ classifiers = [
2727
"Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
2828
]
2929
dependencies = [
30-
"jupyter_server>=2.0.0,<3.0.0",
30+
"jupyter_server>=2.4.0,<3.0.0",
3131
"jupyter_ydoc>=2.0.0,<3.0.0",
3232
"pycrdt-websocket>=0.12.5,<0.13.0",
3333
"jupyter_events>=0.10.0",
@@ -45,12 +45,12 @@ dev = [
4545
]
4646
test = [
4747
"coverage",
48-
"jupyter_server[test]>=2.0.0",
48+
"jupyter_server[test]>=2.4.0",
4949
"jupyter_server_fileid[test]",
5050
"pytest>=7.0",
5151
"pytest-cov",
5252
"websockets",
53-
"importlib_metadata >=3.6; python_version<'3.10'",
53+
"importlib_metadata >=4.8.3; python_version<'3.10'",
5454
]
5555
docs = [
5656
"jupyterlab>=4.0.0",
Loading
Loading

0 commit comments

Comments
 (0)