Commit 8d65cdbd authored by Boxiang Sun's avatar Boxiang Sun

erp5_notebook: Initialize Pyodide before using js load python extension

parent 902d660b
...@@ -65,6 +65,183 @@ ...@@ -65,6 +65,183 @@
}); });
} }
function loadJSResource(url) {
// Copied from renderJS
return new RSVP.Promise(
function waitForJSLoadEvent(resolve, reject) {
var newScript;
newScript = document.createElement('script');
newScript.async = false;
newScript.type = 'text/javascript';
newScript.onload = function (evt) {
resolve(evt.target.value);
};
newScript.onerror = function (error) {
console.warn(error);
reject(error);
};
newScript.src = url;
document.head.appendChild(newScript);
}
);
}
function loadPyodide(imports, success_callback) {
return new RSVP.Queue()
.push(function () {
return ajax({
url: "pyodide.asm.wasm",
dataType: "arraybuffer"
});
})
.push(function (evt) {
// Compile and instantiate WebAssembly code.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate
return WebAssembly.instantiate(evt.target.response, imports);
})
.push(function (result) {
// This function act as instantiateWasm
// And in instantiateWasm, we have to call the second parameter
// See https://emscripten.org/docs/api_reference/module.html#Module.instantiateWasm
return success_callback(result.instance);
});
}
function checkABI(ABI_number) {
// This was needed by pyodide wasm module
if (ABI_number !== 1) {
throw new Error("Expect ABI number 1, got " + ABI_number);
}
return true;
}
// Pyodide package loading
function pyodideLoadPackage(names) {
var queue, toLoad, package_name, k,
subpackage, packages, loadedPackages, defer;
packages = pyodide_instance.packages.dependencies;
loadedPackages = pyodide_instance.loadedPackages;
// packages is dependency list of each package
// e.g.: [pandas: ['numpy', 'python-dateutil', 'pytz'], numpy: []]
queue = new Array(names);
toLoad = [];
defer = RSVP.defer();
// Find all packages that need to load, store in the "toLoad" list
while (queue.length) {
package_name = queue.pop();
if (loadedPackages.indexOf(package_name) === -1) {
toLoad.push(package_name);
// packages is the dependency list
if (packages.hasOwnProperty(package_name)) {
for (k = 0; k < packages[package_name].length; k += 1) {
subpackage = packages[package_name][k];
if (loadedPackages.indexOf(subpackage) === -1 && toLoad.indexOf(subpackage) === -1) {
queue.push(subpackage);
}
}
} else {
throw new Error("Unknown package " + package_name);
}
}
}
if (toLoad.length === 0) {
// No new packages to load
return;
}
// pyodide built with option "--use-preload-plugins"
// So emscripten load the pyodide itself and its extension using createPreloadedFile.
// See createPreloadedFile section in:
// https://github.com/emscripten-core/emscripten/blob/master/site/source/docs/api_reference/Filesystem-API.rst
// And createPreloadedFile use addRunDependency or removeRunDependency to handle the dependencies,
// it will call monitorRunDependencies.
// https://github.com/emscripten-core/emscripten/blob/master/src/preamble.js#L1842
// The logic in here is if we loaded all wasm module, aka the "left" is 0.
// We load all shared lib to the wasm file system through loadSharedlib.
pyodide_instance.monitorRunDependencies = function (left) {
if (left === 0) {
for (k = 0; k < toLoad.length; k += 1) {
package_name = toLoad[k];
loadedPackages.push(package_name);
}
delete pyodide_instance.monitorRunDependencies;
return loadSharedlib(pyodide_instance._module.FS)
.push(function () {
defer.resolve();
return;
});
}
};
for (k = 0; k < toLoad.length; k += 1) {
package_name = toLoad[k];
loadJSResource(package_name + ".js");
}
pyodide_instance.runPython('import importlib as _importlib\n' +
'_importlib.invalidate_caches()\n');
return defer.promise;
}
function initPyodide() {
var Module = {};
// perform the WebAssembly instantiation action
// See https://emscripten.org/docs/api_reference/module.html#Module.instantiateWasm
Module.instantiateWasm = loadPyodide;
Module.checkABI = checkABI;
window.Module = Module;
return RSVP.Queue()
.push(function () {
return loadJSResource('pyodide.asm.data.js');
})
.push(function () {
return loadJSResource('pyodide.asm.js');
})
.push(function () {
// pyodide is a function defined in pyodide.asm.js
// which set the functions under Module
pyodide_instance = window.pyodide(Module);
var defer = RSVP.defer();
// define postRun to resolve the promise
// When pyodide loading completed, it will call Module.postRun
// to signal the module loaded and ready for next step
// https://github.com/emscripten-core/emscripten/blob/1.38.10/src/preamble.js#L1602
// https://emscripten.org/docs/api_reference/preamble.js.html
Module.postRun = defer.resolve;
return defer.promise;
})
.push(function () {
return ajax({
url: 'packages.json'
});
})
.push(function (evt) {
return JSON.parse(evt.target.response);
})
.push(function (json) {
pyodide_instance._module = Module;
pyodide_instance.loadedPackages = [];
pyodide_instance._module.packages = json;
// Make pyodide happy...
// pyodide.asm.js need window.pyodide:
// https://github.com/iodide-project/pyodide/blob/master/src/runpython.c#L91
// var packageNames = self.pyodide._module.packages.import_name_to_package_name;
// import_name_to_package_name was defined when reading package.json
// https://github.com/iodide-project/pyodide/blob/master/pyodide_build/buildall.py#L58
window.pyodide = pyodide_instance;
window.pyodide.loadPackage = pyodideLoadPackage;
return;
});
}
function parseJSMDCellList(jsmd) { function parseJSMDCellList(jsmd) {
// Split the text into a list of Iodide cells // Split the text into a list of Iodide cells
var line_list = jsmd.split(split_line_regex), var line_list = jsmd.split(split_line_regex),
...@@ -120,6 +297,15 @@ ...@@ -120,6 +297,15 @@
br, br,
code; code;
try { try {
// In order to load Python package,
// we have to initialize pyodide first
if (text.includes('loadPackage') && !is_pyodide_loaded) {
is_pyodide_loaded = true;
return initPyodide()
.push(function () {
return eval.call(window, text);
});
}
return eval.call(window, text); return eval.call(window, text);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
...@@ -143,27 +329,6 @@ ...@@ -143,27 +329,6 @@
document.head.appendChild(style); document.head.appendChild(style);
} }
function loadJSResource(url) {
// Copied from renderJS
return new RSVP.Promise(
function waitForJSLoadEvent(resolve, reject) {
var newScript;
newScript = document.createElement('script');
newScript.async = false;
newScript.type = 'text/javascript';
newScript.onload = function (evt) {
resolve(evt.target.value);
};
newScript.onerror = function (error) {
console.warn(error);
reject(error);
};
newScript.src = url;
document.head.appendChild(newScript);
}
);
}
function deferJSResourceLoading(url) { function deferJSResourceLoading(url) {
return function () { return function () {
return loadJSResource(url); return loadJSResource(url);
...@@ -265,161 +430,6 @@ ...@@ -265,161 +430,6 @@
}); });
} }
function loadPyodide(imports, success_callback) {
return new RSVP.Queue()
.push(function () {
return ajax({
url: "pyodide.asm.wasm",
dataType: "arraybuffer"
});
})
.push(function (evt) {
// Compile and instantiate WebAssembly code.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate
return WebAssembly.instantiate(evt.target.response, imports);
})
.push(function (result) {
// This function act as instantiateWasm
// And in instantiateWasm, we have to call the second parameter
// See https://emscripten.org/docs/api_reference/module.html#Module.instantiateWasm
return success_callback(result.instance);
});
}
function checkABI(ABI_number) {
// This was needed by pyodide wasm module
if (ABI_number !== 1) {
throw new Error("Expect ABI number 1, got " + ABI_number);
}
return true;
}
// Pyodide package loading
function pyodideLoadPackage(names) {
var queue, toLoad, package_name, k,
subpackage, packages, loadedPackages, defer;
packages = pyodide_instance.packages.dependencies;
loadedPackages = pyodide_instance.loadedPackages;
// packages is dependency list of each package
// e.g.: [pandas: ['numpy', 'python-dateutil', 'pytz'], numpy: []]
queue = new Array(names);
toLoad = [];
defer = RSVP.defer();
// Find all packages that need to load, store in the "toLoad" list
while (queue.length) {
package_name = queue.pop();
if (loadedPackages.indexOf(package_name) === -1) {
toLoad.push(package_name);
// packages is the dependency list
if (packages.hasOwnProperty(package_name)) {
for (k = 0; k < packages[package_name].length; k += 1) {
subpackage = packages[package_name][k];
if (loadedPackages.indexOf(subpackage) === -1 && toLoad.indexOf(subpackage) === -1) {
queue.push(subpackage);
}
}
} else {
throw new Error("Unknown package " + package_name);
}
}
}
if (toLoad.length === 0) {
// No new packages to load
return;
}
// pyodide built with option "--use-preload-plugins"
// So emscripten load the pyodide itself and its extension using createPreloadedFile.
// See createPreloadedFile section in:
// https://github.com/emscripten-core/emscripten/blob/master/site/source/docs/api_reference/Filesystem-API.rst
// And createPreloadedFile use addRunDependency or removeRunDependency to handle the dependencies,
// it will call monitorRunDependencies.
// https://github.com/emscripten-core/emscripten/blob/master/src/preamble.js#L1842
// The logic in here is if we loaded all wasm module, aka the "left" is 0.
// We load all shared lib to the wasm file system through loadSharedlib.
pyodide_instance.monitorRunDependencies = function (left) {
if (left === 0) {
for (k = 0; k < toLoad.length; k += 1) {
package_name = toLoad[k];
loadedPackages.push(package_name);
}
delete pyodide_instance.monitorRunDependencies;
return loadSharedlib(pyodide_instance._module.FS)
.push(function () {
defer.resolve();
return;
});
}
};
for (k = 0; k < toLoad.length; k += 1) {
package_name = toLoad[k];
loadJSResource(package_name + ".js");
}
pyodide_instance.runPython('import importlib as _importlib\n' +
'_importlib.invalidate_caches()\n');
return defer.promise;
}
function initPyodide() {
var Module = {};
// perform the WebAssembly instantiation action
// See https://emscripten.org/docs/api_reference/module.html#Module.instantiateWasm
Module.instantiateWasm = loadPyodide;
Module.checkABI = checkABI;
window.Module = Module;
return RSVP.Queue()
.push(function () {
return loadJSResource('pyodide.asm.data.js');
})
.push(function () {
return loadJSResource('pyodide.asm.js');
})
.push(function () {
// pyodide is a function defined in pyodide.asm.js
// which set the functions under Module
pyodide_instance = window.pyodide(Module);
var defer = RSVP.defer();
// define postRun to resolve the promise
// When pyodide loading completed, it will call Module.postRun
// to signal the module loaded and ready for next step
// https://github.com/emscripten-core/emscripten/blob/1.38.10/src/preamble.js#L1602
// https://emscripten.org/docs/api_reference/preamble.js.html
Module.postRun = defer.resolve;
return defer.promise;
})
.push(function () {
return ajax({
url: 'packages.json'
});
})
.push(function (evt) {
return JSON.parse(evt.target.response);
})
.push(function (json) {
pyodide_instance._module = Module;
pyodide_instance.loadedPackages = [];
pyodide_instance._module.packages = json;
// Make pyodide happy...
// pyodide.asm.js need window.pyodide:
// https://github.com/iodide-project/pyodide/blob/master/src/runpython.c#L91
// var packageNames = self.pyodide._module.packages.import_name_to_package_name;
// import_name_to_package_name was defined when reading package.json
// https://github.com/iodide-project/pyodide/blob/master/pyodide_build/buildall.py#L58
window.pyodide = pyodide_instance;
return;
});
}
function renderCodeblock(result_text) { function renderCodeblock(result_text) {
var div = document.createElement('div'), var div = document.createElement('div'),
pre = document.createElement('pre'), pre = document.createElement('pre'),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment