Skip to content

Commit 89dc514

Browse files
bollwyvlmaartenbreddels
authored andcommitted
fix,feat: add pack_models
1 parent 81736bb commit 89dc514

File tree

3 files changed

+64
-12
lines changed

3 files changed

+64
-12
lines changed

packages/base/src/widget.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import $ from 'jquery';
99

1010
import { NativeView } from './nativeview';
1111

12-
import { JSONObject, JSONValue } from '@lumino/coreutils';
12+
import { JSONObject, JSONValue, JSONExt } from '@lumino/coreutils';
1313

1414
import { Message, MessageLoop } from '@lumino/messaging';
1515

@@ -29,6 +29,17 @@ import { BufferJSON, Dict } from './utils';
2929

3030
import { KernelMessage } from '@jupyterlab/services';
3131

32+
/**
33+
* The magic key used in the widget graph serialization.
34+
*/
35+
const IPY_MODEL_ = 'IPY_MODEL_';
36+
37+
/**
38+
* A best-effort method for performing deep copies.
39+
*/
40+
const deepcopy =
41+
globalThis.structuredClone || ((x: any) => JSON.parse(JSON.stringify(x)));
42+
3243
/**
3344
* Replace model ids with models recursively.
3445
*/
@@ -38,24 +49,51 @@ export function unpack_models(
3849
): Promise<WidgetModel | Dict<WidgetModel> | WidgetModel[] | any> {
3950
if (Array.isArray(value)) {
4051
const unpacked: any[] = [];
41-
value.forEach((sub_value, key) => {
52+
for (const sub_value of value) {
4253
unpacked.push(unpack_models(sub_value, manager));
43-
});
54+
}
4455
return Promise.all(unpacked);
4556
} else if (value instanceof Object && typeof value !== 'string') {
4657
const unpacked: { [key: string]: any } = {};
47-
Object.keys(value).forEach((key) => {
48-
unpacked[key] = unpack_models(value[key], manager);
49-
});
58+
for (const [key, sub_value] of Object.entries(value)) {
59+
unpacked[key] = unpack_models(sub_value, manager);
60+
}
5061
return utils.resolvePromisesDict(unpacked);
51-
} else if (typeof value === 'string' && value.slice(0, 10) === 'IPY_MODEL_') {
62+
} else if (typeof value === 'string' && value.slice(0, 10) === IPY_MODEL_) {
5263
// get_model returns a promise already
5364
return manager!.get_model(value.slice(10, value.length));
5465
} else {
5566
return Promise.resolve(value);
5667
}
5768
}
5869

70+
/** Replace models with ids recursively.
71+
*
72+
* If the commonly-used `unpack_models` is given as the `seralize` method,
73+
* and no `deserialize` is given, this will be used as a default.
74+
*/
75+
export function pack_models(
76+
value: WidgetModel | Dict<WidgetModel> | WidgetModel[] | any,
77+
widget?: WidgetModel
78+
): any | Dict<unknown> | string | (Dict<unknown> | string)[] {
79+
if (Array.isArray(value)) {
80+
const model_ids: string[] = [];
81+
for (const model of value) {
82+
model_ids.push(pack_models(model, widget));
83+
}
84+
return model_ids;
85+
} else if (value instanceof WidgetModel) {
86+
return `${IPY_MODEL_}${value.model_id}`;
87+
} else if (value instanceof Object && typeof value !== 'string') {
88+
const packed: { [key: string]: string } = {};
89+
for (const [key, sub_value] of Object.entries(value)) {
90+
packed[key] = pack_models(sub_value, widget);
91+
}
92+
} else {
93+
return value;
94+
}
95+
}
96+
5997
/**
6098
* Type declaration for general widget serializers.
6199
*/
@@ -524,18 +562,26 @@ export class WidgetModel extends Backbone.Model {
524562
* binary array buffers.
525563
*/
526564
serialize(state: Dict<any>): JSONObject {
527-
const deepcopy =
528-
globalThis.structuredClone || ((x: any) => JSON.parse(JSON.stringify(x)));
529565
const serializers =
530-
(this.constructor as typeof WidgetModel).serializers || {};
566+
(this.constructor as typeof WidgetModel).serializers ||
567+
JSONExt.emptyObject;
531568
for (const k of Object.keys(state)) {
532569
try {
533-
if (serializers[k] && serializers[k].serialize) {
534-
state[k] = serializers[k].serialize!(state[k], this);
570+
const keySerializers = serializers[k] || JSONExt.emptyObject;
571+
let { serialize } = keySerializers;
572+
573+
if (serialize == null && keySerializers.deserialize === unpack_models) {
574+
// handle https://github.com/jupyter-widgets/ipywidgets/issues/3735
575+
serialize = pack_models;
576+
}
577+
578+
if (serialize) {
579+
state[k] = serialize(state[k], this);
535580
} else {
536581
// the default serializer just deep-copies the object
537582
state[k] = deepcopy(state[k]);
538583
}
584+
539585
if (state[k] && state[k].toJSON) {
540586
state[k] = state[k].toJSON();
541587
}

packages/base/test/karma-cov.conf.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ module.exports = function (config) {
1414
{ type: 'html', dir: 'test/coverage' },
1515
],
1616
},
17+
mochaReporter: {
18+
showDiff: true
19+
},
1720
port: 9876,
1821
colors: true,
1922
singleRun: true,

packages/base/test/karma.conf.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ module.exports = function (config) {
33
basePath: '..',
44
frameworks: ['mocha'],
55
reporters: ['mocha'],
6+
mochaReporter: {
7+
showDiff: true
8+
},
69
files: ['test/build/bundle.js'],
710
port: 9876,
811
colors: true,

0 commit comments

Comments
 (0)