Skip to content

Commit

Permalink
Change from using KernelWidgetManager to registerWidgetManager in uti…
Browse files Browse the repository at this point in the history
…ls & update autostart example.
  • Loading branch information
Alan Fleming committed May 26, 2024
1 parent 7050526 commit b1e305c
Show file tree
Hide file tree
Showing 7 changed files with 1,010 additions and 239 deletions.
32 changes: 0 additions & 32 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,41 +48,9 @@
"url": "http://localhost:9999",
"webRoot": "${workspaceFolder}",
"pathMappings": [
{
"url": "webpack://packages/base/src/utils.ts",
"path": "${workspaceFolder}/../jupyterlab/packages/controls/src/utils.ts"
},
{
"url": "webpack://packages/base/src/utils.ts",
"path": "${workspaceFolder}/packages/controls/src/utils.ts"
},
{
"url": "webpack://ipylab/src",
"path": "${workspaceFolder}/src"
},
{
"url": "webpack://ipywidgets/packages",
"path": "${workspaceFolder}/../ipywidgets/packages"
},
{
"url": "webpack://ipywidgets/src",
"path": "${workspaceFolder}/../ipywidgets/src"
},
{
"url": "webpack://jupyterlab/application-top/node_modules/minimist",
"path": "${workspaceFolder}/../jupyterlab/staging"
},
{
"url": "webpack://jupyterlab/application-top/node_modules/minimist",
"path": "${workspaceFolder}/../jupyterlab/dev_mode"
},
{
"url": "webpack://jupyter-widgets/packages",
"path": "${workspaceFolder}/../jupyter-widgets/packages"
},
{
"url": "webpack:///",
"path": "${webRoot}/"
}
],
"presentation": {
Expand Down
28 changes: 8 additions & 20 deletions examples/autostart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"source": [
"# Autostart\n",
"\n",
"Autostart is a feature implemented using the [`pluggy`](https://pluggy.readthedocs.io/en/stable/index.html#pluggy) plugin system. The code associated with the entry point `ipylab-python-backend` will be called (imported) when `ipylab` is activated. `ipylab` will activate when Jupyterlab is started (provided `ipylab` is installed and enabled). \n",
"Autostart is a feature implemented using the [pluggy](https://pluggy.readthedocs.io/en/stable/index.html#pluggy) plugin system. The code associated with the entry point `ipylab-python-backend` will be called (imported) when `ipylab` is activated. `ipylab` will activate when Jupyterlab is started (provided `ipylab` is installed and enabled). \n",
"\n",
"There are no limitations to what can be done. But it is recommended to import on demand to minimise the time required to launch. Some possibilities include:\n",
"* Create and register custom commands;\n",
Expand All @@ -28,7 +28,7 @@
"\n",
"``` toml\n",
"[project.entry-points.ipylab-python-backend]\n",
"my-plugins-name = \"my_module.ipylab_plugin:ipylab_plugin\"\n",
"myproject = \"myproject.pluginmodule\"\n",
"```\n",
"\n",
"In `my_module.autostart.py` write code that will be run once.\n",
Expand Down Expand Up @@ -73,7 +73,7 @@
"\n",
"\n",
"async def create_app():\n",
" # The code in this function is called in the new kernel (session).\n",
" # The code in this function is called in the new kernel.\n",
" # Ensure imports are performed inside the function.\n",
" import ipywidgets as ipw\n",
"\n",
Expand Down Expand Up @@ -128,15 +128,14 @@
"async def start_my_app(cwd): # noqa: ARG001\n",
" global n # noqa: PLW0603\n",
" n += 1\n",
" task = app.execEval(\n",
" # Currently we need to use notebooks for widgets to in a kernel.\n",
" session = await app.newNotebook(f\"test{n}\")\n",
" app.execEval(\n",
" code=create_app,\n",
" user_expressions={\"main_area_widget\": \"create_app()\"},\n",
" path=f\"my app {n}\",\n",
" kernelId=session[\"kernel\"][\"id\"],\n",
" )\n",
" if app.current_widget_id.startswith(\"launcher\"):\n",
" await app.executeMethod(\"app.shell.currentWidget.dispose\")\n",
" return app\n",
" return await task\n",
"\n",
"\n",
"async def register_commands():\n",
Expand Down Expand Up @@ -187,18 +186,7 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import ipywidgets as ipw"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ipw.Label"
]
"source": []
}
],
"metadata": {
Expand Down
8 changes: 4 additions & 4 deletions ipylab/jupyterfrontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def execEval(
self,
code: str | types.ModuleType | Callable,
user_expressions: dict[str, str | types.ModuleType] | None,
sessionId="",
kernelId="",
**kwgs,
) -> asyncio.Task:
"""exec and eval code on the Python kernel.
Expand All @@ -187,16 +187,16 @@ def execEval(
result is awaitable, the result will be awaited.
The serialized result or result of the awaitable will be returned via the frontend.
ref: https://docs.python.org/3/library/functions.html#eval
sessionId:
The the session.
kernelId:
The Id allocated to the kernel in the frontend.
Addnl kwgs:
path, name for a new session.
"""
return self.app.schedule_operation(
"execEval",
code=pack_code(code),
user_expressions=user_expressions,
sessionId=sessionId,
kernelId=kernelId,
**kwgs,
)

Expand Down
69 changes: 40 additions & 29 deletions src/widgets/frontend.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) ipylab contributors
// Distributed under the terms of the Modified BSD License.

import { unpack_models } from '@jupyter-widgets/base';
import { unpack_models, uuid } from '@jupyter-widgets/base';
import { LabShell } from '@jupyterlab/application';
import {
DOMUtils,
Expand All @@ -10,7 +10,7 @@ import {
showErrorMessage
} from '@jupyterlab/apputils';
import { FileDialog } from '@jupyterlab/filebrowser';
import { Session } from '@jupyterlab/services';
import { Kernel, Session } from '@jupyterlab/services';
import {
ISerializers,
IpylabModel,
Expand Down Expand Up @@ -128,7 +128,10 @@ export class JupyterFrontEndModel extends IpylabModel {
payload.manager = IpylabModel.defaultBrowser.model.manager;
return await FileDialog.getExistingDirectory(payload).then(_get_result);
case 'newSession':
result = await newSession(payload);
result = await newSession({
rendermime: IpylabModel.rendermime.clone(),
...payload
});
return result.model as any;
case 'newNotebook':
result = await newNotebook(payload);
Expand Down Expand Up @@ -188,39 +191,47 @@ export class JupyterFrontEndModel extends IpylabModel {

/**
* Obtain the instance of the JupyterFrontEndModel for the sessions kernel.
* If sessionId is not provided, a new session is created.
* If kernelId is not provided, a new kernel is created.
* @param payload
* @returns
*/
async getJupyterFrontEndModel(payload: any): Promise<JupyterFrontEndModel> {
let session: Session.ISessionConnection;
if (payload.sessionId) {
session = this.app.serviceManager.sessions.findById(
payload.sessionId
) as any;
} else {
session = await newSession(payload);
const kernelId = payload.kernelId || uuid();
if (Private.jupyterFrontEndModels.has(kernelId)) {
return Private.jupyterFrontEndModels.get(kernelId);
}
if (!Private.jupyterFrontEndModels.has(session.kernel.id)) {
const future = session.kernel.requestExecute({
code: 'import ipylab;app=ipylab.JupyterFrontEnd()',
store_history: false,
stop_on_error: true,
silent: true,
allow_stdin: false,
user_expressions: { frontendId: 'app.model_id' }
let kernel: Kernel.IKernelConnection;
const model = await this.app.serviceManager.kernels.findById(kernelId);
if (model) {
kernel = this.app.serviceManager.kernels.connectTo({ model: model });
} else {
payload.kernelId = kernelId;
const session = await newSession({
rendermime: IpylabModel.rendermime.clone(),
...payload
});
const result = (await future.done) as any;
if (
result.content.status !== 'ok' ||
!result.content.user_expressions.frontendId
) {
throw new Error(
`Failed to setup the JupyterFrontEnd in the new kernel with traceback=${result.content.traceback}`
);
}
kernel = session.kernel;
}
// Currently we need the kernel to create the JupyterFrontEnd widget.
const future = kernel.requestExecute({
code: 'import ipylab;_jfem=ipylab.JupyterFrontEnd()',
store_history: false,
stop_on_error: true,
silent: true,
allow_stdin: false,
user_expressions: { frontendId: '_jfem.model_id' }
});
const result = (await future.done) as any;
if (
result.content.status !== 'ok' ||
!result.content.user_expressions.frontendId ||
!Private.jupyterFrontEndModels.has(kernelId)
) {
throw new Error(
`Failed to setup the JupyterFrontEnd in the new kernel with traceback=${result.content.traceback}`
);
}
return Private.jupyterFrontEndModels.get(session.kernel.id);
return Private.jupyterFrontEndModels.get(kernelId);
}

static serializers: ISerializers = {
Expand Down
7 changes: 4 additions & 3 deletions src/widgets/python_backend.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Session } from '@jupyterlab/services';
import { newSession } from './utils';
import { IpylabModel } from './ipylab';
import { IDisposable } from '@lumino/disposable';
import { IpylabModel } from './ipylab';
import { newSession } from './utils';
/**
* The Python backend that auto loads python side plugins using `pluggy` module.
*
Expand All @@ -12,6 +12,7 @@ export class PythonBackendModel {
this._backendSession = await newSession({
path: 'Ipylab backend',
name: 'Ipylab backend',
rendermime: IpylabModel.rendermime.clone(),
language: 'python3',
code: 'import ipylab.scripts; ipylab.scripts.init_ipylab_backend()'
});
Expand All @@ -26,7 +27,7 @@ export class PythonBackendModel {
'Start the Ipylab Python backend that will run registered autostart plugins.\n ' +
' in "pyproject.toml" added entry for: \n' +
'[project.entry-points.ipylab-python-backend] \n' +
'my_plugins_name = "my_module.submodule:instance_of_plugin_class"',
'\tmyproject = "myproject.pluginmodule"',

execute: () => IpylabModel.python_backend.checkStart()
}
Expand Down
82 changes: 11 additions & 71 deletions src/widgets/utils.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
// Copyright (c) ipylab contributors
// Distributed under the terms of the Modified BSD License.

import * as base from '@jupyter-widgets/base';
import { JUPYTER_CONTROLS_VERSION } from '@jupyter-widgets/controls/lib/version';
import { KernelWidgetManager } from '@jupyter-widgets/jupyterlab-manager';
import {
OUTPUT_WIDGET_VERSION,
OutputModel,
OutputView
} from '@jupyter-widgets/output';
import { registerWidgetManager } from '@jupyter-widgets/jupyterlab-manager';
import { SessionContext } from '@jupyterlab/apputils';
import { ObservableMap } from '@jupyterlab/observables';
import { Kernel, Session } from '@jupyterlab/services';
import { UUID } from '@lumino/coreutils';
import { Signal } from '@lumino/signaling';
import { IpylabModel, JSONValue, JSONObject } from './ipylab';
import { IpylabModel, JSONObject, JSONValue } from './ipylab';

/**
* Start a new session that support comms needed for iplab needs for comms.
Expand All @@ -23,12 +15,14 @@ import { IpylabModel, JSONValue, JSONObject } from './ipylab';
export async function newSession({
name,
path,
rendermime,
kernelId = '',
language = 'python3',
code = ''
}: {
name: string;
path: string;
rendermime: any;
kernelId?: string;
language?: string;
code?: string;
Expand All @@ -38,7 +32,7 @@ export async function newSession({
specsManager: IpylabModel.app.serviceManager.kernelspecs,
path: path,
name: name ?? path,
type: 'ipylab',
type: 'notebook',
kernelPreference: {
id: kernelId || `${UUID.uuid4()}`,
language: language
Expand All @@ -47,17 +41,13 @@ export async function newSession({
await sessionContext.initialize();
await sessionContext.ready;

// For the moment we'll use a dummy session.
// In future it might be better to support a document...
const session = sessionContext.session;
const manager = new KernelWidgetManager(
session.kernel,
IpylabModel.rendermime
);
// TODO: register widgets from IpyWidgets widget registry.
// Currently it looks like IpyWidgets prefer to be attached to Document.
// Notebooks (.ipynb) are the only implementation provided IpyWidgets (Feb 2024).
// https://github.com/jupyter-widgets/ipywidgets/blob/b2531796d414b0970f18050d6819d932417b9953/python/jupyterlab_widgets/src/plugin.ts#L112

registerWidgets(manager);
const context = {};
(context as any)['sessionContext'] = sessionContext;
(context as any)['saveState'] = new Signal(null);
registerWidgetManager(context as any, rendermime, [] as any);
if (code) {
const future = session.kernel.requestExecute(
{
Expand Down Expand Up @@ -135,56 +125,6 @@ export async function injectCode({
}
}

/**
* Manually register known widgets.
* TODO: use the JuperWidgetRegistry for models instead.
*
* @param manager The new manager
*/
function registerWidgets(manager: KernelWidgetManager) {
manager.register(IpylabModel.exports);
manager.register({
name: '@jupyter-widgets/base',
version: base.JUPYTER_WIDGETS_VERSION,
exports: {
WidgetModel: base.WidgetModel,
WidgetView: base.WidgetView,
DOMWidgetView: base.DOMWidgetView,
DOMWidgetModel: base.DOMWidgetModel,
LayoutModel: base.LayoutModel,
LayoutView: base.LayoutView,
StyleModel: base.StyleModel,
StyleView: base.StyleView,
ErrorWidgetView: base.ErrorWidgetView
}
});
manager.register({
name: '@jupyter-widgets/controls',
version: JUPYTER_CONTROLS_VERSION,
exports: () => {
return new Promise((resolve, reject) => {
(require as any).ensure(
['@jupyter-widgets/controls'],
(require: NodeRequire) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
resolve(require('@jupyter-widgets/controls'));
},
(err: any) => {
reject(err);
},
'@jupyter-widgets/controls'
);
});
}
});

manager.register({
name: '@jupyter-widgets/output',
version: OUTPUT_WIDGET_VERSION,
exports: { OutputModel, OutputView }
});
}

/**
*Returns a nested object relative to `this`.
* @param base The starting object.
Expand Down
Loading

0 comments on commit b1e305c

Please sign in to comment.