pyodide 0.18.2 → 0.19.0-alpha.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/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 +35 -87
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
|
-
* Run Python code in the simplest way possible. The primary purpose of this
|
|
103
|
-
* method is for bootstrapping. It is also useful for debugging: If the Python
|
|
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
|
-
*
|
|
122
107
|
* @private
|
|
108
|
+
* Just like `runPython` except uses a different globals dict and gets
|
|
109
|
+
* `eval_code` from `_pyodide` so that it can work before `pyodide` is imported.
|
|
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
|
-
* The Javascript/Wasm call stack is too small to handle the default Python call
|
|
141
|
-
* stack limit of 1000 frames. Here, we determine the Javascript call stack
|
|
142
|
-
* depth available, and then divide by 50 (determined heuristically) to set the
|
|
143
|
-
* maximum Python call stack depth.
|
|
144
|
-
*
|
|
145
116
|
* @private
|
|
117
|
+
* A proxy around globals that falls back to checking for a builtin if has or
|
|
118
|
+
* get fails to find a global with the given key. Note that this proxy is
|
|
119
|
+
* transparent to js2python: it won't notice that this wrapper exists at all and
|
|
120
|
+
* will translate this proxy to the globals dictionary.
|
|
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
|
+
* @private
|
|
172
|
+
* This function is called after the emscripten module is finished initializing,
|
|
173
|
+
* so eval_code is newly available.
|
|
174
|
+
* It finishes the bootstrap so that once it is complete, it is possible to use
|
|
175
|
+
* the core `pyodide` apis. (But package loading is not ready quite yet.)
|
|
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
|
-
}
|
package/pyproxy.gen.js
CHANGED
|
@@ -132,11 +132,8 @@
|
|
|
132
132
|
* the callPyObject method, but of course one can also execute arbitrary code
|
|
133
133
|
* via the various __dundermethods__ associated to classes.
|
|
134
134
|
*
|
|
135
|
-
* The only entrypoint into Python that avoids this file is our bootstrap method
|
|
136
|
-
* runPythonSimple which is defined in main.c
|
|
137
|
-
*
|
|
138
135
|
* Any time we call into wasm, the call should be wrapped in a try catch block.
|
|
139
|
-
* This way if a
|
|
136
|
+
* This way if a JavaScript error emerges from the wasm, we can escalate it to a
|
|
140
137
|
* fatal error.
|
|
141
138
|
*
|
|
142
139
|
* This is file is preprocessed with -imacros "pyproxy.c". As a result of this,
|
|
@@ -160,6 +157,7 @@ Module.isPyProxy = isPyProxy;
|
|
|
160
157
|
|
|
161
158
|
if (globalThis.FinalizationRegistry) {
|
|
162
159
|
Module.finalizationRegistry = new FinalizationRegistry(([ptr, cache]) => {
|
|
160
|
+
cache.leaked = (!!1);
|
|
163
161
|
pyproxy_decref_cache(cache);
|
|
164
162
|
try {
|
|
165
163
|
Module._Py_DecRef(ptr);
|
|
@@ -248,7 +246,7 @@ Module.pyproxy_new = function (ptrobj, cache) {
|
|
|
248
246
|
}
|
|
249
247
|
cache.refcnt++;
|
|
250
248
|
Object.defineProperty(target, "$$", {
|
|
251
|
-
value: { ptr: ptrobj, type: "PyProxy",
|
|
249
|
+
value: { ptr: ptrobj, type: "PyProxy", cache },
|
|
252
250
|
});
|
|
253
251
|
Module._Py_IncRef(ptrobj);
|
|
254
252
|
let proxy = new Proxy(target, PyProxyHandlers);
|
|
@@ -318,9 +316,6 @@ Module.getPyProxyClass = function (flags) {
|
|
|
318
316
|
|
|
319
317
|
// Static methods
|
|
320
318
|
Module.PyProxy_getPtr = _getPtr;
|
|
321
|
-
Module.pyproxy_mark_borrowed = function (proxy) {
|
|
322
|
-
proxy.$$.borrowed = (!!1);
|
|
323
|
-
};
|
|
324
319
|
|
|
325
320
|
const pyproxy_cache_destroyed_msg =
|
|
326
321
|
"This borrowed attribute proxy was automatically destroyed in the " +
|
|
@@ -334,18 +329,21 @@ function pyproxy_decref_cache(cache) {
|
|
|
334
329
|
if (cache.refcnt === 0) {
|
|
335
330
|
let cache_map = Module.hiwire.pop_value(cache.cacheId);
|
|
336
331
|
for (let proxy_id of cache_map.values()) {
|
|
337
|
-
Module.
|
|
338
|
-
|
|
339
|
-
pyproxy_cache_destroyed_msg
|
|
340
|
-
|
|
332
|
+
const cache_entry = Module.hiwire.pop_value(proxy_id);
|
|
333
|
+
if (!cache.leaked) {
|
|
334
|
+
Module.pyproxy_destroy(cache_entry, pyproxy_cache_destroyed_msg);
|
|
335
|
+
}
|
|
341
336
|
}
|
|
342
337
|
}
|
|
343
338
|
}
|
|
344
339
|
|
|
345
340
|
Module.pyproxy_destroy = function (proxy, destroyed_msg) {
|
|
341
|
+
if (proxy.$$.ptr === null) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
346
344
|
let ptrobj = _getPtr(proxy);
|
|
347
345
|
Module.finalizationRegistry.unregister(proxy);
|
|
348
|
-
// Maybe the destructor will call
|
|
346
|
+
// Maybe the destructor will call JavaScript code that will somehow try
|
|
349
347
|
// to use this proxy. Mark it deleted before decrementing reference count
|
|
350
348
|
// just in case!
|
|
351
349
|
proxy.$$.ptr = null;
|
|
@@ -360,7 +358,7 @@ Module.pyproxy_destroy = function (proxy, destroyed_msg) {
|
|
|
360
358
|
};
|
|
361
359
|
|
|
362
360
|
// Now a lot of boilerplate to wrap the abstract Object protocol wrappers
|
|
363
|
-
// defined in pyproxy.c in
|
|
361
|
+
// defined in pyproxy.c in JavaScript functions.
|
|
364
362
|
|
|
365
363
|
Module.callPyObjectKwargs = function (ptrobj, ...jsargs) {
|
|
366
364
|
// We don't do any checking for kwargs, checks are in PyProxy.callKwargs
|
|
@@ -462,9 +460,7 @@ class PyProxyClass {
|
|
|
462
460
|
* destroyed".
|
|
463
461
|
*/
|
|
464
462
|
destroy(destroyed_msg) {
|
|
465
|
-
|
|
466
|
-
Module.pyproxy_destroy(this, destroyed_msg);
|
|
467
|
-
}
|
|
463
|
+
Module.pyproxy_destroy(this, destroyed_msg);
|
|
468
464
|
}
|
|
469
465
|
/**
|
|
470
466
|
* Make a new PyProxy pointing to the same Python object.
|
|
@@ -476,7 +472,7 @@ class PyProxyClass {
|
|
|
476
472
|
return Module.pyproxy_new(ptrobj, this.$$.cache);
|
|
477
473
|
}
|
|
478
474
|
/**
|
|
479
|
-
* Converts the ``PyProxy`` into a
|
|
475
|
+
* Converts the ``PyProxy`` into a JavaScript object as best as possible. By
|
|
480
476
|
* default does a deep conversion, if a shallow conversion is desired, you can
|
|
481
477
|
* use ``proxy.toJs({depth : 1})``. See :ref:`Explicit Conversion of PyProxy
|
|
482
478
|
* <type-translations-pyproxy-to-js>` for more info.
|
|
@@ -490,14 +486,14 @@ class PyProxyClass {
|
|
|
490
486
|
* generated structure. The most common use case is to create a new empty
|
|
491
487
|
* list, pass the list as `pyproxies`, and then later iterate over `pyproxies`
|
|
492
488
|
* to destroy all of created proxies.
|
|
493
|
-
* @param {
|
|
489
|
+
* @param {boolean} [options.create_pyproxies] If false, ``toJs`` will throw a
|
|
494
490
|
* ``ConversionError`` rather than producing a ``PyProxy``.
|
|
495
|
-
* @param {
|
|
491
|
+
* @param {boolean} [options.dict_converter] A function to be called on an
|
|
496
492
|
* iterable of pairs ``[key, value]``. Convert this iterable of pairs to the
|
|
497
493
|
* desired output. For instance, ``Object.fromEntries`` would convert the dict
|
|
498
494
|
* to an object, ``Array.from`` converts it to an array of entries, and ``(it) =>
|
|
499
495
|
* new Map(it)`` converts it to a ``Map`` (which is the default behavior).
|
|
500
|
-
* @return {any} The
|
|
496
|
+
* @return {any} The JavaScript object resulting from the conversion.
|
|
501
497
|
*/
|
|
502
498
|
toJs({
|
|
503
499
|
depth = -1,
|
|
@@ -768,8 +764,6 @@ class PyProxyContainsMethods {
|
|
|
768
764
|
}
|
|
769
765
|
}
|
|
770
766
|
|
|
771
|
-
class TempError extends Error {}
|
|
772
|
-
|
|
773
767
|
/**
|
|
774
768
|
* A helper for [Symbol.iterator].
|
|
775
769
|
*
|
|
@@ -788,26 +782,19 @@ class TempError extends Error {}
|
|
|
788
782
|
*/
|
|
789
783
|
function* iter_helper(iterptr, token) {
|
|
790
784
|
try {
|
|
791
|
-
if (iterptr === 0) {
|
|
792
|
-
throw new TempError();
|
|
793
|
-
}
|
|
794
785
|
let item;
|
|
795
786
|
while ((item = Module.__pyproxy_iter_next(iterptr))) {
|
|
796
787
|
yield Module.hiwire.pop_value(item);
|
|
797
788
|
}
|
|
798
|
-
if (Module._PyErr_Occurred()) {
|
|
799
|
-
throw new TempError();
|
|
800
|
-
}
|
|
801
789
|
} catch (e) {
|
|
802
|
-
|
|
803
|
-
Module._pythonexc2js();
|
|
804
|
-
} else {
|
|
805
|
-
Module.fatal_error(e);
|
|
806
|
-
}
|
|
790
|
+
Module.fatal_error(e);
|
|
807
791
|
} finally {
|
|
808
792
|
Module.finalizationRegistry.unregister(token);
|
|
809
793
|
Module._Py_DecRef(iterptr);
|
|
810
794
|
}
|
|
795
|
+
if (Module._PyErr_Occurred()) {
|
|
796
|
+
Module._pythonexc2js();
|
|
797
|
+
}
|
|
811
798
|
}
|
|
812
799
|
|
|
813
800
|
/**
|
|
@@ -840,6 +827,9 @@ class PyProxyIterableMethods {
|
|
|
840
827
|
} catch (e) {
|
|
841
828
|
Module.fatal_error(e);
|
|
842
829
|
}
|
|
830
|
+
if (iterptr === 0) {
|
|
831
|
+
Module._pythonexc2js();
|
|
832
|
+
}
|
|
843
833
|
|
|
844
834
|
let result = iter_helper(iterptr, token);
|
|
845
835
|
Module.finalizationRegistry.register(result, [iterptr, undefined], token);
|
|
@@ -1004,7 +994,7 @@ let PyProxyHandlers = {
|
|
|
1004
994
|
},
|
|
1005
995
|
get(jsobj, jskey) {
|
|
1006
996
|
// Preference order:
|
|
1007
|
-
// 1. stuff from
|
|
997
|
+
// 1. stuff from JavaScript
|
|
1008
998
|
// 2. the result of Python getattr
|
|
1009
999
|
|
|
1010
1000
|
// python_getattr will crash if given a Symbol.
|
|
@@ -1050,7 +1040,7 @@ let PyProxyHandlers = {
|
|
|
1050
1040
|
}
|
|
1051
1041
|
python_delattr(jsobj, jskey);
|
|
1052
1042
|
// Must return "false" if "jskey" is a nonconfigurable own property.
|
|
1053
|
-
// Otherwise
|
|
1043
|
+
// Otherwise JavaScript will throw a TypeError.
|
|
1054
1044
|
return !descr || descr.configurable;
|
|
1055
1045
|
},
|
|
1056
1046
|
ownKeys(jsobj) {
|
|
@@ -1078,7 +1068,7 @@ let PyProxyHandlers = {
|
|
|
1078
1068
|
*/
|
|
1079
1069
|
|
|
1080
1070
|
/**
|
|
1081
|
-
* The Promise /
|
|
1071
|
+
* The Promise / JavaScript awaitable API.
|
|
1082
1072
|
* @private
|
|
1083
1073
|
*/
|
|
1084
1074
|
class PyProxyAwaitableMethods {
|
|
@@ -1089,6 +1079,9 @@ class PyProxyAwaitableMethods {
|
|
|
1089
1079
|
* @private
|
|
1090
1080
|
*/
|
|
1091
1081
|
_ensure_future() {
|
|
1082
|
+
if (this.$$.promise) {
|
|
1083
|
+
return this.$$.promise;
|
|
1084
|
+
}
|
|
1092
1085
|
let ptrobj = _getPtr(this);
|
|
1093
1086
|
let resolveHandle;
|
|
1094
1087
|
let rejectHandle;
|
|
@@ -1114,6 +1107,8 @@ class PyProxyAwaitableMethods {
|
|
|
1114
1107
|
if (errcode === -1) {
|
|
1115
1108
|
Module._pythonexc2js();
|
|
1116
1109
|
}
|
|
1110
|
+
this.$$.promise = promise;
|
|
1111
|
+
this.destroy();
|
|
1117
1112
|
return promise;
|
|
1118
1113
|
}
|
|
1119
1114
|
/**
|
|
@@ -1238,14 +1233,14 @@ let type_to_array_map = new Map([
|
|
|
1238
1233
|
*/
|
|
1239
1234
|
class PyProxyBufferMethods {
|
|
1240
1235
|
/**
|
|
1241
|
-
* Get a view of the buffer data which is usable from
|
|
1236
|
+
* Get a view of the buffer data which is usable from JavaScript. No copy is
|
|
1242
1237
|
* ever performed.
|
|
1243
1238
|
*
|
|
1244
1239
|
* Present only if the proxied Python object supports the `Python Buffer
|
|
1245
1240
|
* Protocol <https://docs.python.org/3/c-api/buffer.html>`_.
|
|
1246
1241
|
*
|
|
1247
1242
|
* We do not support suboffsets, if the buffer requires suboffsets we will
|
|
1248
|
-
* throw an error.
|
|
1243
|
+
* throw an error. JavaScript nd array libraries can't handle suboffsets
|
|
1249
1244
|
* anyways. In this case, you should use the :any:`toJs` api or copy the
|
|
1250
1245
|
* buffer to one that doesn't use suboffets (using e.g.,
|
|
1251
1246
|
* `numpy.ascontiguousarray
|
|
@@ -1391,7 +1386,7 @@ class PyProxyBufferMethods {
|
|
|
1391
1386
|
*/
|
|
1392
1387
|
|
|
1393
1388
|
/**
|
|
1394
|
-
* A class to allow access to a Python data buffers from
|
|
1389
|
+
* A class to allow access to a Python data buffers from JavaScript. These are
|
|
1395
1390
|
* produced by :any:`PyProxy.getBuffer` and cannot be constructed directly.
|
|
1396
1391
|
* When you are done, release it with the :any:`release <PyBuffer.release>`
|
|
1397
1392
|
* method. See
|
|
@@ -1562,50 +1557,3 @@ export class PyBuffer {
|
|
|
1562
1557
|
this.data = null;
|
|
1563
1558
|
}
|
|
1564
1559
|
}
|
|
1565
|
-
|
|
1566
|
-
// A special proxy that we use to wrap pyodide.globals to allow property
|
|
1567
|
-
// access like `pyodide.globals.x`.
|
|
1568
|
-
let globalsPropertyAccessWarned = (!!0);
|
|
1569
|
-
let globalsPropertyAccessWarningMsg =
|
|
1570
|
-
"Access to pyodide.globals via pyodide.globals.key is deprecated and " +
|
|
1571
|
-
"will be removed in version 0.18.0. Use pyodide.globals.get('key'), " +
|
|
1572
|
-
"pyodide.globals.set('key', value), pyodide.globals.delete('key') instead.";
|
|
1573
|
-
let NamespaceProxyHandlers = {
|
|
1574
|
-
has(obj, key) {
|
|
1575
|
-
return Reflect.has(obj, key) || obj.has(key);
|
|
1576
|
-
},
|
|
1577
|
-
get(obj, key) {
|
|
1578
|
-
if (Reflect.has(obj, key)) {
|
|
1579
|
-
return Reflect.get(obj, key);
|
|
1580
|
-
}
|
|
1581
|
-
let result = obj.get(key);
|
|
1582
|
-
if (!globalsPropertyAccessWarned && result !== undefined) {
|
|
1583
|
-
console.warn(globalsPropertyAccessWarningMsg);
|
|
1584
|
-
globalsPropertyAccessWarned = (!!1);
|
|
1585
|
-
}
|
|
1586
|
-
return result;
|
|
1587
|
-
},
|
|
1588
|
-
set(obj, key, value) {
|
|
1589
|
-
if (Reflect.has(obj, key)) {
|
|
1590
|
-
throw new Error(`Cannot set read only field ${key}`);
|
|
1591
|
-
}
|
|
1592
|
-
if (!globalsPropertyAccessWarned) {
|
|
1593
|
-
globalsPropertyAccessWarned = (!!1);
|
|
1594
|
-
console.warn(globalsPropertyAccessWarningMsg);
|
|
1595
|
-
}
|
|
1596
|
-
obj.set(key, value);
|
|
1597
|
-
},
|
|
1598
|
-
ownKeys(obj) {
|
|
1599
|
-
let result = new Set(Reflect.ownKeys(obj));
|
|
1600
|
-
let iter = obj.keys();
|
|
1601
|
-
for (let key of iter) {
|
|
1602
|
-
result.add(key);
|
|
1603
|
-
}
|
|
1604
|
-
iter.destroy();
|
|
1605
|
-
return Array.from(result);
|
|
1606
|
-
},
|
|
1607
|
-
};
|
|
1608
|
-
|
|
1609
|
-
export function wrapNamespace(ns) {
|
|
1610
|
-
return new Proxy(ns, NamespaceProxyHandlers);
|
|
1611
|
-
}
|