jupyterlab-ipyflow 0.0.228 → 0.0.230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/comm/connect.d.ts +1 -1
- package/lib/comm/connect.js +89 -7
- package/lib/commands/runCellPatch.js +7 -1
- package/package.json +1 -1
package/lib/comm/connect.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { INotebookTracker, Notebook } from '@jupyterlab/notebook';
|
|
|
7
7
|
* dispatcher, and open the comm. Returns a disconnect handler that tears the
|
|
8
8
|
* connection down.
|
|
9
9
|
*/
|
|
10
|
-
export declare function connectToComm(session: ISessionContext, notebooks: INotebookTracker, notebook: Notebook, docManager: IDocumentManager): () => void;
|
|
10
|
+
export declare function connectToComm(session: ISessionContext, notebooks: INotebookTracker, notebook: Notebook, docManager: IDocumentManager, allowSelfEstablish?: boolean): () => void;
|
|
11
11
|
/**
|
|
12
12
|
* Activate-time wiring: track the foreground notebook for the debug hook and
|
|
13
13
|
* register the `ipyflow-client` comm target (plus kernel-restart handling) for
|
package/lib/comm/connect.js
CHANGED
|
@@ -3,16 +3,70 @@ import { updateUI } from '../ui/decorations';
|
|
|
3
3
|
import { clearCellState } from '../ui/dom';
|
|
4
4
|
import { createMessageHandler } from './messageHandlers';
|
|
5
5
|
import { createNotebookEventHandlers } from './notebookEvents';
|
|
6
|
+
/**
|
|
7
|
+
* Pre-install (but do NOT activate) ipyflow under JupyterLite/Pyodide, so the
|
|
8
|
+
* user can `%load_ext ipyflow` without a manual `%pip install` first. This only
|
|
9
|
+
* `pip install`s `ipyflow-core` (resolved offline from the bundled piplite
|
|
10
|
+
* index) -- it does not run `load_ipython_extension`, so nothing activates until
|
|
11
|
+
* the user opts in. On a regular Jupyter kernel the `sys.platform` gate makes it
|
|
12
|
+
* a no-op.
|
|
13
|
+
*
|
|
14
|
+
* Awaited before the comm is set up so it lands as the first execute on the
|
|
15
|
+
* fresh kernel; `import ipyflow` is then ready by the time the user runs
|
|
16
|
+
* `%load_ext ipyflow` (even from a "Run All").
|
|
17
|
+
*/
|
|
18
|
+
async function ensureIpyflowInstalled(session) {
|
|
19
|
+
const kernel = session.session?.kernel;
|
|
20
|
+
if (kernel == null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const code = [
|
|
24
|
+
'import sys as _sys',
|
|
25
|
+
"if _sys.platform == 'emscripten':",
|
|
26
|
+
' try:',
|
|
27
|
+
' import piplite as _piplite',
|
|
28
|
+
// keep_going=True mirrors what the `%pip install` magic does: skip deps that
|
|
29
|
+
// can't be resolved in-browser (e.g. ipykernel/pyzmq, ipywidgets) rather
|
|
30
|
+
// than failing the whole install. ipyflow imports fine without them.
|
|
31
|
+
" await _piplite.install('ipyflow-core', keep_going=True)",
|
|
32
|
+
' except Exception:',
|
|
33
|
+
' pass',
|
|
34
|
+
].join('\n');
|
|
35
|
+
try {
|
|
36
|
+
await kernel.requestExecute({
|
|
37
|
+
code,
|
|
38
|
+
silent: true,
|
|
39
|
+
store_history: false,
|
|
40
|
+
stop_on_error: false,
|
|
41
|
+
}).done;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Expected if the kernel is restarted during the heavy first load; the
|
|
45
|
+
// install re-runs on reconnect (kernelChanged handler in setupComm).
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create the `ipyflow` data comm. We let the kernel comm manager assign the
|
|
50
|
+
* comm id instead of pinning it to a fixed `'ipyflow'`: on a JupyterLite/Pyodide
|
|
51
|
+
* kernel reconnect (e.g. the kernel stalling during the one-time in-browser
|
|
52
|
+
* install) the previous comm may not be disposed yet, and re-creating it under a
|
|
53
|
+
* fixed id throws "Comm is already created", leaving the session with a dead
|
|
54
|
+
* comm. The kernel side keys off the target name, not the id, so a fresh id each
|
|
55
|
+
* time is safe and reconnect-resilient.
|
|
56
|
+
*/
|
|
57
|
+
function createIpyflowComm(session) {
|
|
58
|
+
return session.session.kernel.createComm('ipyflow');
|
|
59
|
+
}
|
|
6
60
|
/**
|
|
7
61
|
* Establish the ipyflow comm for a notebook session: create the store, build
|
|
8
62
|
* the connection context, wire the notebook event handlers and message
|
|
9
63
|
* dispatcher, and open the comm. Returns a disconnect handler that tears the
|
|
10
64
|
* connection down.
|
|
11
65
|
*/
|
|
12
|
-
export function connectToComm(session, notebooks, notebook, docManager) {
|
|
66
|
+
export function connectToComm(session, notebooks, notebook, docManager, allowSelfEstablish = false) {
|
|
13
67
|
const store = initStore(session.session.id);
|
|
14
68
|
store.activeCell = notebook.activeCell;
|
|
15
|
-
store.comm = session
|
|
69
|
+
store.comm = createIpyflowComm(session);
|
|
16
70
|
store.notebook = notebook;
|
|
17
71
|
store.session = session;
|
|
18
72
|
const ipyflowMetadata = notebook.model.getMetadata?.('ipyflow') ?? {};
|
|
@@ -42,7 +96,7 @@ export function connectToComm(session, notebooks, notebook, docManager) {
|
|
|
42
96
|
else if (store.comm.isDisposed) {
|
|
43
97
|
ctx.onEstablishPayload = data;
|
|
44
98
|
const oldComm = store.comm;
|
|
45
|
-
store.comm = session
|
|
99
|
+
store.comm = createIpyflowComm(session);
|
|
46
100
|
store.comm.onMsg = oldComm.onMsg;
|
|
47
101
|
store.comm.open({
|
|
48
102
|
interface: 'jupyterlab',
|
|
@@ -73,6 +127,29 @@ export function connectToComm(session, notebooks, notebook, docManager) {
|
|
|
73
127
|
cell_parents: ctx.ipyflowMetadata?.cell_parents ?? {},
|
|
74
128
|
cell_children: ctx.ipyflowMetadata?.cell_children ?? {},
|
|
75
129
|
});
|
|
130
|
+
// Fallback for kernels that cannot deliver the kernel-side `establish` reply
|
|
131
|
+
// sent from inside their comm_open handler (JupyterLite/Pyodide: the worker is
|
|
132
|
+
// mid-RPC and blocks its event loop between requests, so the reply never
|
|
133
|
+
// lands). comm_open runs synchronously kernel-side there, so by now the kernel
|
|
134
|
+
// has already initialized the comm target; if we still haven't been
|
|
135
|
+
// established shortly after opening, drive the establish handling ourselves.
|
|
136
|
+
//
|
|
137
|
+
// Only do this when the connect was triggered by the `ipyflow-client`
|
|
138
|
+
// establish (i.e. `%load_ext ipyflow` actually ran and registered the comm
|
|
139
|
+
// target) -- NOT for the speculative connect on session-ready. Otherwise a
|
|
140
|
+
// kernel with ipyflow not loaded (e.g. before `%load_ext`) would falsely
|
|
141
|
+
// appear ipyflow-connected. On a normal kernel the real `establish` arrives
|
|
142
|
+
// first and this no-ops regardless.
|
|
143
|
+
if (allowSelfEstablish) {
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
if (ctx.disconnected || store.isIpyflowCommConnected) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
store.comm.onMsg({
|
|
149
|
+
content: { data: { type: 'establish', success: true } },
|
|
150
|
+
});
|
|
151
|
+
}, 2000);
|
|
152
|
+
}
|
|
76
153
|
return commDisconnectHandler;
|
|
77
154
|
}
|
|
78
155
|
/**
|
|
@@ -108,15 +185,19 @@ export function setupComm(notebooks, docManager) {
|
|
|
108
185
|
}
|
|
109
186
|
else if (payload.type === 'establish') {
|
|
110
187
|
commDisconnectHandler();
|
|
111
|
-
|
|
188
|
+
// `%load_ext ipyflow` ran and registered the comm target, so allow
|
|
189
|
+
// the self-establish fallback (the JupyterLite data-comm establish
|
|
190
|
+
// is not delivered on its own).
|
|
191
|
+
commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager, true);
|
|
112
192
|
}
|
|
113
193
|
};
|
|
114
194
|
commDisconnectHandler();
|
|
115
|
-
commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
|
|
195
|
+
commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager, true);
|
|
116
196
|
});
|
|
117
197
|
};
|
|
118
|
-
session.ready.then(() => {
|
|
198
|
+
session.ready.then(async () => {
|
|
119
199
|
clearCellState(nbPanel.content);
|
|
200
|
+
await ensureIpyflowInstalled(session);
|
|
120
201
|
registerCommTarget();
|
|
121
202
|
commDisconnectHandler();
|
|
122
203
|
commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
|
|
@@ -128,7 +209,8 @@ export function setupComm(notebooks, docManager) {
|
|
|
128
209
|
commDisconnectHandler();
|
|
129
210
|
resetStore(session.session.id);
|
|
130
211
|
commDisconnectHandler = () => resetStore(session.session.id);
|
|
131
|
-
session.ready.then(() => {
|
|
212
|
+
session.ready.then(async () => {
|
|
213
|
+
await ensureIpyflowInstalled(session);
|
|
132
214
|
registerCommTarget();
|
|
133
215
|
commDisconnectHandler = connectToComm(session, notebooks, nbPanel.content, docManager);
|
|
134
216
|
});
|
|
@@ -31,7 +31,13 @@ export function patchRunCommands(app, notebooks) {
|
|
|
31
31
|
};
|
|
32
32
|
const isBatchReactive = () => {
|
|
33
33
|
const state = getIpyflowState();
|
|
34
|
-
|
|
34
|
+
// `getIpyflowState` returns an empty `{}` when the store is missing (e.g.
|
|
35
|
+
// during a JupyterLite kernel reconnect, when the store was reset). Guard so
|
|
36
|
+
// running a cell / pressing a shortcut doesn't throw
|
|
37
|
+
// "isBatchReactive is not a function" before the cell is ever submitted.
|
|
38
|
+
return typeof state.isBatchReactive === 'function'
|
|
39
|
+
? state.isBatchReactive()
|
|
40
|
+
: false;
|
|
35
41
|
};
|
|
36
42
|
const executeBatchReactive = (skipFirst = false) => {
|
|
37
43
|
const state = getIpyflowState();
|
package/package.json
CHANGED