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

DRAFT: Single widget manager per kernel #3922

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
07ce11b
Per-kernel widget manager.
May 27, 2024
5bb3c52
Add attachToRendermime function.
May 29, 2024
9452054
Update lock file.
May 29, 2024
90e010f
WidgetModel: set comm_live to false when comm closed and remove redun…
May 31, 2024
7715db9
Improve ManagerBase_loadFromKernel
May 31, 2024
f7c2dcf
Update jupyterlab_widgets package.json
May 31, 2024
9cd1b47
Improved restoring widgets when opening and closing of notebooks.
May 31, 2024
fc3967c
manager and render code refactoring.
Jun 1, 2024
3819e7d
Avoid generating a warning "Failed to fetch ipywidgets through the "j…
Jun 1, 2024
b873722
WidgetModel.close - catch any error when closing comm to ensure closi…
Jun 2, 2024
b830b76
Improved manager logic and re-rendering when the kernel is re-connected.
Jun 2, 2024
a7a6d5f
Added delays for getWidgetManager and get_model if the model isn't im…
Jun 2, 2024
ab0c544
Manager, WidgetManager and plugin refactoring (simplification) . Warn…
Jun 3, 2024
658f151
Change packaging workflow to jupyterlab~4.0.
Jun 4, 2024
70864f9
WidgetManager - do nothing if rendermime is the global rendermime ins…
Jun 9, 2024
23d9b2b
Fix not awaiting delay promise in get_model.
Jun 9, 2024
b6a01b0
Merge branch 'main' of https://github.com/jupyter-widgets/ipywidgets …
Aug 31, 2024
66a2d33
Update yarn.lock
Aug 31, 2024
63e47d3
Add kernel monitoring.
Sep 8, 2024
29da2bd
Changed getWidgetManager to be a static method of KernelWidgetManage…
Sep 25, 2024
43dfd3f
Tweak restoration
Nov 2, 2024
79d6a0c
Merge branch 'main' of https://github.com/jupyter-widgets/ipywidgets …
Nov 2, 2024
86b5b0e
Improved KernelWidgetManager creation and kernel restoration.
Nov 3, 2024
fb8adbe
Create new comm for existing models when restoring from kernel.
Nov 8, 2024
119a5bf
Output widget - modified so layout applies to the OutputArea directly…
Nov 14, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Check the JupyterLab extension is installed
if: matrix.dist != 'widgetsnbextension*.tar.gz'
run: |
python -m pip install jupyterlab~=3.0
python -m pip install jupyterlab~=4.0
jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "@jupyter-widgets/jupyterlab-manager.*enabled.*ok" -
Expand Down
33 changes: 24 additions & 9 deletions packages/base-manager/src/manager-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,14 @@ export abstract class ManagerBase implements IWidgetManager {
*
* If you would like to synchronously test if a model exists, use .has_model().
*/
async get_model(model_id: string): Promise<WidgetModel> {
async get_model(model_id: string, delays = [500]): Promise<WidgetModel> {
let i = 0;
while (!this._models[model_id] && i < delays.length) {
await new Promise((resolve) => setTimeout(resolve, delays[i++]));
}
const modelPromise = this._models[model_id];
if (modelPromise === undefined) {
throw new Error('widget model not found');
throw new Error(`widget model '${model_id}' not found`);
}
return modelPromise;
}
Expand Down Expand Up @@ -383,15 +387,21 @@ export abstract class ManagerBase implements IWidgetManager {
// Try fetching all widget states through the control comm
let data: any;
let buffers: any;
let timeoutID: number | undefined;
const comm_ids = await this._get_comm_info();
if (Object.keys(comm_ids).length === 0) {
// There is nothing to load, either a new kernel or no widgets in the kernel.
return Promise.resolve();
}
try {
const initComm = await this._create_comm(
CONTROL_COMM_TARGET,
uuid(),
{},
{ version: CONTROL_COMM_PROTOCOL_VERSION }
);

await new Promise((resolve, reject) => {
let succeeded = false;
initComm.on_msg((msg: any) => {
data = msg['content']['data'];

Expand All @@ -409,22 +419,24 @@ export abstract class ManagerBase implements IWidgetManager {
return new DataView(b instanceof ArrayBuffer ? b : b.buffer);
}
});

succeeded = true;
clearTimeout(timeoutID);
resolve(null);
});

initComm.on_close(() => reject('Control comm was closed too early'));
initComm.on_close(() => {
if (!succeeded) reject('Control comm was closed too early');
});

// Send a states request msg
initComm.send({ method: 'request_states' }, {});

// Reject if we didn't get a response in time
setTimeout(
timeoutID = window.setTimeout(
() => reject('Control comm did not respond in time'),
CONTROL_COMM_TIMEOUT
);
});

initComm.close();
} catch (error) {
// Fall back to the old implementation for old ipywidgets backend versions (ipywidgets<=7.6)
Expand Down Expand Up @@ -487,6 +499,9 @@ export abstract class ManagerBase implements IWidgetManager {
model.constructor as typeof WidgetModel
)._deserialize_state(state.state, this);
model!.set_state(deserializedState);
if (!model.comm_live) {
model.comm = await this._create_comm('jupyter.widget', widget_id);
}
}
} catch (error) {
// Failed to create a widget model, we continue creating other models so that
Expand Down Expand Up @@ -738,12 +753,12 @@ export abstract class ManagerBase implements IWidgetManager {

/**
* Disconnect the widget manager from the kernel, setting each model's comm
* as dead.
* as undefined.
*/
disconnect(): void {
Object.keys(this._models).forEach((i) => {
this._models[i].then((model) => {
model.comm_live = false;
model.comm = undefined;
});
});
}
Expand Down
4 changes: 3 additions & 1 deletion packages/base/src/errorwidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export function createErrorWidgetModel(
error: error,
};
super(attributes, options);
this.comm_live = true;
}
get comm_live(): boolean {
return true;
}
}
return ErrorWidget;
Expand Down
63 changes: 32 additions & 31 deletions packages/base/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class WidgetModel extends Backbone.Model {
// Attributes should be initialized here, since user initialization may depend on it
this.widget_manager = options.widget_manager;
this.model_id = options.model_id;
const comm = options.comm;
this.comm = options.comm;

this.views = Object.create(null);
this.state_change = Promise.resolve();
Expand All @@ -174,27 +174,23 @@ export class WidgetModel extends Backbone.Model {
// _buffered_state_diff must be created *after* the super.initialize
// call above. See the note in the set() method below.
this._buffered_state_diff = {};
}

if (comm) {
// Remember comm associated with the model.
this.comm = comm;
get comm() {
return this._comm;
}

// Hook comm messages up to model.
set comm(comm: IClassicComm | undefined) {
this._comm = comm;
if (comm) {
comm.on_close(this._handle_comm_closed.bind(this));
comm.on_msg(this._handle_comm_msg.bind(this));

this.comm_live = true;
} else {
this.comm_live = false;
}
this.trigger('comm_live_update');
}

get comm_live(): boolean {
return this._comm_live;
}
set comm_live(x) {
this._comm_live = x;
this.trigger('comm_live_update');
get comm_live() {
return Boolean(this.comm);
}

/**
Expand All @@ -218,39 +214,46 @@ export class WidgetModel extends Backbone.Model {
*
* @returns - a promise that is fulfilled when all the associated views have been removed.
*/
close(comm_closed = false): Promise<void> {
async close(comm_closed = false): Promise<void> {
// can only be closed once.
if (this._closed) {
return Promise.resolve();
return;
}
this._closed = true;
if (this.comm && !comm_closed) {
this.comm.close();
if (this._comm && !comm_closed) {
try {
this._comm.close();
} catch (err) {
// Do Nothing
}
}
this.stopListening();
this.trigger('destroy', this);
if (this.comm) {
delete this.comm;
}
delete this._comm;

// Delete all views of this model
if (this.views) {
const views = Object.keys(this.views).map((id: string) => {
return this.views![id].then((view) => view.remove());
});
delete this.views;
return Promise.all(views).then(() => {
return;
});
await Promise.all(views);
return;
}
return Promise.resolve();
}

/**
* Handle when a widget comm is closed.
*/
_handle_comm_closed(msg: KernelMessage.ICommCloseMsg): void {
if (!this.comm) {
return;
}
this.comm = undefined;
this.trigger('comm:close');
this.close(true);
if (!this._closed) {
this.close(true);
}
}

/**
Expand Down Expand Up @@ -635,7 +638,7 @@ export class WidgetModel extends Backbone.Model {
* This invokes a Backbone.Sync.
*/
save_changes(callbacks?: {}): void {
if (this.comm_live) {
if (this.comm) {
const options: any = { patch: true };
if (callbacks) {
options.callbacks = callbacks;
Expand Down Expand Up @@ -721,11 +724,9 @@ export class WidgetModel extends Backbone.Model {
model_id: string;
views?: { [key: string]: Promise<WidgetView> };
state_change: Promise<any>;
comm?: IClassicComm;
name: string;
module: string;

private _comm_live: boolean;
private _comm?: IClassicComm;
private _closed: boolean;
private _state_lock: any;
private _buffered_state_diff: any;
Expand Down
14 changes: 7 additions & 7 deletions packages/base/test/src/widget_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ describe('WidgetModel', function () {
});
});

it('sets the widget_manager, id, comm, and comm_live properties', function () {
const widgetDead = new WidgetModel({}, {
model_id: 'widgetDead',
it('sets the widget_manager, id, comm, properties', function () {
const widgetNoComm = new WidgetModel({}, {
model_id: 'noComm',
widget_manager: this.manager,
} as IBackboneModelOptions);
expect(widgetDead.model_id).to.equal('widgetDead');
expect(widgetDead.widget_manager).to.equal(this.manager);
expect(widgetDead.comm).to.be.undefined;
expect(widgetDead.comm_live).to.be.false;
expect(widgetNoComm.model_id).to.equal('noComm');
expect(widgetNoComm.widget_manager).to.equal(this.manager);
expect(widgetNoComm.comm).to.be.undefined;
expect(widgetNoComm.comm_live).to.be.false;

const comm = new MockComm();
const widgetLive = new WidgetModel({}, {
Expand Down
12 changes: 12 additions & 0 deletions packages/controls/css/widgets-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@
flex-direction: column;
}

.jupyter-widget-output {
box-sizing: border-box;
display: flex;
margin: 0;
overflow: auto;
flex-direction: column;
}
.jupyter-widget-output .jp-OutputArea-prompt{
width: 0;
flex : 0;
}

/* General Tags Styling */

.jupyter-widget-tagsinput {
Expand Down
3 changes: 1 addition & 2 deletions python/jupyterlab_widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
"@jupyter-widgets/controls": "^5.0.11",
"@jupyter-widgets/output": "^6.0.10",
"@jupyterlab/application": "^3.0.0 || ^4.0.0",
"@jupyterlab/apputils": "^3.0.0 || ^4.0.0",
"@jupyterlab/console": "^3.0.0 || ^4.0.0",
"@jupyterlab/docregistry": "^3.0.0 || ^4.0.0",
"@jupyterlab/logconsole": "^3.0.0 || ^4.0.0",
"@jupyterlab/mainmenu": "^3.0.0 || ^4.0.0",
"@jupyterlab/nbformat": "^3.0.0 || ^4.0.0",
"@jupyterlab/notebook": "^3.0.0 || ^4.0.0",
"@jupyterlab/observables": "^5.2.1",
"@jupyterlab/outputarea": "^3.0.0 || ^4.0.0",
"@jupyterlab/rendermime": "^3.0.0 || ^4.0.0",
"@jupyterlab/rendermime-interfaces": "^3.0.0 || ^4.0.0",
Expand All @@ -75,7 +75,6 @@
},
"devDependencies": {
"@jupyterlab/builder": "^3.0.0 || ^4.0.0",
"@jupyterlab/cells": "^3.0.0 || ^4.0.0",
"@types/jquery": "^3.5.16",
"@types/semver": "^7.3.6",
"@typescript-eslint/eslint-plugin": "^5.8.0",
Expand Down
6 changes: 1 addition & 5 deletions python/jupyterlab_widgets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ export default WidgetManagerProvider;

export { registerWidgetManager } from './plugin';

export {
KernelWidgetManager,
LabWidgetManager,
WidgetManager,
} from './manager';
export { KernelWidgetManager, WidgetManager } from './manager';

export { WidgetRenderer } from './renderer';

Expand Down
Loading