From 6f523ba66eb25cc797727e7ddb66b942cb091e5a Mon Sep 17 00:00:00 2001 From: Alan Fleming <> Date: Mon, 19 Aug 2024 21:29:26 +1000 Subject: [PATCH] Change LuminoWidgetConnection to DisposableConnection --- ipylab/asyncwidget.py | 46 +++++++++--------- ipylab/commands.py | 4 +- ...connection.py => disposable_connection.py} | 32 ++++++++----- ipylab/jupyterfrontend_subsection.py | 23 ++++----- ipylab/sessions.py | 6 +-- ipylab/shell.py | 12 ++--- ipylab/widgets.py | 6 +-- src/widget.ts | 4 +- src/widgets/commands.ts | 10 ++-- ...connection.ts => disposable_connection.ts} | 11 ++--- src/widgets/frontend.ts | 3 +- src/widgets/ipylab.ts | 47 ++++++++++++------- src/widgets/main_area.ts | 4 +- src/widgets/palette.ts | 5 +- src/widgets/python_backend.ts | 3 +- src/widgets/utils.ts | 10 ++-- 16 files changed, 121 insertions(+), 105 deletions(-) rename ipylab/{luminowidget_connection.py => disposable_connection.py} (51%) rename src/widgets/{luminowidget_connection.ts => disposable_connection.ts} (78%) diff --git a/ipylab/asyncwidget.py b/ipylab/asyncwidget.py index e92e35e..d775714 100644 --- a/ipylab/asyncwidget.py +++ b/ipylab/asyncwidget.py @@ -26,18 +26,15 @@ from collections.abc import Callable, Iterable from typing import ClassVar - from ipylab.luminowidget_connection import LuminoWidgetConnection + from ipylab.disposable_connection import DisposableConnection __all__ = ["AsyncWidgetBase", "WidgetBase", "register", "pack", "Widget"] -def pack(obj: Widget | LuminoWidgetConnection | Any): +def pack(obj: Widget | Any): """Return serialized obj if it is a Widget otherwise return it unchanged.""" - from ipylab.luminowidget_connection import LuminoWidgetConnection - if isinstance(obj, LuminoWidgetConnection): - return obj.id if isinstance(obj, Widget): return widget_serialization["to_json"](obj, None) return obj @@ -55,9 +52,9 @@ class TransformMode(StrEnum): - done: [default] A string '--DONE--' - raw: No conversion. Note: data is serialized when sending, some objects shouldn't be serialized. - - string: Result is converted to a string. - attribute: A dotted attribute of the returned object is returned. ['path']='dotted.path.name' - function: Use a function to calculate the return value. ['code'] = 'function...' + - connection: Hopefully return a connection to a disposeable. `attribute` --------- @@ -185,8 +182,11 @@ def _check_closed(self): msg = f"This widget is closed {self!r}" raise RuntimeError(msg) - def new_task(self, coro: asyncio._CoroutineLike): - """Start a task""" + def start_maybe(self, coro: asyncio._CoroutineLike, *, start: bool): + "Run the coro in a task only if start is True returning the coro or task." + self._check_closed() + if not start: + return coro task = asyncio.create_task(coro) self._tasks.add(task) task.add_done_callback(self._tasks.discard) @@ -236,9 +236,9 @@ async def _send_receive(self, content: dict): async def _wait_response_check_error(self, response: Response, content: dict) -> Any: payload = await response.wait() if content["transform"] is TransformMode.connection: - from ipylab.luminowidget_connection import LuminoWidgetConnection + from ipylab.disposable_connection import DisposableConnection - return LuminoWidgetConnection(id=payload) + return DisposableConnection(id=payload) return payload def _on_frontend_msg(self, _, content: dict, buffers: list): @@ -251,7 +251,9 @@ def _on_frontend_msg(self, _, content: dict, buffers: list): if ipylab_backend: self._pending_operations.pop(ipylab_backend).set(payload, error) if "ipylab_FE" in content: - self.new_task(self._handle_frontend_operation(content["ipylab_FE"], operation, payload, buffers)) + self.start_maybe( + self._handle_frontend_operation(content["ipylab_FE"], operation, payload, buffers), start=True + ) elif "init" in content: self._ready_response.set(content) elif "closed" in content: @@ -302,7 +304,7 @@ def schedule_operation( toLuminoWidget: Iterable[str] | None = None, start=True, **kwgs, - ) -> asyncio._AwaitableLike[Dict | str | list | float | int | None | LuminoWidgetConnection]: + ) -> asyncio._AwaitableLike[Dict | str | list | float | int | None | DisposableConnection]: """ operation: str @@ -334,8 +336,7 @@ def schedule_operation( content = {"ipylab_BE": ipylab_BE, "operation": operation, "kwgs": kwgs, "transform": TransformMode(transform)} if toLuminoWidget: content["toLuminoWidget"] = list(map(str, toLuminoWidget)) - coro = self._send_receive(content) - return self.new_task(coro) if start else coro + return self.start_maybe(self._send_receive(content), start=start) def execute_method( self, @@ -388,8 +389,7 @@ async def _list_methods(): return [n for n in payload if not n.startswith("_")] return payload - coro = _list_methods() - return self.new_task(coro) if start else coro + return self.start_maybe(_list_methods(), start=start) def list_attributes( self, @@ -421,15 +421,17 @@ async def list_attributes_(): return groups return payload - coro = list_attributes_() - return self.new_task(coro) if start else coro + return self.start_maybe(list_attributes_(), start=start) - def execute_command(self, command_id: str, *, execute_settings: dict | None = None, **kwgs): - """Execute command_id. + def execute_command(self, command_id: str, *, execute_kwgs: dict | None = None, **kwgs): + """Execute the command_id registered with Jupyterlab. `kwgs` correspond to `args` in JupyterLab. - Finding what the `args` are remains an outstanding issue in JupyterLab. + execute_kwgs: dict | None + Passed to execute_method (we use a dict to avoid any potential of argument clash). + + Finding what `args` can be used remains an outstanding issue in JupyterLab. see: https://github.com/jtpio/ipylab/issues/128#issuecomment-1683097383 for hints about how args can be found. @@ -438,5 +440,5 @@ def execute_command(self, command_id: str, *, execute_settings: dict | None = No "app.commands.execute", command_id, kwgs, # -> used as 'args' in Jupyter - **execute_settings or {}, + **execute_kwgs or {}, ) diff --git a/ipylab/commands.py b/ipylab/commands.py index 1e36d80..d8dc34d 100644 --- a/ipylab/commands.py +++ b/ipylab/commands.py @@ -89,7 +89,7 @@ def addPythonCommand( **kwgs, ) - def removePythonCommand(self, command_id: str): + def removePythonCommand(self, command_id: str, *, start=True): # TODO: check whether to keep this method, or return disposables like in lab if command_id not in self._execute_callbacks: msg = f"{command_id=} is not a registered command!" @@ -106,4 +106,4 @@ async def removePythonCommand_(): await coro_ self._execute_callbacks.pop(command_id, None) - return self.new_task(removePythonCommand_()) + return self.start_maybe(removePythonCommand_(), start=start) diff --git a/ipylab/luminowidget_connection.py b/ipylab/disposable_connection.py similarity index 51% rename from ipylab/luminowidget_connection.py rename to ipylab/disposable_connection.py index 4bfabd9..ec7fed4 100644 --- a/ipylab/luminowidget_connection.py +++ b/ipylab/disposable_connection.py @@ -3,28 +3,32 @@ from __future__ import annotations +import asyncio +import contextlib from typing import ClassVar from ipywidgets import register from traitlets import Unicode from ipylab.asyncwidget import AsyncWidgetBase -from ipylab.hasapp import HasApp +from ipylab.jupyterfrontend_subsection import FrontEndSubsection @register -class LuminoWidgetConnection(AsyncWidgetBase, HasApp): - """A connection to a single Lumino widget in the Jupyterlab shell. +class DisposableConnection(FrontEndSubsection, AsyncWidgetBase): + """A connection to a disposable object in the Frontend. + + The dispose method is directly accesssable, but The comm trait can be observed for when the lumino widget in Jupyterlab is closed. - There is no direct connection to the widget on the frontend, rather, it - can be accessed using the prefix 'widget.' in the method calls: - * execute_method + see: https://lumino.readthedocs.io/en/latest/api/modules/disposable.html + """ - _connections: ClassVar[dict[str, LuminoWidgetConnection]] = {} - _model_name = Unicode("LuminoWidgetConnectionModel").tag(sync=True) + SUB_PATH_BASE = "obj" + _connections: ClassVar[dict[str, DisposableConnection]] = {} + _model_name = Unicode("DisposableConnectionModel").tag(sync=True) id = Unicode(read_only=True).tag(sync=True) def __new__(cls, *, id: str, **kwgs): # noqa: A002 @@ -42,8 +46,12 @@ def close(self): self._connections.pop(self.id, None) super().close() - def dispose(self): - """Close the Lumino widget at the frontend. + def dispose(self, *, start=True) -> asyncio._AwaitableLike[None]: + "Close the disposable on the frontend." + + async def dispose_(): + if self.comm: + with contextlib.suppress(asyncio.CancelledError): + await self.execute_method("dispose", start=start) - Note: The task is cancelled when the connection is closed.""" - return self.execute_method("widget.dispose") + return self.start_maybe(dispose_(), start=start) diff --git a/ipylab/jupyterfrontend_subsection.py b/ipylab/jupyterfrontend_subsection.py index 50830b6..a73794b 100644 --- a/ipylab/jupyterfrontend_subsection.py +++ b/ipylab/jupyterfrontend_subsection.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from ipylab import TransformMode -from ipylab.hasapp import HasApp +from ipylab.asyncwidget import AsyncWidgetBase if TYPE_CHECKING: from collections.abc import Iterable @@ -13,13 +13,13 @@ from ipylab.asyncwidget import TransformType -class JupyterFrontEndSubsection(HasApp): - """Use as a sub section in the JupyterFrontEnd class""" +class FrontEndSubsection(AsyncWidgetBase): + """Direct access to methods on an object relative to the frontend Model.""" - # Point to the attribute on the JupyterFrontEndModel for which this class represents. + # Point to the attribute on the model to which this model corresponds. # Nested attributes are support such as "app.sessionManager" - # see ipylab/src/widgets/frontend.ts -> JupyterFrontEndModel - JFE_JS_SUB_PATH = "" + # see ipylab/src/widgets/ipylab.ts -> IpylabModel + SUB_PATH_BASE = "app" def execute_method( self, @@ -32,8 +32,9 @@ def execute_method( """Execute a nested method on this objects JFE_SUB_PATH relative to the instance of the JupyterFrontEndModel in the JS frontend. """ - return self.app.execute_method( - f"{self.JFE_JS_SUB_PATH}.{method}", + + return super().execute_method( + f"{self.SUB_PATH_BASE}.{method}", *args, transform=transform, toLuminoWidget=toLuminoWidget, @@ -42,12 +43,12 @@ def execute_method( def get_attribute(self, path: str, *, transform: TransformType = TransformMode.raw, start=True): """Get an attribute by name from the front end.""" - return self.app.get_attribute(f"{self.JFE_JS_SUB_PATH}.{path}", transform=transform, start=start) + return super().get_attribute(f"{self.SUB_PATH_BASE}.{path}", transform=transform, start=start) def list_attributes(self, path: str = "", *, transform: TransformType = TransformMode.raw, start=True): """Get a list of all attributes.""" - return self.app.list_attributes( - f"{self.JFE_JS_SUB_PATH}.{path}", + return super().list_attributes( + f"{self.SUB_PATH_BASE}.{path}", transform=transform, start=start, ) diff --git a/ipylab/sessions.py b/ipylab/sessions.py index 8775a15..710c0f0 100644 --- a/ipylab/sessions.py +++ b/ipylab/sessions.py @@ -3,15 +3,15 @@ import asyncio -from ipylab.jupyterfrontend_subsection import JupyterFrontEndSubsection +from ipylab.jupyterfrontend_subsection import FrontEndSubsection -class SessionManager(JupyterFrontEndSubsection): +class SessionManager(FrontEndSubsection): """ https://jupyterlab.readthedocs.io/en/latest/api/interfaces/services.Session.IManager.html """ - JFE_JS_SUB_PATH = "sessionManager" + SUB_PATH_BASE = "app.sessionManager" def refreshRunning(self) -> asyncio.Task: """Force a call to refresh running sessions.""" diff --git a/ipylab/shell.py b/ipylab/shell.py index aa046ae..24ce578 100644 --- a/ipylab/shell.py +++ b/ipylab/shell.py @@ -7,8 +7,8 @@ from ipylab import pack from ipylab.asyncwidget import TransformMode -from ipylab.jupyterfrontend_subsection import JupyterFrontEndSubsection -from ipylab.luminowidget_connection import LuminoWidgetConnection +from ipylab.disposable_connection import DisposableConnection +from ipylab.jupyterfrontend_subsection import FrontEndSubsection if sys.version_info >= (3, 11): from enum import StrEnum @@ -48,7 +48,7 @@ class InsertMode(StrEnum): tab_after = "tab-after" -class Shell(JupyterFrontEndSubsection): +class Shell(FrontEndSubsection): """ Provides access to the shell. The minimal interface is: @@ -59,7 +59,7 @@ class Shell(JupyterFrontEndSubsection): ref: https://jupyterlab.readthedocs.io/en/latest/api/interfaces/application.JupyterFrontEnd.IShell.html#add """ - JFE_JS_SUB_PATH = "shell" + SUB_PATH_BASE = "app.shell" def addToShell( self, @@ -69,7 +69,7 @@ def addToShell( activate: bool = True, mode: InsertMode = InsertMode.split_right, rank: int | None = None, - ref: LuminoWidgetConnection | str = "", + ref: DisposableConnection | str = "", start=True, **options, ): @@ -86,7 +86,7 @@ def addToShell( "activate": activate, "mode": InsertMode(mode), "rank": int(rank) if rank else None, - "ref": ref.id if isinstance(ref, LuminoWidgetConnection) else ref or None, + "ref": ref.id if isinstance(ref, DisposableConnection) else ref or None, } return self.app.schedule_operation( "addToShell", diff --git a/ipylab/widgets.py b/ipylab/widgets.py index 3555f47..88ec94f 100644 --- a/ipylab/widgets.py +++ b/ipylab/widgets.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: import asyncio - from ipylab.luminowidget_connection import LuminoWidgetConnection + from ipylab.disposable_connection import DisposableConnection @register @@ -64,10 +64,10 @@ def addToShell( activate: bool = True, mode: InsertMode = InsertMode.split_right, rank: int | None = None, - ref: LuminoWidgetConnection | str = "", + ref: DisposableConnection | str = "", start=True, **options, - ) -> asyncio._AwaitableLike[LuminoWidgetConnection]: + ) -> asyncio._AwaitableLike[DisposableConnection]: """Add this panel to the shell.""" return self.app.shell.addToShell( self, diff --git a/src/widget.ts b/src/widget.ts index 711e600..8a0210a 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -5,7 +5,7 @@ import { CommandRegistryModel } from './widgets/commands'; import { JupyterFrontEndModel } from './widgets/frontend'; import { IconModel, IconView } from './widgets/icon'; import { IpylabModel } from './widgets/ipylab'; -import { LuminoWidgetConnectionModel } from './widgets/luminowidget_connection'; +import { DisposableConnectionModel } from './widgets/disposable_connection'; import { MainAreaModel } from './widgets/main_area'; import { CommandPaletteModel, LauncherModel } from './widgets/palette'; import { PanelModel, PanelView } from './widgets/panel'; @@ -20,7 +20,7 @@ export { IpylabModel, JupyterFrontEndModel, LauncherModel, - LuminoWidgetConnectionModel, + DisposableConnectionModel, MainAreaModel, PanelModel, PanelView, diff --git a/src/widgets/commands.ts b/src/widgets/commands.ts index fae079b..9c1c12d 100644 --- a/src/widgets/commands.ts +++ b/src/widgets/commands.ts @@ -4,8 +4,7 @@ import { unpack_models } from '@jupyter-widgets/base'; import { ObservableMap } from '@jupyterlab/observables'; import { LabIcon } from '@jupyterlab/ui-components'; -import { IDisposable } from '@lumino/disposable'; -import { ISerializers, IpylabModel, JSONValue, Widget } from './ipylab'; +import { ISerializers, IpylabModel, JSONValue, IDisposable } from './ipylab'; /** * The model for a command registry. @@ -53,7 +52,7 @@ export class CommandRegistryModel extends IpylabModel { return super.close(comm_closed); } - async operation(op: string, payload: any): Promise { + async operation(op: string, payload: any): Promise { let id, args, result, commands; switch (op) { case 'execute': @@ -101,7 +100,7 @@ export class CommandRegistryModel extends IpylabModel { * @param options.iconClass * @param options.icon */ - private async _addCommand(options: any): Promise { + private async _addCommand(options: any): Promise { const { id, caption, label, iconClass, icon, command_result_transform } = options; if (this.commands.hasCommand(id)) { @@ -141,7 +140,8 @@ export class CommandRegistryModel extends IpylabModel { isVisible: () => commandEnabled(command) }); Private.customCommands.set(id, command); - command.id = id; + (command as any).id = id; + IpylabModel.trackDisposable(command); return command; } diff --git a/src/widgets/luminowidget_connection.ts b/src/widgets/disposable_connection.ts similarity index 78% rename from src/widgets/luminowidget_connection.ts rename to src/widgets/disposable_connection.ts index 11d8b63..62756c9 100644 --- a/src/widgets/luminowidget_connection.ts +++ b/src/widgets/disposable_connection.ts @@ -10,18 +10,18 @@ import { IpylabModel } from './ipylab'; * The widget must exist in the shell or have already been added to the tracker. * */ -export class LuminoWidgetConnectionModel extends IpylabModel { +export class DisposableConnectionModel extends IpylabModel { async initialize( attributes: ObjectHash, options: IBackboneModelOptions ): Promise { super.initialize(attributes, options); - this.widget.disposed.connect(() => this.close()); + this.obj.disposed.connect(() => this.close()); } - get widget() { + get obj() { try { - return this.getLuminoWidget(this.get('id')); + return this.getDisposable(this.get('id')); } catch { this.close(); } @@ -33,7 +33,7 @@ export class LuminoWidgetConnectionModel extends IpylabModel { defaults(): any { return { ...super.defaults(), - _model_name: LuminoWidgetConnectionModel.model_name, + _model_name: IpylabModel.disposable_model_name, _model_module: IpylabModel.model_module, _model_module_version: IpylabModel.model_module_version, _view_name: null, @@ -42,7 +42,6 @@ export class LuminoWidgetConnectionModel extends IpylabModel { }; } - static model_name = 'LuminoWidgetConnectionModel'; static serializers = { ...IpylabModel.serializers, content: { deserialize: unpack_models } diff --git a/src/widgets/frontend.ts b/src/widgets/frontend.ts index b096b1c..abcd531 100644 --- a/src/widgets/frontend.ts +++ b/src/widgets/frontend.ts @@ -13,6 +13,7 @@ import { import { FileDialog } from '@jupyterlab/filebrowser'; import { Kernel, Session } from '@jupyterlab/services'; import { + IDisposable, ISerializers, IpylabModel, JSONValue, @@ -96,7 +97,7 @@ export class JupyterFrontEndModel extends IpylabModel { this.save_changes(); } - async operation(op: string, payload: any): Promise { + async operation(op: string, payload: any): Promise { function _get_result(result: any): any { if (result.value === null) { throw new Error('Cancelled'); diff --git a/src/widgets/ipylab.ts b/src/widgets/ipylab.ts index 3415250..c1a6d50 100644 --- a/src/widgets/ipylab.ts +++ b/src/widgets/ipylab.ts @@ -24,6 +24,7 @@ import { PromiseDelegate, UUID } from '@lumino/coreutils'; +import { IDisposable } from '@lumino/disposable'; import { Widget } from '@lumino/widgets'; import { ObjectHash } from 'backbone'; import { MODULE_NAME, MODULE_VERSION } from '../version'; @@ -35,10 +36,10 @@ import { setNestedAttribute, transformObject } from './utils'; - export { CommandRegistry, IBackboneModelOptions, + IDisposable, ILabShell, ILauncher, ISerializers, @@ -181,13 +182,19 @@ export class IpylabModel extends WidgetModel { async toLuminoWidget(value: string): Promise { if (value.slice(0, 10) === 'IPY_MODEL_') { const model = await unpack_models(value, this.widget_manager); + if (model.model_name === IpylabModel.disposable_model_name) { + const widget = this.getDisposable(model.id); + if (!(widget instanceof Widget)) { + throw new Error(`Failed to get a lumio widget for: ${value}`); + } + } const view = await this.widget_manager.create_view(model, {}); const lw = view.luminoWidget; - IpylabModel.trackLuminoWidget(lw); + IpylabModel.trackDisposable(lw); onKernelLost((this.widget_manager as any).kernel, lw.dispose, lw); return lw; } - return this.getLuminoWidget(value); + return this.getDisposable(value); } /** @@ -224,7 +231,7 @@ export class IpylabModel extends WidgetModel { * @param payload Options relevant to the operation. * @returns Raw result of the operation. */ - async operation(op: string, payload: any): Promise { + async operation(op: string, payload: any): Promise { switch (op) { case 'myFunction': // do something @@ -300,27 +307,30 @@ export class IpylabModel extends WidgetModel { * @param id Get a lumino widget using its id. * @returns */ - getLuminoWidget(id: string) { - if (Private.luminoWidgets.has(id)) { - return Private.luminoWidgets.get(id); + getDisposable(id: string) { + if (Private.disposables.has(id)) { + return Private.disposables.get(id); } - const luminoWidget = this._getLuminoWidgetFromShell(id); - IpylabModel.trackLuminoWidget(luminoWidget); - return luminoWidget; + const disposable = this._getLuminoWidgetFromShell(id); + IpylabModel.trackDisposable(disposable); + return disposable; } /** * * @param widget Keep a reference to a Lumino widget so it can be found from the backend. */ - static trackLuminoWidget(luminoWidget: Widget) { - if (!luminoWidget.id) { - luminoWidget.id = DOMUtils.createDomID(); + static trackDisposable(disposable: any) { + if (typeof disposable.dispose !== 'function') { + throw new Error(`Not disposable: ${disposable}`); + } + if (!disposable.id) { + disposable.id = DOMUtils.createDomID(); } - const key = luminoWidget.id; - if (!Private.luminoWidgets.has(key)) { - Private.luminoWidgets.set(key, luminoWidget); - luminoWidget.disposed.connect(() => Private.luminoWidgets.delete(key)); + const key = disposable.id; + if (!Private.disposables.has(key)) { + Private.disposables.set(key, disposable); + disposable.disposed.connect(() => Private.disposables.delete(key)); } } @@ -375,11 +385,12 @@ export class IpylabModel extends WidgetModel { static launcher: ILauncher; static exports: IWidgetRegistryData; static OPERATION_DONE = '--DONE--'; + static disposable_model_name = 'DisposableConnectionModel'; } /** * A namespace for private data */ namespace Private { - export const luminoWidgets = new ObservableMap(); + export const disposables = new ObservableMap(); } diff --git a/src/widgets/main_area.ts b/src/widgets/main_area.ts index 3a9d87a..9f4cb9b 100644 --- a/src/widgets/main_area.ts +++ b/src/widgets/main_area.ts @@ -14,7 +14,7 @@ import { Token } from '@lumino/coreutils'; import { Message } from '@lumino/messaging'; import { SplitPanel, Widget } from '@lumino/widgets'; import { ObjectHash } from 'backbone'; -import { IpylabModel, JSONValue } from './ipylab'; +import { IDisposable, IpylabModel, JSONValue } from './ipylab'; /** * A main area widget with a sessionContext and the ability to add other children. */ @@ -101,7 +101,7 @@ export class MainAreaModel extends IpylabModel { await this.sessionContext.initialize(); } - async operation(op: string, payload: any): Promise { + async operation(op: string, payload: any): Promise { switch (op) { case 'load': // Using lock for mutex diff --git a/src/widgets/palette.ts b/src/widgets/palette.ts index 70155b5..e567f52 100644 --- a/src/widgets/palette.ts +++ b/src/widgets/palette.ts @@ -3,8 +3,7 @@ import { IPaletteItem } from '@jupyterlab/apputils'; import { ObservableMap } from '@jupyterlab/observables'; -import { IDisposable } from '@lumino/disposable'; -import { IpylabModel, JSONValue, Widget } from './ipylab'; +import { IpylabModel, JSONValue, IDisposable } from './ipylab'; /** * The model for a command palette. @@ -35,7 +34,7 @@ export class CommandPaletteModel extends IpylabModel { this._customItems.changed.connect(this._sendItems, this); } - async operation(op: string, payload: any): Promise { + async operation(op: string, payload: any): Promise { switch (op) { case 'addItem': { return this._addItem(payload); diff --git a/src/widgets/python_backend.ts b/src/widgets/python_backend.ts index bd74493..9d488be 100644 --- a/src/widgets/python_backend.ts +++ b/src/widgets/python_backend.ts @@ -1,6 +1,5 @@ import { Session } from '@jupyterlab/services'; -import { IDisposable } from '@lumino/disposable'; -import { IpylabModel } from './ipylab'; +import { IpylabModel, IDisposable } from './ipylab'; import { newSession } from './utils'; /** * The Python backend that auto loads python side plugins using `pluggy` module. diff --git a/src/widgets/utils.ts b/src/widgets/utils.ts index c8a862d..26bb178 100644 --- a/src/widgets/utils.ts +++ b/src/widgets/utils.ts @@ -8,7 +8,7 @@ import { Kernel, Session } from '@jupyterlab/services'; import { UUID } from '@lumino/coreutils'; import { Signal } from '@lumino/signaling'; import { IpylabModel, JSONObject, JSONValue } from './ipylab'; -import { Widget } from '@lumino/widgets'; + /** * Start a new session that support comms needed for iplab needs for comms. * @returns @@ -208,12 +208,8 @@ export async function transformObject( case 'null': return null; case 'connection': - if (obj instanceof Widget) { - IpylabModel.trackLuminoWidget(obj); - return obj.id; - } - throw new Error(`obj is not a lumino widget ${obj}`); - + IpylabModel.trackDisposable(obj); + return obj.id; case 'attribute': // expects simple: {parts:['dotted.attribute']} // or advanced: {parts:[{path:'dotted.attribute', transform:'...' }]