Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support server-side execution #279

Merged
merged 10 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ jobs:
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
python_version: "3.11"
python_version: "3.8"
dependency_type: minimum

- name: Install minimum versions
uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1
- name: Install the Python dependencies
run: |
pip install -e ".[test]"

- name: Run the unit tests
run: |
Expand Down
19 changes: 18 additions & 1 deletion docs/source/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ you happen to delete it, there shouldn't be any serious consequence either.
There are a number of settings that you can change:

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

Expand All @@ -29,3 +29,20 @@ jupyter lab --YDocExtension.document_cleanup_delay=100
# The YStore class to use for storing Y updates (default: JupyterSQLiteYStore).
jupyter lab --YDocExtension.ystore_class=pycrdt_websocket.ystore.TempFileYStore
```

There is an experimental feature that is currently only supported by the
[Jupyverse](https://github.com/jupyter-server/jupyverse) server
(not yet with [jupyter-server](https://github.com/jupyter-server/jupyter_server)):
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved
server-side execution. With this, running notebook code cells is not done in the frontend through
the low-level kernel protocol over WebSocket API, but through a high-level REST API. Communication
with the kernel is then delegated to the server, and cell outputs are populated in the notebook
shared document. The frontend gets these outputs changes and shows them live. What this means is
that the notebook state can be recovered even if the frontend disconnects, because cell outputs are
not populated frontend-side but server-side.

This feature is disabled by default, and can be enabled like so:
```bash
pip install "jupyterlab>=4.2.0b0"
pip install "jupyverse[jupyterlab, auth]>=0.4.2"
jupyverse --set kernels.require_yjs=true --set jupyterlab.server_side_execution=true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the command also set the YDocProvider.server_side_execution trait? Or is that automatically picked up by jupyverser in some way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jupyverse doesn't use traits nor the Python side of jupyter-collaboration, so passing --set jupyterlab.server_side_execution=true is all that is needed.

```
14 changes: 13 additions & 1 deletion jupyter_collaboration/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ class YDocExtension(ExtensionApp):
directory.""",
)

server_side_execution = Bool(
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved
False,
config=True,
help="""Whether to execute notebooks in the server using the REST API, not using the kernel
protocol over WebSocket. The frontend only interacts with the notebook through its shared
model.""",
)

def initialize(self):
super().initialize()
self.serverapp.event_logger.register_event_schema(EVENTS_SCHEMA_PATH)
Expand All @@ -74,7 +82,11 @@ def initialize_settings(self):

def initialize_handlers(self):
self.serverapp.web_app.settings.setdefault(
"page_config_data", {"disableRTC": self.disable_rtc}
"page_config_data",
{
"disableRTC": self.disable_rtc,
"serverSideExecution": self.server_side_execution,
},
)

# Set configurable parameters to YStore class
Expand Down
27 changes: 14 additions & 13 deletions packages/collaboration-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,20 @@
"@jupyter/collaboration": "^2.0.11",
"@jupyter/docprovider": "^2.0.11",
"@jupyter/ydoc": "^1.1.0-a0",
"@jupyterlab/application": "^4.0.5",
"@jupyterlab/apputils": "^4.0.5",
"@jupyterlab/codemirror": "^4.0.5",
"@jupyterlab/application": "^4.2.0-beta.0",
"@jupyterlab/apputils": "^4.2.0-beta.0",
"@jupyterlab/codemirror": "^4.2.0-beta.0",
"@jupyterlab/coreutils": "^6.0.5",
"@jupyterlab/docregistry": "^4.0.5",
"@jupyterlab/filebrowser": "^4.0.5",
"@jupyterlab/fileeditor": "^4.0.5",
"@jupyterlab/logconsole": "^4.0.5",
"@jupyterlab/notebook": "^4.0.5",
"@jupyterlab/docregistry": "^4.2.0-beta.0",
"@jupyterlab/filebrowser": "^4.2.0-beta.0",
"@jupyterlab/fileeditor": "^4.2.0-beta.0",
"@jupyterlab/logconsole": "^4.2.0-beta.0",
"@jupyterlab/notebook": "^4.2.0-beta.0",
"@jupyterlab/services": "^7.0.5",
"@jupyterlab/settingregistry": "^4.0.5",
"@jupyterlab/statedb": "^4.0.5",
"@jupyterlab/translation": "^4.0.5",
"@jupyterlab/ui-components": "^4.0.5",
"@jupyterlab/settingregistry": "^4.2.0-beta.0",
"@jupyterlab/statedb": "^4.2.0-beta.0",
"@jupyterlab/translation": "^4.2.0-beta.0",
"@jupyterlab/ui-components": "^4.2.0-beta.0",
"@lumino/commands": "^2.1.0",
"@lumino/widgets": "^2.1.0",
"y-protocols": "^1.0.5",
Expand Down Expand Up @@ -97,7 +97,8 @@
"schemaDir": "./schema",
"outputDir": "../../jupyter_collaboration/labextension",
"disabledExtensions": [
"@jupyterlab/filebrowser-extension:defaultFileBrowser"
"@jupyterlab/filebrowser-extension:defaultFileBrowser",
"@jupyterlab/notebook-extension:cell-executor"
],
"sharedPackages": {
"@codemirror/state": {
Expand Down
64 changes: 63 additions & 1 deletion packages/collaboration-extension/src/collaboration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import {
EditorExtensionRegistry,
IEditorExtensionRegistry
} from '@jupyterlab/codemirror';
import { type MarkdownCell } from '@jupyterlab/cells';
import { INotebookCellExecutor, runCell } from '@jupyterlab/notebook';
import { WebSocketAwarenessProvider } from '@jupyter/docprovider';
import { SidePanel, usersIcon } from '@jupyterlab/ui-components';
import { URLExt } from '@jupyterlab/coreutils';
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
import { ServerConnection } from '@jupyterlab/services';
import { IStateDB, StateDB } from '@jupyterlab/statedb';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
Expand Down Expand Up @@ -189,3 +191,63 @@ export const userEditorCursors: JupyterFrontEndPlugin<void> = {
});
}
};

export const notebookCellExecutor: JupyterFrontEndPlugin<INotebookCellExecutor> =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to consider: do we want this plugin in a separate package in the future so that when we split frontend and backend (#269) we can still load this plugin but not the others? This question does not block merging this PR in any way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But does it make sense to have server-side execution without collaboration? The former is based on the latter, so in my opinion they go together.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be sufficient to have the collaborative drive, right?

{
id: '@jupyter/collaboration-extension:notebook-cell-executor',
description:
'Add notebook cell executor that uses REST API instead of kernel protocol over WebSocket.',
autoStart: true,
provides: INotebookCellExecutor,
activate: (app: JupyterFrontEnd): INotebookCellExecutor => {
if (PageConfig.getOption('serverSideExecution') === 'true') {
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved
return Object.freeze({ runCell: runCellServerSide });
}
return Object.freeze({ runCell });
}
};

async function runCellServerSide({
cell,
notebook,
notebookConfig,
onCellExecuted,
onCellExecutionScheduled,
sessionContext,
sessionDialogs,
translator
}: INotebookCellExecutor.IRunCellOptions): Promise<boolean> {
switch (cell.model.type) {
case 'markdown':
(cell as MarkdownCell).rendered = true;
cell.inputHidden = false;
onCellExecuted({ cell, success: true });
break;
case 'code': {
const kernelId = sessionContext?.session?.kernel?.id;
const settings = ServerConnection.makeSettings();
const apiURL = URLExt.join(
settings.baseUrl,
`api/kernels/${kernelId}/execute`
);
const cellId = cell.model.sharedModel.getId();
const documentId = `json:notebook:${notebook.sharedModel.getState(
'file_id'
)}`;
const body = `{"cell_id":"${cellId}","document_id":"${documentId}"}`;
const init = {
method: 'POST',
body
};
try {
await ServerConnection.makeRequest(apiURL, init, settings);
} catch (error: any) {
throw new ServerConnection.NetworkError(error);
}
break;
}
default:
break;
}
return Promise.resolve(true);
}
6 changes: 4 additions & 2 deletions packages/collaboration-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
menuBarPlugin,
rtcGlobalAwarenessPlugin,
rtcPanelPlugin,
userEditorCursors
userEditorCursors,
notebookCellExecutor
} from './collaboration';
import { sharedLink } from './sharedlink';

Expand All @@ -37,7 +38,8 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
rtcGlobalAwarenessPlugin,
rtcPanelPlugin,
sharedLink,
userEditorCursors
userEditorCursors,
notebookCellExecutor
];

export default plugins;
4 changes: 4 additions & 0 deletions packages/docprovider/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ const jestJupyterLab = require('@jupyterlab/testing/lib/jest-config');

const esModules = [
'@codemirror',
'@microsoft',
'exenv-es6',
'@jupyter/ydoc',
'@jupyter/react-components',
'@jupyter/web-components',
'@jupyterlab/',
'lib0',
'nanoid',
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ classifiers = [
"Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
]
dependencies = [
"jupyter_server>=2.0.0,<3.0.0",
"jupyter_server>=2.4.0,<3.0.0",
"jupyter_ydoc>=2.0.0,<3.0.0",
"pycrdt-websocket>=0.12.5,<0.13.0",
"jupyter_events>=0.10.0",
Expand All @@ -44,12 +44,12 @@ dev = [
]
test = [
"coverage",
"jupyter_server[test]>=2.0.0",
"jupyter_server[test]>=2.4.0",
"jupyter_server_fileid[test]",
"pytest>=7.0",
"pytest-cov",
"websockets",
"importlib_metadata >=3.6; python_version<'3.10'",
"importlib_metadata >=4.8.3; python_version<'3.10'",
]
docs = [
"jupyterlab>=4.0.0",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading