From f2d6137673e0040e69b05b69a8fd0a0f8c98bd7f Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Wed, 24 Mar 2021 12:05:00 +0100 Subject: [PATCH] Deprecate pyodide.pyimport (#1367) --- docs/project/changelog.md | 2 ++ docs/usage/quickstart.md | 7 +++--- docs/usage/type-conversions.md | 26 +++++++++---------- packages/numpy/test_numpy.py | 20 ++++++++------- src/pyodide.js | 42 ++++++++++++++++++------------- src/templates/console.html | 2 +- src/tests/test_pyproxy.py | 26 ++++++++++--------- src/tests/test_python.py | 12 ++++----- src/tests/test_typeconversions.py | 28 +++++++++++++-------- 9 files changed, 92 insertions(+), 73 deletions(-) diff --git a/docs/project/changelog.md b/docs/project/changelog.md index ba7464296..92912bc05 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -51,6 +51,8 @@ substitutions: access, then the wrapper has `get`, `set`, `has`, and `delete` methods which do `obj[key]`, `obj[key] = val`, `key in obj` and `del obj[key]` respectively. [#1175](https://github.com/iodide-project/pyodide/pull/1175) +- {{ API }} The `pyodide.pyimport` function is deprecated in favor of using + `pyodide.globals.get('key')`. [#1367](https://github.com/iodide-project/pyodide/pull/1367) ### Fixed - {{ Fix }} getattr and dir on JsProxy now report consistent results and include all diff --git a/docs/usage/quickstart.md b/docs/usage/quickstart.md index b843c4cfa..90e737726 100644 --- a/docs/usage/quickstart.md +++ b/docs/usage/quickstart.md @@ -125,19 +125,18 @@ Create and save a test `index.html` page with the following contents: ## Accessing Python scope from Javascript You can also access from Javascript all functions and variables defined in -Python by using the {any}`pyodide.pyimport` api or the {any}`pyodide.globals` -object. +Python by using the {any}`pyodide.globals` object. For example, if you run the code `x = numpy.ones([3,3])` in Python, you can access the variable ``x`` from Javascript in your browser's developer console -as either `pyodide.globals.get("x")` or `pyodide.pyimport('x')`. The same goes +as `pyodide.globals.get("x")`. The same goes for functions and imports. See {ref}`type-translations` for more details. You can try it yourself in the browser console: ```js pyodide.runPython(`import numpy`); pyodide.runPython(`x=numpy.ones((3, 4))`); -pyodide.globals.get('x').toJs(); // or pyodide.pyimport('x').toJs(); +pyodide.globals.get('x').toJs(); // >>> [ Float64Array(4), Float64Array(4), Float64Array(4) ] // create the same 3x4 ndarray from js diff --git a/docs/usage/type-conversions.md b/docs/usage/type-conversions.md index afe02ac79..475fb8e29 100644 --- a/docs/usage/type-conversions.md +++ b/docs/usage/type-conversions.md @@ -16,7 +16,7 @@ converted using the explicit conversion methods `JsProxy.to_py` and Python to Javascript translations occur: - when returning the final expression from a {any}`pyodide.runPython` call, -- when using {any}`pyodide.pyimport`, +- when using `pyodide.globals.get('key')`, - when passing arguments to a Javascript function called from Python, - when returning the results of a Python function called from Javascript, - when accessing an attribute of a `PyProxy` @@ -37,7 +37,7 @@ done with it. Unfortunately, we currently provide no convenient way to do this, particularly when calling Javascript functions from Python. ````` -## Round trip conversions +## Round trip conversions Translating an object from Python to Javascript and then back to Python is guaranteed to give an object that is equal to the original object (with the exception of `nan` because `nan != nan`). Furthermore, if the object @@ -195,7 +195,7 @@ object exist in Python either, then the Python garbage collector can eventually collect it. ```javascript -let foo = pyodide.pyimport('foo'); +let foo = pyodide.globals.get('foo'); foo(); foo.destroy(); foo(); // throws Error: Object has already been destroyed @@ -207,7 +207,7 @@ foo(); // throws Error: Object has already been destroyed Every time you access a Python method on a `PyProxy`, it creates a new temporary `PyProxy` of a Python bound method. If you do not capture this temporary and -destroy it, you will leak the Python object. +destroy it, you will leak the Python object. ````` Here's an example: @@ -220,7 +220,7 @@ pyodide.runPython(` import sys print(sys.getrefcount(d)) # prints 2 `); -let d = pyodide.pyimport("d"); +let d = pyodide.globals.get("d"); // Leak three temporary bound "get" methods! let l = [d.get("a", 0), d.get("b", 0), d.get("c", 0)]; d.destroy(); // Try to free dict @@ -232,7 +232,7 @@ pyodide.runPython(` ``` Here is how we can do this without leaking: ```pyodide -let d = pyodide.pyimport("d"); +let d = pyodide.globals.get("d"); let d_get = d.get; // this time avoid the leak let l = [d_get("a", 0), d_get("b", 0), d_get("c", 0)]; d.destroy(); @@ -247,7 +247,7 @@ Another exciting inconsistency is that `d.set` is a __Javascript__ method not a PyProxy of a bound method, so using it has no effect on refcounts or memory reclamation and it cannot be destroyed. ```pyodide -let d = pyodide.pyimport("d"); +let d = pyodide.globals.get("d"); let d_set = d.set; d_set("x", 7); pyodide.runPython(` @@ -282,7 +282,7 @@ would have different semantics in Javascript than in Python, then a The `toJs` method can create many proxies at arbitrary depth. It is your responsibility to manually `destroy` these proxies if you wish to avoid memory -leaks, but we provide no way to manage this. +leaks, but we provide no way to manage this. ````` To ensure that no `PyProxy` is leaked, the following code suffices: @@ -327,7 +327,7 @@ Object.setPrototypeOf(y, Test.prototype); pyodide.runPython(` from js import x, y # x is converted to a dictionary - assert x.to_py() == { "a" : 7, "b" : 2} + assert x.to_py() == { "a" : 7, "b" : 2} # y is not a "Plain Old JavaScript Object", it's an instance of type Test so it's not converted assert y.to_py() == y `); @@ -379,14 +379,14 @@ implementation for Javascript. ## Importing Python objects into Javascript A Python object in the `__main__` global scope can imported into Javascript -using the {any}`pyodide.pyimport` function. Given the name of the Python object -to import, `pyimport` returns the object translated to Javascript. +using the `pyodide.globals.get` method. Given the name of the Python object +to import, it returns the object translated to Javascript. ```js -let sys = pyodide.pyimport('sys'); +let sys = pyodide.globals.get('sys'); ``` -As always, if the result is a `PyProxy` and you care about not leaking the Python +As always, if the result is a `PyProxy` and you care about not leaking the Python object, you must destroy it when you are done. (type-translations_using-js-obj-from-py)= diff --git a/packages/numpy/test_numpy.py b/packages/numpy/test_numpy.py index 527c10fec..6f1e54d1d 100644 --- a/packages/numpy/test_numpy.py +++ b/packages/numpy/test_numpy.py @@ -2,12 +2,14 @@ def test_numpy(selenium): selenium.load_package("numpy") selenium.run("import numpy") selenium.run("x = numpy.ones((32, 64))") - assert selenium.run_js("return pyodide.pyimport('x').toJs().length == 32") + assert selenium.run_js("return pyodide.globals.get('x').toJs().length == 32") for i in range(32): - assert selenium.run_js(f"return pyodide.pyimport('x').toJs()[{i}].length == 64") + assert selenium.run_js( + f"return pyodide.globals.get('x').toJs()[{i}].length == 64" + ) for j in range(64): assert selenium.run_js( - f"return pyodide.pyimport('x').toJs()[{i}][{j}] == 1" + f"return pyodide.globals.get('x').toJs()[{i}][{j}] == 1" ) @@ -51,7 +53,7 @@ def test_python2js_numpy_dtype(selenium_standalone): for k in range(2): assert ( selenium.run_js( - f"return pyodide.pyimport('x').toJs()[{i}][{j}][{k}]" + f"return pyodide.globals.get('x').toJs()[{i}][{j}][{k}]" ) == expected_result[i][j][k] ) @@ -78,7 +80,7 @@ def test_python2js_numpy_dtype(selenium_standalone): ) assert_equal() classname = selenium.run_js( - "return pyodide.pyimport('x').toJs()[0][0].constructor.name" + "return pyodide.globals.get('x').toJs()[0][0].constructor.name" ) if order == "C" and dtype not in ("uint64", "int64"): # Here we expect a TypedArray subclass, such as Uint8Array, but @@ -94,7 +96,7 @@ def test_python2js_numpy_dtype(selenium_standalone): ) assert_equal() classname = selenium.run_js( - "return pyodide.pyimport('x').toJs()[0][0].constructor.name" + "return pyodide.globals.get('x').toJs()[0][0].constructor.name" ) if order == "C" and dtype in ("int8", "uint8"): # Here we expect a TypedArray subclass, such as Uint8Array, but @@ -147,7 +149,7 @@ def test_python2js_numpy_scalar(selenium_standalone): assert ( selenium.run_js( """ - return pyodide.pyimport('x') == 1 + return pyodide.globals.get('x') == 1 """ ) is True @@ -160,7 +162,7 @@ def test_python2js_numpy_scalar(selenium_standalone): assert ( selenium.run_js( """ - return pyodide.pyimport('x') == 1 + return pyodide.globals.get('x') == 1 """ ) is True @@ -176,7 +178,7 @@ def test_runpythonasync_numpy(selenium_standalone): ) for i in range(5): assert selenium_standalone.run_js( - f"return pyodide.pyimport('x').toJs()[{i}] == 0" + f"return pyodide.globals.get('x').toJs()[{i}] == 0" ) diff --git a/src/pyodide.js b/src/pyodide.js index bbfc71f8c..cca65163e 100644 --- a/src/pyodide.js +++ b/src/pyodide.js @@ -435,9 +435,8 @@ globalThis.languagePluginLoader = (async () => { * * An alias to the global Python namespace. * - * An object whose attributes are members of the Python global namespace. This - * is an alternative to :meth:`pyimport`. For example, to access the ``foo`` - * Python object from Javascript use + * An object whose attributes are members of the Python global namespace. + * For example, to access the ``foo`` Python object from Javascript use * ``pyodide.globals.get("foo")`` * * @type {PyProxy} @@ -503,23 +502,23 @@ globalThis.languagePluginLoader = (async () => { // clang-format off /** - * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to load any known - * packages that the code chunk imports. Uses + * Inspect a Python code chunk and use :js:func:`pyodide.loadPackage` to load any known + * packages that the code chunk imports. Uses * :func:`pyodide_py.find_imports ` to inspect the code. - * + * * For example, given the following code as input - * + * * .. code-block:: python - * + * * import numpy as np * x = np.array([1, 2, 3]) - * + * * :js:func:`loadPackagesFromImports` will call ``pyodide.loadPackage(['numpy'])``. * See also :js:func:`runPythonAsync`. * - * @param {*} code - * @param {*} messageCallback - * @param {*} errorCallback + * @param {*} code + * @param {*} messageCallback + * @param {*} errorCallback */ Module.loadPackagesFromImports = async function(code, messageCallback, errorCallback) { let imports = Module.pyodide_py.find_imports(code).toJs(); @@ -543,12 +542,21 @@ globalThis.languagePluginLoader = (async () => { /** * Access a Python object in the global namespace from Javascript. + * + * Note: this function is deprecated and will be removed in version 0.18.0. + * Use pyodide.globals.get('key') instead. + * * @param {string} name Python variable name * @returns If the Python object is an immutable type (string, number, * boolean), it is converted to Javascript and returned. For other types, a * ``PyProxy`` object is returned. */ - Module.pyimport = name => Module.globals.get(name); + Module.pyimport = name => { + console.warn( + "Access to the Python global namespace via pyodide.pyimport is deprecated and " + + "will be removed in version 0.18.0. Use pyodide.globals.get('key') instead."); + return Module.globals.get(name); + }; /** * Runs Python code, possibly asynchronously loading any known packages that @@ -599,8 +607,8 @@ globalThis.languagePluginLoader = (async () => { * @param {string} name Name of js module to add * @param {object} module Javascript object backing the module */ - Module.registerJsModule = function(name, module) { - Module.pyodide_py.register_js_module(name, module); + Module.registerJsModule = function(name, module) { + Module.pyodide_py.register_js_module(name, module); }; /** @@ -614,8 +622,8 @@ globalThis.languagePluginLoader = (async () => { * * @param {string} name Name of js module to remove */ - Module.unregisterJsModule = function(name) { - Module.pyodide_py.unregister_js_module(name); + Module.unregisterJsModule = function(name) { + Module.pyodide_py.unregister_js_module(name); }; // clang-format on diff --git a/src/templates/console.html b/src/templates/console.html index abb5a8408..05797a99d 100644 --- a/src/templates/console.html +++ b/src/templates/console.html @@ -18,7 +18,7 @@