pyodide 0.18.2 → 0.19.1
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/error_handling.gen.ts +294 -0
- 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 → pyproxy.gen.ts} +414 -607
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Pyodide
|
|
1
|
+
# Pyodide JavaScript package
|
|
2
2
|
|
|
3
3
|
<a href="https://www.npmjs.com/package/pyodide"><img src="https://img.shields.io/npm/v/pyodide" alt="npm"></a>
|
|
4
4
|
|
|
@@ -26,15 +26,15 @@ See the [documentation](https://pyodide.org/en/stable/) fore more details.
|
|
|
26
26
|
|
|
27
27
|
## Details
|
|
28
28
|
|
|
29
|
-
The
|
|
29
|
+
The JavaScript code in this package is responsible for the following tasks:
|
|
30
30
|
|
|
31
|
-
1. Defines the public [
|
|
31
|
+
1. Defines the public [JavaScript API](https://pyodide.org/en/stable/usage/api/js-api.html)
|
|
32
32
|
- Package loading code to allow loading of other Python packages.
|
|
33
33
|
- Can load
|
|
34
34
|
[micropip](https://pyodide.org/en/stable/usage/api/micropip-api.html) to
|
|
35
35
|
bootstrap loading of pure Python wheels
|
|
36
36
|
2. Loads the CPython interpreter and the core/pyodide emscripten application
|
|
37
37
|
which embeds the interpreter.
|
|
38
|
-
3. Injects the `js/pyodide`
|
|
38
|
+
3. Injects the `js/pyodide` JavaScript API into `sys.modules`. This is the
|
|
39
39
|
final runtime dependency for `core/pyodide` & `py/pyodide`, so after this step
|
|
40
40
|
the interpreter is fully up and running.
|
package/api.js
CHANGED
|
@@ -8,17 +8,18 @@ export { loadPackage, loadedPackages, isPyProxy };
|
|
|
8
8
|
* @typedef {import('./pyproxy.gen').PyProxy} PyProxy
|
|
9
9
|
* @typedef {import('./pyproxy.gen').TypedArray} TypedArray
|
|
10
10
|
* @typedef {import('emscripten')} Emscripten
|
|
11
|
+
* @typedef {import('emscripten').Module.FS} FS
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* An alias to the Python :py:mod:`pyodide` package.
|
|
15
16
|
*
|
|
16
17
|
* You can use this to call functions defined in the Pyodide Python package
|
|
17
|
-
* from
|
|
18
|
+
* from JavaScript.
|
|
18
19
|
*
|
|
19
20
|
* @type {PyProxy}
|
|
20
21
|
*/
|
|
21
|
-
let pyodide_py = {}; // actually defined in
|
|
22
|
+
let pyodide_py = {}; // actually defined in loadPyodide (see pyodide.js)
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
*
|
|
@@ -29,10 +30,10 @@ let pyodide_py = {}; // actually defined in runPythonSimple in loadPyodide (see
|
|
|
29
30
|
*
|
|
30
31
|
* @type {PyProxy}
|
|
31
32
|
*/
|
|
32
|
-
let globals = {}; // actually defined in
|
|
33
|
+
let globals = {}; // actually defined in loadPyodide (see pyodide.js)
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* A
|
|
36
|
+
* A JavaScript error caused by a Python exception.
|
|
36
37
|
*
|
|
37
38
|
* In order to reduce the risk of large memory leaks, the ``PythonError``
|
|
38
39
|
* contains no reference to the Python exception that caused it. You can find
|
|
@@ -74,19 +75,19 @@ export class PythonError {
|
|
|
74
75
|
*
|
|
75
76
|
* @type {string}
|
|
76
77
|
*/
|
|
77
|
-
export let version = ""; // actually defined in
|
|
78
|
+
export let version = ""; // actually defined in loadPyodide (see pyodide.js)
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
|
-
* Runs a string of Python code from
|
|
81
|
+
* Runs a string of Python code from JavaScript.
|
|
81
82
|
*
|
|
82
83
|
* The last part of the string may be an expression, in which case, its value
|
|
83
84
|
* is returned.
|
|
84
85
|
*
|
|
85
86
|
* @param {string} code Python code to evaluate
|
|
86
|
-
* @param {PyProxy} globals An optional Python dictionary to use as the globals.
|
|
87
|
+
* @param {PyProxy=} globals An optional Python dictionary to use as the globals.
|
|
87
88
|
* Defaults to :any:`pyodide.globals`. Uses the Python API
|
|
88
89
|
* :any:`pyodide.eval_code` to evaluate the code.
|
|
89
|
-
* @returns {Py2JsResult} The result of the Python code translated to
|
|
90
|
+
* @returns {Py2JsResult} The result of the Python code translated to JavaScript. See the
|
|
90
91
|
* documentation for :any:`pyodide.eval_code` for more info.
|
|
91
92
|
*/
|
|
92
93
|
export function runPython(code, globals = Module.globals) {
|
|
@@ -150,22 +151,6 @@ export async function loadPackagesFromImports(
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
/**
|
|
154
|
-
* Access a Python object in the global namespace from Javascript.
|
|
155
|
-
*
|
|
156
|
-
* @deprecated This function will be removed in version 0.18.0. Use
|
|
157
|
-
* :any:`pyodide.globals.get('key') <pyodide.globals>` instead.
|
|
158
|
-
*
|
|
159
|
-
* @param {string} name Python variable name
|
|
160
|
-
* @returns {Py2JsResult} The Python object translated to Javascript.
|
|
161
|
-
*/
|
|
162
|
-
export function pyimport(name) {
|
|
163
|
-
console.warn(
|
|
164
|
-
"Access to the Python global namespace via pyodide.pyimport is deprecated and " +
|
|
165
|
-
"will be removed in version 0.18.0. Use pyodide.globals.get('key') instead."
|
|
166
|
-
);
|
|
167
|
-
return Module.globals.get(name);
|
|
168
|
-
}
|
|
169
154
|
/**
|
|
170
155
|
* Runs Python code using `PyCF_ALLOW_TOP_LEVEL_AWAIT
|
|
171
156
|
* <https://docs.python.org/3/library/ast.html?highlight=pycf_allow_top_level_await#ast.PyCF_ALLOW_TOP_LEVEL_AWAIT>`_.
|
|
@@ -185,36 +170,33 @@ export function pyimport(name) {
|
|
|
185
170
|
* from js import fetch
|
|
186
171
|
* response = await fetch("./packages.json")
|
|
187
172
|
* packages = await response.json()
|
|
188
|
-
* # If final statement is an expression, its value is returned to
|
|
173
|
+
* # If final statement is an expression, its value is returned to JavaScript
|
|
189
174
|
* len(packages.packages.object_keys())
|
|
190
175
|
* `);
|
|
191
176
|
* console.log(result); // 79
|
|
192
177
|
*
|
|
193
178
|
* @param {string} code Python code to evaluate
|
|
194
|
-
* @
|
|
179
|
+
* @param {PyProxy=} globals An optional Python dictionary to use as the globals.
|
|
180
|
+
* Defaults to :any:`pyodide.globals`. Uses the Python API
|
|
181
|
+
* :any:`pyodide.eval_code_async` to evaluate the code.
|
|
182
|
+
* @returns {Py2JsResult} The result of the Python code translated to JavaScript.
|
|
195
183
|
* @async
|
|
196
184
|
*/
|
|
197
|
-
export async function runPythonAsync(code) {
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
let result = await coroutine;
|
|
201
|
-
return result;
|
|
202
|
-
} finally {
|
|
203
|
-
coroutine.destroy();
|
|
204
|
-
}
|
|
185
|
+
export async function runPythonAsync(code, globals = Module.globals) {
|
|
186
|
+
return await Module.pyodide_py.eval_code_async(code, globals);
|
|
205
187
|
}
|
|
206
188
|
Module.runPythonAsync = runPythonAsync;
|
|
207
189
|
|
|
208
190
|
/**
|
|
209
|
-
* Registers the
|
|
191
|
+
* Registers the JavaScript object ``module`` as a JavaScript module named
|
|
210
192
|
* ``name``. This module can then be imported from Python using the standard
|
|
211
193
|
* Python import system. If another module by the same name has already been
|
|
212
194
|
* imported, this won't have much effect unless you also delete the imported
|
|
213
195
|
* module from ``sys.modules``. This calls the ``pyodide_py`` API
|
|
214
196
|
* :func:`pyodide.register_js_module`.
|
|
215
197
|
*
|
|
216
|
-
* @param {string} name Name of the
|
|
217
|
-
* @param {object} module
|
|
198
|
+
* @param {string} name Name of the JavaScript module to add
|
|
199
|
+
* @param {object} module JavaScript object backing the module
|
|
218
200
|
*/
|
|
219
201
|
export function registerJsModule(name, module) {
|
|
220
202
|
Module.pyodide_py.register_js_module(name, module);
|
|
@@ -229,24 +211,24 @@ export function registerComlink(Comlink) {
|
|
|
229
211
|
}
|
|
230
212
|
|
|
231
213
|
/**
|
|
232
|
-
* Unregisters a
|
|
214
|
+
* Unregisters a JavaScript module with given name that has been previously
|
|
233
215
|
* registered with :js:func:`pyodide.registerJsModule` or
|
|
234
|
-
* :func:`pyodide.register_js_module`. If a
|
|
216
|
+
* :func:`pyodide.register_js_module`. If a JavaScript module with that name
|
|
235
217
|
* does not already exist, will throw an error. Note that if the module has
|
|
236
218
|
* already been imported, this won't have much effect unless you also delete
|
|
237
219
|
* the imported module from ``sys.modules``. This calls the ``pyodide_py`` API
|
|
238
220
|
* :func:`pyodide.unregister_js_module`.
|
|
239
221
|
*
|
|
240
|
-
* @param {string} name Name of the
|
|
222
|
+
* @param {string} name Name of the JavaScript module to remove
|
|
241
223
|
*/
|
|
242
224
|
export function unregisterJsModule(name) {
|
|
243
225
|
Module.pyodide_py.unregister_js_module(name);
|
|
244
226
|
}
|
|
245
227
|
|
|
246
228
|
/**
|
|
247
|
-
* Convert the
|
|
229
|
+
* Convert the JavaScript object to a Python object as best as possible.
|
|
248
230
|
*
|
|
249
|
-
* This is similar to :any:`JsProxy.to_py` but for use from
|
|
231
|
+
* This is similar to :any:`JsProxy.to_py` but for use from JavaScript. If the
|
|
250
232
|
* object is immutable or a :any:`PyProxy`, it will be returned unchanged. If
|
|
251
233
|
* the object cannot be converted into Python, it will be returned unchanged.
|
|
252
234
|
*
|
|
@@ -254,7 +236,7 @@ export function unregisterJsModule(name) {
|
|
|
254
236
|
*
|
|
255
237
|
* @param {*} obj
|
|
256
238
|
* @param {object} options
|
|
257
|
-
* @param {number} options.depth Optional argument to limit the depth of the
|
|
239
|
+
* @param {number=} options.depth Optional argument to limit the depth of the
|
|
258
240
|
* conversion.
|
|
259
241
|
* @returns {PyProxy} The object converted to Python.
|
|
260
242
|
*/
|
|
@@ -277,9 +259,13 @@ export function toPy(obj, { depth = -1 } = {}) {
|
|
|
277
259
|
let result = 0;
|
|
278
260
|
try {
|
|
279
261
|
obj_id = Module.hiwire.new_value(obj);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
262
|
+
try {
|
|
263
|
+
py_result = Module.js2python_convert(obj_id, new Map(), depth);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
if (e instanceof Module._PropagatePythonError) {
|
|
266
|
+
Module._pythonexc2js();
|
|
267
|
+
}
|
|
268
|
+
throw e;
|
|
283
269
|
}
|
|
284
270
|
if (Module._JsProxy_Check(py_result)) {
|
|
285
271
|
// Oops, just created a JsProxy. Return the original object.
|
|
@@ -297,6 +283,56 @@ export function toPy(obj, { depth = -1 } = {}) {
|
|
|
297
283
|
return Module.hiwire.pop_value(result);
|
|
298
284
|
}
|
|
299
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Imports a module and returns it.
|
|
288
|
+
*
|
|
289
|
+
* .. admonition:: Warning
|
|
290
|
+
* :class: warning
|
|
291
|
+
*
|
|
292
|
+
* This function has a completely different behavior than the old removed pyimport function!
|
|
293
|
+
*
|
|
294
|
+
* ``pyimport`` is roughly equivalent to:
|
|
295
|
+
*
|
|
296
|
+
* .. code-block:: js
|
|
297
|
+
*
|
|
298
|
+
* pyodide.runPython(`import ${pkgname}; ${pkgname}`);
|
|
299
|
+
*
|
|
300
|
+
* except that the global namespace will not change.
|
|
301
|
+
*
|
|
302
|
+
* Example:
|
|
303
|
+
*
|
|
304
|
+
* .. code-block:: js
|
|
305
|
+
*
|
|
306
|
+
* let sysmodule = pyodide.pyimport("sys");
|
|
307
|
+
* let recursionLimit = sys.getrecursionlimit();
|
|
308
|
+
*
|
|
309
|
+
* @param {string} mod_name The name of the module to import
|
|
310
|
+
* @returns A PyProxy for the imported module
|
|
311
|
+
*/
|
|
312
|
+
export function pyimport(mod_name) {
|
|
313
|
+
return Module.importlib.import_module(mod_name);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Unpack an archive into a target directory.
|
|
318
|
+
*
|
|
319
|
+
* @param {ArrayBuffer} buffer The archive as an ArrayBuffer (it's also fine to pass a TypedArray).
|
|
320
|
+
* @param {string} format The format of the archive. Should be one of the formats recognized by `shutil.unpack_archive`.
|
|
321
|
+
* By default the options are 'bztar', 'gztar', 'tar', 'zip', and 'wheel'. Several synonyms are accepted for each format, e.g.,
|
|
322
|
+
* for 'gztar' any of '.gztar', '.tar.gz', '.tgz', 'tar.gz' or 'tgz' are considered to be synonyms.
|
|
323
|
+
*
|
|
324
|
+
* @param {string=} extract_dir The directory to unpack the archive into. Defaults to the working directory.
|
|
325
|
+
*/
|
|
326
|
+
export function unpackArchive(buffer, format, extract_dir) {
|
|
327
|
+
if (!Module._util_module) {
|
|
328
|
+
Module._util_module = pyimport("pyodide._util");
|
|
329
|
+
}
|
|
330
|
+
Module._util_module.unpack_buffer_archive.callKwargs(buffer, {
|
|
331
|
+
format,
|
|
332
|
+
extract_dir,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
300
336
|
/**
|
|
301
337
|
* @private
|
|
302
338
|
*/
|
|
@@ -308,11 +344,34 @@ Module.saveState = () => Module.pyodide_py._state.save_state();
|
|
|
308
344
|
Module.restoreState = (state) => Module.pyodide_py._state.restore_state(state);
|
|
309
345
|
|
|
310
346
|
/**
|
|
347
|
+
* Sets the interrupt buffer to be `interrupt_buffer`. This is only useful when
|
|
348
|
+
* Pyodide is used in a webworker. The buffer should be a `SharedArrayBuffer`
|
|
349
|
+
* shared with the main browser thread (or another worker). To request an
|
|
350
|
+
* interrupt, a `2` should be written into `interrupt_buffer` (2 is the posix
|
|
351
|
+
* constant for SIGINT).
|
|
352
|
+
*
|
|
311
353
|
* @param {TypedArray} interrupt_buffer
|
|
312
354
|
*/
|
|
313
|
-
function setInterruptBuffer(interrupt_buffer) {
|
|
314
|
-
|
|
315
|
-
|
|
355
|
+
export function setInterruptBuffer(interrupt_buffer) {
|
|
356
|
+
Module.interrupt_buffer = interrupt_buffer;
|
|
357
|
+
Module._set_pyodide_callback(!!interrupt_buffer);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Throws a KeyboardInterrupt error if a KeyboardInterrupt has been requested
|
|
362
|
+
* via the interrupt buffer.
|
|
363
|
+
*
|
|
364
|
+
* This can be used to enable keyboard interrupts during execution of JavaScript
|
|
365
|
+
* code, just as `PyErr_CheckSignals` is used to enable keyboard interrupts
|
|
366
|
+
* during execution of C code.
|
|
367
|
+
*/
|
|
368
|
+
export function checkInterrupt() {
|
|
369
|
+
if (Module.interrupt_buffer[0] === 2) {
|
|
370
|
+
Module.interrupt_buffer[0] = 0;
|
|
371
|
+
Module._PyErr_SetInterrupt();
|
|
372
|
+
Module.runPython("");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
316
375
|
|
|
317
376
|
export function makePublicAPI() {
|
|
318
377
|
/**
|
|
@@ -325,15 +384,14 @@ export function makePublicAPI() {
|
|
|
325
384
|
* which can be used to extend the in-memory filesystem with features like `persistence
|
|
326
385
|
* <https://emscripten.org/docs/api_reference/Filesystem-API.html#persistent-data>`_.
|
|
327
386
|
*
|
|
328
|
-
* While all
|
|
387
|
+
* While all the file systems implementations are enabled, only the default
|
|
329
388
|
* ``MEMFS`` is guaranteed to work in all runtime settings. The implementations
|
|
330
389
|
* are available as members of ``FS.filesystems``:
|
|
331
390
|
* ``IDBFS``, ``NODEFS``, ``PROXYFS``, ``WORKERFS``.
|
|
332
391
|
*
|
|
333
|
-
* @type {FS}
|
|
392
|
+
* @type {FS}
|
|
334
393
|
*/
|
|
335
394
|
const FS = Module.FS;
|
|
336
|
-
|
|
337
395
|
let namespace = {
|
|
338
396
|
globals,
|
|
339
397
|
FS,
|
|
@@ -343,13 +401,15 @@ export function makePublicAPI() {
|
|
|
343
401
|
loadPackagesFromImports,
|
|
344
402
|
loadedPackages,
|
|
345
403
|
isPyProxy,
|
|
346
|
-
pyimport,
|
|
347
404
|
runPython,
|
|
348
405
|
runPythonAsync,
|
|
349
406
|
registerJsModule,
|
|
350
407
|
unregisterJsModule,
|
|
351
408
|
setInterruptBuffer,
|
|
409
|
+
checkInterrupt,
|
|
352
410
|
toPy,
|
|
411
|
+
pyimport,
|
|
412
|
+
unpackArchive,
|
|
353
413
|
registerComlink,
|
|
354
414
|
PythonError,
|
|
355
415
|
PyBuffer,
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import ErrorStackParser from "error-stack-parser";
|
|
2
|
+
import { Module, API, Hiwire, Tests } from "./module.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dump the Python traceback to the browser console.
|
|
6
|
+
*
|
|
7
|
+
* @private
|
|
8
|
+
*/
|
|
9
|
+
API.dump_traceback = function () {
|
|
10
|
+
const fd_stdout = 1;
|
|
11
|
+
Module.__Py_DumpTraceback(fd_stdout, Module._PyGILState_GetThisThreadState());
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let fatal_error_occurred = false;
|
|
15
|
+
/**
|
|
16
|
+
* Signal a fatal error.
|
|
17
|
+
*
|
|
18
|
+
* Dumps the Python traceback, shows a JavaScript traceback, and prints a clear
|
|
19
|
+
* message indicating a fatal error. It then dummies out the public API so that
|
|
20
|
+
* further attempts to use Pyodide will clearly indicate that Pyodide has failed
|
|
21
|
+
* and can no longer be used. pyodide._module is left accessible, and it is
|
|
22
|
+
* possible to continue using Pyodide for debugging purposes if desired.
|
|
23
|
+
*
|
|
24
|
+
* @argument e {Error} The cause of the fatal error.
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
API.fatal_error = function (e: any) {
|
|
28
|
+
if (e.pyodide_fatal_error) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (fatal_error_occurred) {
|
|
32
|
+
console.error("Recursive call to fatal_error. Inner error was:");
|
|
33
|
+
console.error(e);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (typeof e === "number") {
|
|
37
|
+
// A C++ exception. Have to do some conversion work.
|
|
38
|
+
e = convertCppException(e);
|
|
39
|
+
} else if (typeof e === "string") {
|
|
40
|
+
e = new Error(e);
|
|
41
|
+
} else if (typeof e !== "object") {
|
|
42
|
+
e = new Error(
|
|
43
|
+
`An object of type ${typeof e} was thrown as an error. toString returns ${e.toString()}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
// Mark e so we know not to handle it later in EM_JS wrappers
|
|
47
|
+
e.pyodide_fatal_error = true;
|
|
48
|
+
fatal_error_occurred = true;
|
|
49
|
+
console.error(
|
|
50
|
+
"Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers."
|
|
51
|
+
);
|
|
52
|
+
console.error("The cause of the fatal error was:");
|
|
53
|
+
if (API.inTestHoist) {
|
|
54
|
+
// Test hoist won't print the error object in a useful way so convert it to
|
|
55
|
+
// string.
|
|
56
|
+
console.error(e.toString());
|
|
57
|
+
console.error(e.stack);
|
|
58
|
+
} else {
|
|
59
|
+
console.error(e);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
API.dump_traceback();
|
|
63
|
+
for (let key of Object.keys(API.public_api)) {
|
|
64
|
+
if (key.startsWith("_") || key === "version") {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
Object.defineProperty(API.public_api, key, {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
get: () => {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"Pyodide already fatally failed and can no longer be used."
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (API.on_fatal) {
|
|
78
|
+
API.on_fatal(e);
|
|
79
|
+
}
|
|
80
|
+
} catch (err2) {
|
|
81
|
+
console.error("Another error occurred while handling the fatal error:");
|
|
82
|
+
console.error(err2);
|
|
83
|
+
}
|
|
84
|
+
throw e;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
class CppException extends Error {
|
|
88
|
+
ty: string;
|
|
89
|
+
constructor(ty: string, msg: string) {
|
|
90
|
+
super(msg);
|
|
91
|
+
this.ty = ty;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
Object.defineProperty(CppException.prototype, "name", {
|
|
95
|
+
get() {
|
|
96
|
+
return `${this.constructor.name} ${this.ty}`;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
* Return the type name, whether the pointer inherits from exception, and the
|
|
103
|
+
* vtable pointer for the type.
|
|
104
|
+
*
|
|
105
|
+
* This code is based on imitating:
|
|
106
|
+
* 1. the implementation of __cxa_find_matching_catch
|
|
107
|
+
* 2. the disassembly from:
|
|
108
|
+
* ```C++
|
|
109
|
+
* try {
|
|
110
|
+
* ...
|
|
111
|
+
* } catch(exception e){
|
|
112
|
+
* ...
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* @param ptr
|
|
116
|
+
* @returns
|
|
117
|
+
* exc_type_name : the type name of the exception, as would be reported by
|
|
118
|
+
* `typeid(type).name()` but also demangled.
|
|
119
|
+
*
|
|
120
|
+
* is_exception_subclass : true if the object is a subclass of exception. In
|
|
121
|
+
* this case we will use `exc.what()` to get an error message.
|
|
122
|
+
*
|
|
123
|
+
* adjusted_ptr : The adjusted vtable pointer for the exception to use to invoke
|
|
124
|
+
* exc.what().
|
|
125
|
+
*
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
function cppExceptionInfo(ptr: number): [string, boolean, number] {
|
|
129
|
+
const base_exception_type = Module._exc_type();
|
|
130
|
+
const ei = new Module.ExceptionInfo(ptr);
|
|
131
|
+
const caught_exception_type = ei.get_type();
|
|
132
|
+
const stackTop = Module.stackSave();
|
|
133
|
+
const exceptionThrowBuf = Module.stackAlloc(4);
|
|
134
|
+
Module.HEAP32[exceptionThrowBuf / 4] = ptr;
|
|
135
|
+
const exc_type_name = Module.demangle(
|
|
136
|
+
Module.UTF8ToString(Module._exc_typename(caught_exception_type))
|
|
137
|
+
);
|
|
138
|
+
const is_exception_subclass = !!Module.___cxa_can_catch(
|
|
139
|
+
base_exception_type,
|
|
140
|
+
caught_exception_type,
|
|
141
|
+
exceptionThrowBuf
|
|
142
|
+
);
|
|
143
|
+
const adjusted_ptr = Module.HEAP32[exceptionThrowBuf / 4];
|
|
144
|
+
Module.stackRestore(stackTop);
|
|
145
|
+
return [exc_type_name, is_exception_subclass, adjusted_ptr];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function convertCppException(ptr: number): CppException {
|
|
149
|
+
const [exc_type_name, is_exception_subclass, adjusted_ptr] =
|
|
150
|
+
cppExceptionInfo(ptr);
|
|
151
|
+
let msg;
|
|
152
|
+
if (is_exception_subclass) {
|
|
153
|
+
// If the ptr inherits from exception, we can use exception.what() to
|
|
154
|
+
// generate a message
|
|
155
|
+
const msgPtr = Module._exc_what(adjusted_ptr);
|
|
156
|
+
msg = Module.UTF8ToString(msgPtr);
|
|
157
|
+
} else {
|
|
158
|
+
msg = `The exception is an object of type ${exc_type_name} at address ${ptr} which does not inherit from std::exception`;
|
|
159
|
+
}
|
|
160
|
+
return new CppException(exc_type_name, msg);
|
|
161
|
+
}
|
|
162
|
+
// Expose for testing
|
|
163
|
+
Tests.convertCppException = convertCppException;
|
|
164
|
+
|
|
165
|
+
function isPyodideFrame(frame: ErrorStackParser.StackFrame): boolean {
|
|
166
|
+
const fileName = frame.fileName || "";
|
|
167
|
+
if (fileName.includes("pyodide.asm")) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (fileName.includes("wasm-function")) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (!fileName.includes("pyodide.js")) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
let funcName = frame.functionName || "";
|
|
177
|
+
if (funcName.startsWith("Object.")) {
|
|
178
|
+
funcName = funcName.slice("Object.".length);
|
|
179
|
+
}
|
|
180
|
+
if (funcName in API.public_api && funcName !== "PythonError") {
|
|
181
|
+
frame.functionName = funcName;
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isErrorStart(frame: ErrorStackParser.StackFrame): boolean {
|
|
188
|
+
if (!isPyodideFrame(frame)) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const funcName = frame.functionName;
|
|
192
|
+
return funcName === "PythonError" || funcName === "new_error";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Module.handle_js_error = function (e: any) {
|
|
196
|
+
if (e.pyodide_fatal_error) {
|
|
197
|
+
throw e;
|
|
198
|
+
}
|
|
199
|
+
if (e instanceof Module._PropagatePythonError) {
|
|
200
|
+
// Python error indicator is already set in this case. If this branch is
|
|
201
|
+
// not taken, Python error indicator should be unset, and we have to set
|
|
202
|
+
// it. In this case we don't want to tamper with the traceback.
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let restored_error = false;
|
|
206
|
+
if (e instanceof API.PythonError) {
|
|
207
|
+
// Try to restore the original Python exception.
|
|
208
|
+
restored_error = Module._restore_sys_last_exception(e.__error_address);
|
|
209
|
+
}
|
|
210
|
+
if (!restored_error) {
|
|
211
|
+
// Wrap the JavaScript error
|
|
212
|
+
let eidx = Hiwire.new_value(e);
|
|
213
|
+
let err = Module._JsProxy_create(eidx);
|
|
214
|
+
Module._set_error(err);
|
|
215
|
+
Module._Py_DecRef(err);
|
|
216
|
+
Hiwire.decref(eidx);
|
|
217
|
+
}
|
|
218
|
+
let stack = ErrorStackParser.parse(e);
|
|
219
|
+
if (isErrorStart(stack[0])) {
|
|
220
|
+
while (isPyodideFrame(stack[0])) {
|
|
221
|
+
stack.shift();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Add the Javascript stack frames to the Python traceback
|
|
225
|
+
for (const frame of stack) {
|
|
226
|
+
if (isPyodideFrame(frame)) {
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
const funcnameAddr = Module.stringToNewUTF8(frame.functionName || "???");
|
|
230
|
+
const fileNameAddr = Module.stringToNewUTF8(frame.fileName || "???.js");
|
|
231
|
+
Module.__PyTraceback_Add(funcnameAddr, fileNameAddr, frame.lineNumber);
|
|
232
|
+
Module._free(funcnameAddr);
|
|
233
|
+
Module._free(fileNameAddr);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A JavaScript error caused by a Python exception.
|
|
239
|
+
*
|
|
240
|
+
* In order to reduce the risk of large memory leaks, the ``PythonError``
|
|
241
|
+
* contains no reference to the Python exception that caused it. You can find
|
|
242
|
+
* the actual Python exception that caused this error as `sys.last_value
|
|
243
|
+
* <https://docs.python.org/3/library/sys.html#sys.last_value>`_.
|
|
244
|
+
*
|
|
245
|
+
* See :ref:`type-translations-errors` for more information.
|
|
246
|
+
*
|
|
247
|
+
* .. admonition:: Avoid Stack Frames
|
|
248
|
+
* :class: warning
|
|
249
|
+
*
|
|
250
|
+
* If you make a :any:`PyProxy` of ``sys.last_value``, you should be
|
|
251
|
+
* especially careful to :any:`destroy() <PyProxy.destroy>` it when you are
|
|
252
|
+
* done. You may leak a large amount of memory including the local
|
|
253
|
+
* variables of all the stack frames in the traceback if you don't. The
|
|
254
|
+
* easiest way is to only handle the exception in Python.
|
|
255
|
+
*/
|
|
256
|
+
export class PythonError extends Error {
|
|
257
|
+
/** The address of the error we are wrapping. We may later compare this
|
|
258
|
+
* against sys.last_value.
|
|
259
|
+
* WARNING: we don't own a reference to this pointer, dereferencing it
|
|
260
|
+
* may be a use-after-free error!
|
|
261
|
+
* @private
|
|
262
|
+
*/
|
|
263
|
+
__error_address: number;
|
|
264
|
+
|
|
265
|
+
constructor(message: string, error_address: number) {
|
|
266
|
+
const oldLimit = Error.stackTraceLimit;
|
|
267
|
+
Error.stackTraceLimit = Infinity;
|
|
268
|
+
super(message);
|
|
269
|
+
Error.stackTraceLimit = oldLimit;
|
|
270
|
+
this.__error_address = error_address;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
Object.defineProperty(PythonError.prototype, "name", {
|
|
274
|
+
value: PythonError.name,
|
|
275
|
+
});
|
|
276
|
+
API.PythonError = PythonError;
|
|
277
|
+
// A special marker. If we call a CPython API from an EM_JS function and the
|
|
278
|
+
// CPython API sets an error, we might want to return an error status back to
|
|
279
|
+
// C keeping the current Python error flag. This signals to the EM_JS wrappers
|
|
280
|
+
// that the Python error flag is set and to leave it alone and return the
|
|
281
|
+
// appropriate error value (either NULL or -1).
|
|
282
|
+
class _PropagatePythonError extends Error {
|
|
283
|
+
constructor() {
|
|
284
|
+
API.fail_test = true;
|
|
285
|
+
super(
|
|
286
|
+
"If you are seeing this message, an internal Pyodide error has " +
|
|
287
|
+
"occurred. Please report it to the Pyodide maintainers."
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
Object.defineProperty(_PropagatePythonError.prototype, "name", {
|
|
292
|
+
value: _PropagatePythonError.name,
|
|
293
|
+
});
|
|
294
|
+
Module._PropagatePythonError = _PropagatePythonError;
|
package/index.test-d.ts
CHANGED
|
@@ -27,8 +27,8 @@ async function main() {
|
|
|
27
27
|
indexURL: "blah",
|
|
28
28
|
fullStdLib: true,
|
|
29
29
|
stdin: () => "a string",
|
|
30
|
-
stdout: (x) => {},
|
|
31
|
-
stderr: (err) => {},
|
|
30
|
+
stdout: (x: string) => {},
|
|
31
|
+
stderr: (err: string) => {},
|
|
32
32
|
})
|
|
33
33
|
);
|
|
34
34
|
|
|
@@ -49,29 +49,32 @@ async function main() {
|
|
|
49
49
|
|
|
50
50
|
expectType<Promise<void>>(pyodide.loadPackagesFromImports("import some_pkg"));
|
|
51
51
|
expectType<Promise<void>>(
|
|
52
|
-
pyodide.loadPackagesFromImports("import some_pkg", (x) =>
|
|
52
|
+
pyodide.loadPackagesFromImports("import some_pkg", (x: any) =>
|
|
53
|
+
console.log(x)
|
|
54
|
+
)
|
|
53
55
|
);
|
|
54
56
|
expectType<Promise<void>>(
|
|
55
57
|
pyodide.loadPackagesFromImports(
|
|
56
58
|
"import some_pkg",
|
|
57
|
-
(x) => console.log(x),
|
|
58
|
-
(x) => console.warn(x)
|
|
59
|
+
(x: any) => console.log(x),
|
|
60
|
+
(x: any) => console.warn(x)
|
|
59
61
|
)
|
|
60
62
|
);
|
|
61
63
|
|
|
62
64
|
expectType<Promise<void>>(pyodide.loadPackage("blah"));
|
|
63
65
|
expectType<Promise<void>>(pyodide.loadPackage(["blah", "blah2"]));
|
|
64
|
-
expectType<Promise<void>>(
|
|
66
|
+
expectType<Promise<void>>(
|
|
67
|
+
pyodide.loadPackage("blah", (x: any) => console.log(x))
|
|
68
|
+
);
|
|
65
69
|
expectType<Promise<void>>(
|
|
66
70
|
pyodide.loadPackage(
|
|
67
71
|
["blah", "blah2"],
|
|
68
|
-
(x) => console.log(x),
|
|
69
|
-
(x) => console.warn(x)
|
|
72
|
+
(x: any) => console.log(x),
|
|
73
|
+
(x: any) => console.warn(x)
|
|
70
74
|
)
|
|
71
75
|
);
|
|
72
76
|
expectType<Promise<void>>(pyodide.loadPackage(px));
|
|
73
77
|
|
|
74
|
-
expectType<Py2JsResult>(pyodide.pyimport("blah"));
|
|
75
78
|
expectType<PyProxy>(pyodide.pyodide_py);
|
|
76
79
|
expectType<void>(pyodide.registerJsModule("blah", { a: 7 }));
|
|
77
80
|
expectType<void>(pyodide.unregisterJsModule("blah"));
|