Skip to content

Commit

Permalink
IpylabModel initialize changed to async and add ModelConnection.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Fleming committed Sep 16, 2024
1 parent 4ead38b commit df85597
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 52 deletions.
32 changes: 20 additions & 12 deletions ipylab/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import uuid
from typing import TYPE_CHECKING, Any, ClassVar

from ipywidgets import register
from ipywidgets import Widget, register
from traitlets import Bool, Dict, Unicode

from ipylab.asyncwidget import AsyncWidgetBase
from ipylab.asyncwidget import AsyncWidgetBase, pack

if TYPE_CHECKING:
from collections.abc import Generator
Expand Down Expand Up @@ -49,7 +49,7 @@ class Connection(AsyncWidgetBase):
"""

CID_PREFIX = "" # Required in subclassess to discriminate when creating.
_CLASS_DEFINITIONS: ClassVar[dict[str, type[Connection]]] = {}
_CLASS_DEFINITIONS: ClassVar[dict[str, type[Self]]] = {}
_connections: dict[str, Connection] = {} # noqa RUF012
_model_name = Unicode("ConnectionModel").tag(sync=True)
cid = Unicode(read_only=True, help="connection id").tag(sync=True)
Expand All @@ -63,26 +63,25 @@ def __init_subclass__(cls, **kwargs) -> None:
cls._CLASS_DEFINITIONS[cls.CID_PREFIX] = cls # type: ignore
super().__init_subclass__(**kwargs)

def __new__(cls, *, cid: str, id: str | None = None, **kwgs) -> Self: # noqa: A002, ARG003
def __new__(cls, *, cid: str, id: str = "", info: dict | None = None, **kwgs) -> Self: # noqa: A002
if cid not in cls._connections:
if cls.CID_PREFIX and not cid.startswith(cls.CID_PREFIX):
msg = f"Expected prefix '{cls.CID_PREFIX}' not found for {cid=}"
raise ValueError(msg)
# Check if a subclass is registered with 'CID_PREFIX'
cls_ = cls._CLASS_DEFINITIONS.get(cid.split(":")[0], cls) if ":" in cid else cls
kwgs.pop("info", None)
cls._connections[cid] = super().__new__(cls_, **kwgs) # type: ignore
cls._connections[cid] = inst = super().__new__(cls_, **kwgs)
inst.set_trait("cid", cid)
inst.set_trait("id", id)
inst.set_trait("info", info or {})

return cls._connections[cid] # type: ignore

def __init__(self, *, cid: str, model_id=None, id: str | None = None, **kwgs): # noqa: A002
def __init__(self, cid: str, id: str = "", info: dict | None = None, **kwgs): # noqa: A002, ARG002
if self._async_widget_base_init_complete:
return
self.set_trait("cid", cid)
self.set_trait("id", id or "")
info = kwgs.pop("info", None)
if info:
self.set_trait("info", info)
super().__init__(model_id=model_id, **kwgs)
super().__init__(**kwgs)

def __str__(self):
return self.cid
Expand Down Expand Up @@ -153,3 +152,12 @@ async def activate_():
return self

return self.to_task(activate_())


class ModelConnection(Connection):
"""A connection to the model of a widget."""

CID_PREFIX = "ipylab widget connection"

def __new__(cls, widget: Widget, **kwgs) -> Self:
return super().__new__(cls, cid=cls.new_cid(), id=pack(widget), **kwgs) # type: ignore
7 changes: 5 additions & 2 deletions ipylab/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ def __new__(cls, *, model_id=None, **kwgs):
kwgs.pop("basename", None)
return super().__new__(cls, model_id=model_id, **kwgs)

def __init__(self, *, model_id=None, **kwgs):
def __init__(self, *, model_id=None, basename="", **kwgs):
if self._async_widget_base_init_complete:
return
if basename := kwgs.pop("basename", None):
if basename:
self.set_trait("_basename", basename)
super().__init__(model_id=model_id, **kwgs)

Expand Down Expand Up @@ -106,6 +106,9 @@ def add_item(
)
return self.to_task(self._add_to_tuple_trait("items", task))

def activate(self):
return self.execute_method("open")


class MenuConnection(RankedMenu, Connection):
"""A connection to a custom menu"""
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export class CommandRegistryModel extends IpylabModel {
* @param attributes The base attributes.
* @param options The initialization options.
*/
initialize(attributes: any, options: any): void {
super.initialize(attributes, options);
async initialize(attributes: any, options: any): Promise<void> {
await super.initialize(attributes, options);
this.commands.commandChanged.connect(this._sendCommandList, this);
this._sendCommandList();
}
Expand Down
19 changes: 3 additions & 16 deletions src/widgets/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,9 @@ import { IpylabModel } from './ipylab';
*/
export class ConnectionModel extends IpylabModel {
async initialize(attributes: ObjectHash, options: any): Promise<void> {
let base;
const cid = this.get('cid');
const id = this.get('id');
try {
base = this.getConnection(cid, id);
} catch {}
super.initialize(attributes, { ...options, base });
if (base) {
this.base.disposed.connect(() => this.close());
this.on('change:_dispose', this.dispose, this);
} else {
console.log(
`Failed to get connection for cid='${cid}' id='${id}' so closing...`
);
this.close();
}
await super.initialize(attributes, options);
this.base.disposed.connect(() => this.close());
this.on('change:_dispose', this.dispose, this);
}

close(comm_closed?: boolean): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions src/widgets/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export class JupyterFrontEndModel extends IpylabModel {
* @param attributes The base attributes.
* @param options The initialization options.
*/
initialize(attributes: any, options: any): void {
super.initialize(attributes, options);
async initialize(attributes: any, options: any): Promise<void> {
await super.initialize(attributes, options);
this.set('version', this.app.version);
Private.jupyterFrontEndModels.set(this.kernelId, this);

Expand Down
49 changes: 33 additions & 16 deletions src/widgets/ipylab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,31 @@ export {
* Base model for common features
*/
export class IpylabModel extends WidgetModel {
initialize(attributes: ObjectHash, options: any): void {
async initialize(attributes: ObjectHash, options: any): Promise<void> {
super.initialize(attributes, options);
try {
if (this.get('cid')) {
this._base = await this.getConnection(this.get('cid'), this.get('id'));
} else {
const basename = this.get('_basename');
this._base = basename
? getNestedObject({
base: this,
path: basename,
basename: `model_name= '${this.defaults()._model_name}`
})
: this;
}
} catch {
this.close();
throw new Error(`Failed to set the base so closing...`);
}
this.set('kernelId', this.kernelId);
this.on('msg:custom', this._onCustomMessage, this);
this.save_changes();
const msg = `ipylab ${this.get('_model_name')} ready for operations`;
this.send({ init: msg });
onKernelLost(this.kernel, this.close, this);
if (typeof options.base === 'object') {
this._base = options.base;
} else {
const basename = this.get('_basename');
this._base = basename
? getNestedObject({
base: this,
path: basename,
basename: `model_name= '${this.defaults()._model_name}`
})
: this;
}
}

get base(): any {
Expand Down Expand Up @@ -272,7 +277,11 @@ export class IpylabModel extends WidgetModel {
onKernelLost(this.kernel, lw.dispose, lw);
return lw;
}
return this.getConnection(value);
const obj = await this.getConnection(value);
if (!(obj instanceof Widget)) {
throw new Error(`Not a widget '${value}'`);
}
return obj;
}
/**
* Returns the object for the dotted path 'value'.
Expand Down Expand Up @@ -438,11 +447,19 @@ export class IpylabModel extends WidgetModel {
* @param cid Get an object that has been registered as a connection.
* @returns
*/
getConnection(cid: string, id: string | null = null): any {
async getConnection(cid: string, id: string | null = null): Promise<object> {
if (Private.connection.has(cid)) {
return Private.connection.get(cid);
}
const obj = this._getLuminoWidgetFromShell(id || cid);
let obj;
if (id.slice(0, 10) === 'IPY_MODEL_') {
obj = await unpack_models(id, this.widget_manager);
if (!(obj instanceof WidgetModel)) {
throw new Error(`Failed to get model ${id}`);
}
} else {
obj = this._getLuminoWidgetFromShell(id || cid);
}
IpylabModel.registerConnection(obj, cid);
return obj;
}
Expand Down
5 changes: 3 additions & 2 deletions src/widgets/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

import { Notification } from '@jupyterlab/apputils';
import { ObservableDisposableDelegate } from '@lumino/disposable';
import { ObjectHash } from 'backbone';
import { IpylabModel } from './ipylab';
/**
* The model for a notification.
*/
export class NotificationManagerModel extends IpylabModel {
initialize(attributes: Backbone.ObjectHash, options: any): void {
super.initialize(attributes, options);
async initialize(attributes: ObjectHash, options: any): Promise<void> {
await super.initialize(attributes, options);
Notification.manager.changed.connect(this.update, this);
}

Expand Down

0 comments on commit df85597

Please sign in to comment.