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/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
- let fd_stdout = 1;
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 Javascript traceback, and prints a clear
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.runPythonSimple = function (code) {
125
- let code_c_string = Module.stringToNewUTF8(code);
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 fixRecursionLimit() {
148
- let depth = 0;
149
- function recurse() {
150
- depth += 1;
151
- recurse();
152
- }
153
- try {
154
- recurse();
155
- } catch (err) {}
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
- let recursionLimit = Math.min(depth / 25, 500);
158
- Module.runPythonSimple(
159
- `import sys; sys.setrecursionlimit(int(${recursionLimit}))`
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 Javascript global scope
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 | (() => string)} config.stdin - Override the standard input callback. Should ask the user for one line of input.
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 | ((text: string) => void)} config.stdout - Override the standard output callback.
233
+ * @param {undefined | function(string)} config.stdout - Override the standard output callback.
180
234
  * Default: undefined
181
- * @param {undefined | ((text: string) => void)} config.stderr - Override the standard error output callback.
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
- if (globalThis.__pyodide_module) {
195
- if (globalThis.languagePluginURL) {
196
- throw new Error(
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 = baseURL;
215
- let packageIndexReady = initializePackageIndex(baseURL);
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 = `${baseURL}pyodide.asm.js`;
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
- fixRecursionLimit();
234
- let pyodide = makePublicAPI();
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.globals = Module.globals;
272
- pyodide.pyodide_py = Module.pyodide_py;
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 Javascript error emerges from the wasm, we can escalate it to 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", borrowed: (!!0), cache },
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.pyproxy_destroy(
338
- Module.hiwire.pop_value(proxy_id),
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 Javascript code that will somehow try
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 Javascript functions.
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
- if (!this.$$.borrowed) {
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 Javascript object as best as possible. By
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 {bool} [options.create_pyproxies] If false, ``toJs`` will throw a
489
+ * @param {boolean} [options.create_pyproxies] If false, ``toJs`` will throw a
494
490
  * ``ConversionError`` rather than producing a ``PyProxy``.
495
- * @param {bool} [options.dict_converter] A function to be called on an
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 Javascript object resulting from the conversion.
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
- if (e instanceof TempError) {
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 Javascript
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 Javascript will throw a TypeError.
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 / javascript awaitable API.
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 Javascript. No copy is
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. Javascript nd array libraries can't handle suboffsets
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 Javascript. These are
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
- }