pyodide 0.18.0 → 0.19.0
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/README.md +4 -4
- package/api.js +113 -53
- package/index.test-d.ts +12 -9
- package/load-pyodide.js +141 -112
- package/module.js +37 -9
- package/package.json +1 -1
- package/pyodide.js +145 -157
- package/pyproxy.gen.js +36 -88
package/pyodide.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The main bootstrap code for loading pyodide.
|
|
3
3
|
*/
|
|
4
|
-
import { Module, setStandardStreams } from "./module.js";
|
|
4
|
+
import { Module, setStandardStreams, setHomeDirectory } from "./module.js";
|
|
5
5
|
import {
|
|
6
6
|
loadScript,
|
|
7
7
|
initializePackageIndex,
|
|
8
|
+
_fetchBinaryFile,
|
|
8
9
|
loadPackage,
|
|
9
10
|
} from "./load-pyodide.js";
|
|
10
11
|
import { makePublicAPI, registerJsModule } from "./api.js";
|
|
11
12
|
import "./pyproxy.gen.js";
|
|
12
13
|
|
|
13
|
-
import { wrapNamespace } from "./pyproxy.gen.js";
|
|
14
|
-
|
|
15
14
|
/**
|
|
16
15
|
* @typedef {import('./pyproxy.gen').PyProxy} PyProxy
|
|
17
16
|
* @typedef {import('./pyproxy.gen').PyProxyWithLength} PyProxyWithLength
|
|
@@ -36,7 +35,7 @@ import { wrapNamespace } from "./pyproxy.gen.js";
|
|
|
36
35
|
* @private
|
|
37
36
|
*/
|
|
38
37
|
Module.dump_traceback = function () {
|
|
39
|
-
|
|
38
|
+
const fd_stdout = 1;
|
|
40
39
|
Module.__Py_DumpTraceback(fd_stdout, Module._PyGILState_GetThisThreadState());
|
|
41
40
|
};
|
|
42
41
|
|
|
@@ -44,21 +43,26 @@ let fatal_error_occurred = false;
|
|
|
44
43
|
/**
|
|
45
44
|
* Signal a fatal error.
|
|
46
45
|
*
|
|
47
|
-
* Dumps the Python traceback, shows a
|
|
46
|
+
* Dumps the Python traceback, shows a JavaScript traceback, and prints a clear
|
|
48
47
|
* message indicating a fatal error. It then dummies out the public API so that
|
|
49
48
|
* further attempts to use Pyodide will clearly indicate that Pyodide has failed
|
|
50
|
-
* and can no longer be used. pyodide._module is left accessible and it is
|
|
49
|
+
* and can no longer be used. pyodide._module is left accessible, and it is
|
|
51
50
|
* possible to continue using Pyodide for debugging purposes if desired.
|
|
52
51
|
*
|
|
53
52
|
* @argument e {Error} The cause of the fatal error.
|
|
54
53
|
* @private
|
|
55
54
|
*/
|
|
56
55
|
Module.fatal_error = function (e) {
|
|
56
|
+
if (e.pyodide_fatal_error) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
57
59
|
if (fatal_error_occurred) {
|
|
58
60
|
console.error("Recursive call to fatal_error. Inner error was:");
|
|
59
61
|
console.error(e);
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
64
|
+
// Mark e so we know not to handle it later in EM_JS wrappers
|
|
65
|
+
e.pyodide_fatal_error = true;
|
|
62
66
|
fatal_error_occurred = true;
|
|
63
67
|
console.error(
|
|
64
68
|
"Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers."
|
|
@@ -98,128 +102,179 @@ Module.fatal_error = function (e) {
|
|
|
98
102
|
throw e;
|
|
99
103
|
};
|
|
100
104
|
|
|
105
|
+
let runPythonInternal_dict; // Initialized in finalizeBootstrap
|
|
101
106
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* interpreter is initialized successfully then it should be possible to use
|
|
105
|
-
* this method to run Python code even if everything else in the Pyodide
|
|
106
|
-
* `core` module fails.
|
|
107
|
-
*
|
|
108
|
-
* The differences are:
|
|
109
|
-
* 1. `runPythonSimple` doesn't return anything (and so won't leak
|
|
110
|
-
* PyProxies)
|
|
111
|
-
* 2. `runPythonSimple` doesn't require access to any state on the
|
|
112
|
-
* Javascript `pyodide` module.
|
|
113
|
-
* 3. `runPython` uses `pyodide.eval_code`, whereas `runPythonSimple` uses
|
|
114
|
-
* `PyRun_String` which is the C API for `eval` / `exec`.
|
|
115
|
-
* 4. `runPythonSimple` runs with `globals` a separate dict which is called
|
|
116
|
-
* `init_dict` (keeps global state private)
|
|
117
|
-
* 5. `runPythonSimple` doesn't dedent the argument
|
|
118
|
-
*
|
|
119
|
-
* When `core` initialization is completed, the globals for `runPythonSimple`
|
|
120
|
-
* is made available as `Module.init_dict`.
|
|
121
|
-
*
|
|
107
|
+
* Just like `runPython` except uses a different globals dict and gets
|
|
108
|
+
* `eval_code` from `_pyodide` so that it can work before `pyodide` is imported.
|
|
122
109
|
* @private
|
|
123
110
|
*/
|
|
124
|
-
Module.
|
|
125
|
-
|
|
126
|
-
let errcode;
|
|
127
|
-
try {
|
|
128
|
-
errcode = Module._run_python_simple_inner(code_c_string);
|
|
129
|
-
} catch (e) {
|
|
130
|
-
Module.fatal_error(e);
|
|
131
|
-
} finally {
|
|
132
|
-
Module._free(code_c_string);
|
|
133
|
-
}
|
|
134
|
-
if (errcode === -1) {
|
|
135
|
-
Module._pythonexc2js();
|
|
136
|
-
}
|
|
111
|
+
Module.runPythonInternal = function (code) {
|
|
112
|
+
return Module._pyodide._base.eval_code(code, runPythonInternal_dict);
|
|
137
113
|
};
|
|
138
114
|
|
|
139
115
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
116
|
+
* A proxy around globals that falls back to checking for a builtin if has or
|
|
117
|
+
* get fails to find a global with the given key. Note that this proxy is
|
|
118
|
+
* transparent to js2python: it won't notice that this wrapper exists at all and
|
|
119
|
+
* will translate this proxy to the globals dictionary.
|
|
145
120
|
* @private
|
|
146
121
|
*/
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
122
|
+
function wrapPythonGlobals(globals_dict, builtins_dict) {
|
|
123
|
+
return new Proxy(globals_dict, {
|
|
124
|
+
get(target, symbol) {
|
|
125
|
+
if (symbol === "get") {
|
|
126
|
+
return (key) => {
|
|
127
|
+
let result = target.get(key);
|
|
128
|
+
if (result === undefined) {
|
|
129
|
+
result = builtins_dict.get(key);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (symbol === "has") {
|
|
135
|
+
return (key) => target.has(key) || builtins_dict.has(key);
|
|
136
|
+
}
|
|
137
|
+
return Reflect.get(target, symbol);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
156
141
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
function unpackPyodidePy(pyodide_py_tar) {
|
|
143
|
+
const fileName = "/pyodide_py.tar";
|
|
144
|
+
let stream = Module.FS.open(fileName, "w");
|
|
145
|
+
Module.FS.write(
|
|
146
|
+
stream,
|
|
147
|
+
new Uint8Array(pyodide_py_tar),
|
|
148
|
+
0,
|
|
149
|
+
pyodide_py_tar.byteLength,
|
|
150
|
+
undefined,
|
|
151
|
+
true
|
|
160
152
|
);
|
|
153
|
+
Module.FS.close(stream);
|
|
154
|
+
const code_ptr = Module.stringToNewUTF8(`
|
|
155
|
+
import shutil
|
|
156
|
+
shutil.unpack_archive("/pyodide_py.tar", "/lib/python3.9/site-packages/")
|
|
157
|
+
del shutil
|
|
158
|
+
import importlib
|
|
159
|
+
importlib.invalidate_caches()
|
|
160
|
+
del importlib
|
|
161
|
+
`);
|
|
162
|
+
let errcode = Module._PyRun_SimpleString(code_ptr);
|
|
163
|
+
if (errcode) {
|
|
164
|
+
throw new Error("OOPS!");
|
|
165
|
+
}
|
|
166
|
+
Module._free(code_ptr);
|
|
167
|
+
Module.FS.unlink(fileName);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* This function is called after the emscripten module is finished initializing,
|
|
172
|
+
* so eval_code is newly available.
|
|
173
|
+
* It finishes the bootstrap so that once it is complete, it is possible to use
|
|
174
|
+
* the core `pyodide` apis. (But package loading is not ready quite yet.)
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
function finalizeBootstrap(config) {
|
|
178
|
+
// First make internal dict so that we can use runPythonInternal.
|
|
179
|
+
// runPythonInternal uses a separate namespace, so we don't pollute the main
|
|
180
|
+
// environment with variables from our setup.
|
|
181
|
+
runPythonInternal_dict = Module._pyodide._base.eval_code("{}");
|
|
182
|
+
Module.importlib = Module.runPythonInternal("import importlib; importlib");
|
|
183
|
+
let import_module = Module.importlib.import_module;
|
|
184
|
+
|
|
185
|
+
Module.sys = import_module("sys");
|
|
186
|
+
Module.sys.path.insert(0, config.homedir);
|
|
187
|
+
|
|
188
|
+
// Set up globals
|
|
189
|
+
let globals = Module.runPythonInternal("import __main__; __main__.__dict__");
|
|
190
|
+
let builtins = Module.runPythonInternal("import builtins; builtins.__dict__");
|
|
191
|
+
Module.globals = wrapPythonGlobals(globals, builtins);
|
|
192
|
+
|
|
193
|
+
// Set up key Javascript modules.
|
|
194
|
+
let importhook = Module._pyodide._importhook;
|
|
195
|
+
importhook.register_js_finder();
|
|
196
|
+
importhook.register_js_module("js", config.jsglobals);
|
|
197
|
+
|
|
198
|
+
let pyodide = makePublicAPI();
|
|
199
|
+
importhook.register_js_module("pyodide_js", pyodide);
|
|
200
|
+
|
|
201
|
+
// import pyodide_py. We want to ensure that as much stuff as possible is
|
|
202
|
+
// already set up before importing pyodide_py to simplify development of
|
|
203
|
+
// pyodide_py code (Otherwise it's very hard to keep track of which things
|
|
204
|
+
// aren't set up yet.)
|
|
205
|
+
Module.pyodide_py = import_module("pyodide");
|
|
206
|
+
Module.version = Module.pyodide_py.__version__;
|
|
207
|
+
|
|
208
|
+
// copy some last constants onto public API.
|
|
209
|
+
pyodide.pyodide_py = Module.pyodide_py;
|
|
210
|
+
pyodide.version = Module.version;
|
|
211
|
+
pyodide.globals = Module.globals;
|
|
212
|
+
return pyodide;
|
|
161
213
|
}
|
|
214
|
+
|
|
162
215
|
/**
|
|
163
216
|
* Load the main Pyodide wasm module and initialize it.
|
|
164
217
|
*
|
|
165
|
-
* Only one copy of Pyodide can be loaded in a given
|
|
218
|
+
* Only one copy of Pyodide can be loaded in a given JavaScript global scope
|
|
166
219
|
* because Pyodide uses global variables to load packages. If an attempt is made
|
|
167
220
|
* to load a second copy of Pyodide, :any:`loadPyodide` will throw an error.
|
|
168
221
|
* (This can be fixed once `Firefox adopts support for ES6 modules in webworkers
|
|
169
222
|
* <https://bugzilla.mozilla.org/show_bug.cgi?id=1247687>`_.)
|
|
170
223
|
*
|
|
171
|
-
* @param {{ indexURL : string, fullStdLib? : boolean = true, stdin?: () => string, stdout?: (text: string) => void, stderr?: (text: string) => void }} config
|
|
172
224
|
* @param {string} config.indexURL - The URL from which Pyodide will load
|
|
173
225
|
* packages
|
|
226
|
+
* @param {string} config.homedir - The home directory which Pyodide will use inside virtual file system
|
|
227
|
+
* Default: /home/pyodide
|
|
174
228
|
* @param {boolean} config.fullStdLib - Load the full Python standard library.
|
|
175
229
|
* Setting this to false excludes following modules: distutils.
|
|
176
230
|
* Default: true
|
|
177
|
-
* @param {undefined | (
|
|
231
|
+
* @param {undefined | function(): string} config.stdin - Override the standard input callback. Should ask the user for one line of input.
|
|
178
232
|
* Default: undefined
|
|
179
|
-
* @param {undefined | (
|
|
233
|
+
* @param {undefined | function(string)} config.stdout - Override the standard output callback.
|
|
180
234
|
* Default: undefined
|
|
181
|
-
* @param {undefined | (
|
|
235
|
+
* @param {undefined | function(string)} config.stderr - Override the standard error output callback.
|
|
182
236
|
* Default: undefined
|
|
183
237
|
* @returns The :ref:`js-api-pyodide` module.
|
|
184
238
|
* @memberof globalThis
|
|
185
239
|
* @async
|
|
186
240
|
*/
|
|
187
241
|
export async function loadPyodide(config) {
|
|
242
|
+
if (globalThis.__pyodide_module) {
|
|
243
|
+
throw new Error("Pyodide is already loading.");
|
|
244
|
+
}
|
|
245
|
+
if (!config.indexURL) {
|
|
246
|
+
throw new Error("Please provide indexURL parameter to loadPyodide");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
loadPyodide.inProgress = true;
|
|
250
|
+
// A global "mount point" for the package loaders to talk to pyodide
|
|
251
|
+
// See "--export-name=__pyodide_module" in buildpkg.py
|
|
252
|
+
globalThis.__pyodide_module = Module;
|
|
253
|
+
|
|
188
254
|
const default_config = {
|
|
189
255
|
fullStdLib: true,
|
|
190
256
|
jsglobals: globalThis,
|
|
191
257
|
stdin: globalThis.prompt ? globalThis.prompt : undefined,
|
|
258
|
+
homedir: "/home/pyodide",
|
|
192
259
|
};
|
|
193
260
|
config = Object.assign(default_config, config);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"Pyodide is already loading because languagePluginURL is defined."
|
|
198
|
-
);
|
|
199
|
-
} else {
|
|
200
|
-
throw new Error("Pyodide is already loading.");
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// A global "mount point" for the package loaders to talk to pyodide
|
|
204
|
-
// See "--export-name=__pyodide_module" in buildpkg.py
|
|
205
|
-
globalThis.__pyodide_module = Module;
|
|
206
|
-
loadPyodide.inProgress = true;
|
|
207
|
-
if (!config.indexURL) {
|
|
208
|
-
throw new Error("Please provide indexURL parameter to loadPyodide");
|
|
209
|
-
}
|
|
210
|
-
let baseURL = config.indexURL;
|
|
211
|
-
if (!baseURL.endsWith("/")) {
|
|
212
|
-
baseURL += "/";
|
|
261
|
+
|
|
262
|
+
if (!config.indexURL.endsWith("/")) {
|
|
263
|
+
config.indexURL += "/";
|
|
213
264
|
}
|
|
214
|
-
Module.indexURL =
|
|
215
|
-
let packageIndexReady = initializePackageIndex(
|
|
265
|
+
Module.indexURL = config.indexURL;
|
|
266
|
+
let packageIndexReady = initializePackageIndex(config.indexURL);
|
|
267
|
+
let pyodide_py_tar_promise = _fetchBinaryFile(
|
|
268
|
+
config.indexURL,
|
|
269
|
+
"pyodide_py.tar"
|
|
270
|
+
);
|
|
216
271
|
|
|
217
272
|
setStandardStreams(config.stdin, config.stdout, config.stderr);
|
|
273
|
+
setHomeDirectory(config.homedir);
|
|
218
274
|
|
|
219
|
-
Module.locateFile = (path) => baseURL + path;
|
|
220
275
|
let moduleLoaded = new Promise((r) => (Module.postRun = r));
|
|
221
276
|
|
|
222
|
-
const scriptSrc = `${
|
|
277
|
+
const scriptSrc = `${config.indexURL}pyodide.asm.js`;
|
|
223
278
|
await loadScript(scriptSrc);
|
|
224
279
|
|
|
225
280
|
// _createPyodideModule is specified in the Makefile by the linker flag:
|
|
@@ -230,85 +285,18 @@ export async function loadPyodide(config) {
|
|
|
230
285
|
// being called.
|
|
231
286
|
await moduleLoaded;
|
|
232
287
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// Bootstrap steps:
|
|
237
|
-
//
|
|
238
|
-
// 1. _pyodide_core is ready now so we can call _pyodide.register_js_finder
|
|
239
|
-
// 2. Use the jsfinder to register the js and pyodide_js packages
|
|
240
|
-
// 3. Import pyodide, this requires _pyodide_core, js and pyodide_js to be
|
|
241
|
-
// ready.
|
|
242
|
-
// 4. Add the pyodide_py and Python __main__.__dict__ objects to pyodide_js
|
|
243
|
-
Module.runPythonSimple(`
|
|
244
|
-
def temp(pyodide_js, Module, jsglobals):
|
|
245
|
-
from _pyodide._importhook import register_js_finder
|
|
246
|
-
jsfinder = register_js_finder()
|
|
247
|
-
jsfinder.register_js_module("js", jsglobals)
|
|
248
|
-
jsfinder.register_js_module("pyodide_js", pyodide_js)
|
|
249
|
-
|
|
250
|
-
import pyodide
|
|
251
|
-
import __main__
|
|
252
|
-
import builtins
|
|
253
|
-
|
|
254
|
-
globals = __main__.__dict__
|
|
255
|
-
globals.update(builtins.__dict__)
|
|
256
|
-
|
|
257
|
-
Module.version = pyodide.__version__
|
|
258
|
-
Module.globals = globals
|
|
259
|
-
Module.builtins = builtins.__dict__
|
|
260
|
-
Module.pyodide_py = pyodide
|
|
261
|
-
print("Python initialization complete")
|
|
262
|
-
`);
|
|
263
|
-
|
|
264
|
-
Module.init_dict.get("temp")(pyodide, Module, config.jsglobals);
|
|
265
|
-
// Module.runPython works starting from here!
|
|
266
|
-
|
|
267
|
-
// Wrap "globals" in a special Proxy that allows `pyodide.globals.x` access.
|
|
268
|
-
// TODO: Should we have this?
|
|
269
|
-
Module.globals = wrapNamespace(Module.globals);
|
|
288
|
+
const pyodide_py_tar = await pyodide_py_tar_promise;
|
|
289
|
+
unpackPyodidePy(pyodide_py_tar);
|
|
290
|
+
Module._pyodide_init();
|
|
270
291
|
|
|
271
|
-
pyodide
|
|
272
|
-
|
|
273
|
-
pyodide.version = Module.version;
|
|
292
|
+
let pyodide = finalizeBootstrap(config);
|
|
293
|
+
// Module.runPython works starting here.
|
|
274
294
|
|
|
275
295
|
await packageIndexReady;
|
|
276
296
|
if (config.fullStdLib) {
|
|
277
297
|
await loadPackage(["distutils"]);
|
|
278
298
|
}
|
|
279
|
-
|
|
299
|
+
pyodide.runPython("print('Python initialization complete')");
|
|
280
300
|
return pyodide;
|
|
281
301
|
}
|
|
282
302
|
globalThis.loadPyodide = loadPyodide;
|
|
283
|
-
|
|
284
|
-
if (globalThis.languagePluginUrl) {
|
|
285
|
-
console.warn(
|
|
286
|
-
"languagePluginUrl is deprecated and will be removed in version 0.18.0, " +
|
|
287
|
-
"instead use loadPyodide({ indexURL : <some_url>})"
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* A deprecated parameter that specifies the Pyodide ``indexURL``. If present,
|
|
292
|
-
* Pyodide will automatically invoke
|
|
293
|
-
* ``loadPyodide({indexURL : languagePluginUrl})``
|
|
294
|
-
* and will store the resulting promise in
|
|
295
|
-
* :any:`globalThis.languagePluginLoader`. Use :any:`loadPyodide`
|
|
296
|
-
* directly instead of defining this.
|
|
297
|
-
*
|
|
298
|
-
* @type String
|
|
299
|
-
* @deprecated Will be removed in version 0.18.0
|
|
300
|
-
*/
|
|
301
|
-
globalThis.languagePluginUrl;
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* A deprecated promise that resolves to ``undefined`` when Pyodide is
|
|
305
|
-
* finished loading. Only created if :any:`languagePluginUrl` is
|
|
306
|
-
* defined. Instead use :any:`loadPyodide`.
|
|
307
|
-
*
|
|
308
|
-
* @type Promise
|
|
309
|
-
* @deprecated Will be removed in version 0.18.0
|
|
310
|
-
*/
|
|
311
|
-
globalThis.languagePluginLoader = loadPyodide({
|
|
312
|
-
indexURL: globalThis.languagePluginUrl,
|
|
313
|
-
}).then((pyodide) => (self.pyodide = pyodide));
|
|
314
|
-
}
|