Deprecate pyodide.pyimport (#1367)

This commit is contained in:
Roman Yurchak 2021-03-24 12:05:00 +01:00 committed by GitHub
parent e408dede25
commit f2d6137673
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 92 additions and 73 deletions

View File

@ -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

View File

@ -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

View File

@ -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)=

View File

@ -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"
)

View File

@ -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 <pyodide.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

View File

@ -18,7 +18,7 @@
<body>
<script>
languagePluginLoader.then(() => {
let namespace = pyodide.pyimport("dict")();
let namespace = pyodide.globals.get("dict")();
pyodide.runPython(`
import sys
import js

View File

@ -12,7 +12,7 @@ def test_pyproxy(selenium):
f = Foo()
"""
)
selenium.run_js("window.f = pyodide.pyimport('f')")
selenium.run_js("window.f = pyodide.globals.get('f')")
assert selenium.run_js("return f.type") == "Foo"
assert selenium.run_js("return f.get_value(2)") == 128
assert selenium.run_js("return f.bar") == 42
@ -53,9 +53,11 @@ def test_pyproxy(selenium):
]
)
assert selenium.run("hasattr(f, 'baz')")
selenium.run_js("delete pyodide.pyimport('f').baz")
selenium.run_js("delete pyodide.globals.get('f').baz")
assert not selenium.run("hasattr(f, 'baz')")
assert selenium.run_js("return pyodide.pyimport('f').toString()").startswith("<Foo")
assert selenium.run_js("return pyodide.globals.get('f').toString()").startswith(
"<Foo"
)
def test_pyproxy_refcount(selenium):
@ -77,8 +79,8 @@ def test_pyproxy_refcount(selenium):
// the refcount should be 2 because:
//
// 1. pyfunc exists
// 2. pyfunc is referenced from the sys.getrefcount()-test below
// 2. pyfunc is referenced from the sys.getrefcount()-test below
result.push([getRefCount(), 2]);
// the refcount should be 3 because:
@ -86,7 +88,7 @@ def test_pyproxy_refcount(selenium):
// 1. pyfunc exists
// 2. one reference from PyProxy to pyfunc is alive
// 3. pyfunc is referenced from the sys.getrefcount()-test below
pyodide.runPython(`
window.jsfunc(pyfunc) # creates new PyProxy
`);
@ -96,7 +98,7 @@ def test_pyproxy_refcount(selenium):
window.jsfunc(pyfunc) # re-used existing PyProxy
window.jsfunc(pyfunc) # re-used existing PyProxy
`)
// the refcount should be 3 because:
//
// 1. pyfunc exists
@ -124,7 +126,7 @@ def test_pyproxy_destroy(selenium):
with pytest.raises(selenium.JavascriptException, match=msg):
selenium.run_js(
"""
let f = pyodide.pyimport('f');
let f = pyodide.globals.get('f');
console.assert(f.get_value(1) === 64);
f.destroy();
f.get_value();
@ -205,15 +207,15 @@ def test_pyproxy_mixins(selenium):
class Await:
def __await__(self):
return iter([])
class Iter:
def __iter__(self):
return iter([])
class Next:
def __next__(self):
pass
class AwaitIter(Await, Iter): pass
class AwaitNext(Await, Next): pass
@ -303,7 +305,7 @@ def test_pyproxy_mixins2(selenium):
assert(() => get_method.prototype === undefined);
assert(() => !("length" in get_method));
assert(() => !("name" in get_method));
assert(() => pyodide.globals.get.type === "builtin_function_or_method");
assert(() => pyodide.globals.set.type === undefined);

View File

@ -35,18 +35,18 @@ def test_import_js(selenium):
assert "window" in result
def test_pyimport_multiple(selenium):
def test_globals_get_multiple(selenium):
"""See #1151"""
selenium.run("v = 0.123")
selenium.run_js("pyodide.pyimport('v')")
selenium.run_js("pyodide.pyimport('v')")
selenium.run_js("pyodide.globals.get('v')")
selenium.run_js("pyodide.globals.get('v')")
def test_pyimport_same(selenium):
def test_globals_get_same(selenium):
"""See #382"""
selenium.run("def func(): return 42")
assert selenium.run_js(
"return pyodide.pyimport('func') == pyodide.pyimport('func')"
"return pyodide.globals.get('func') == pyodide.globals.get('func')"
)
@ -72,7 +72,7 @@ def test_load_package_after_convert_string(selenium):
See #93.
"""
selenium.run("import sys\n" "x = sys.version")
selenium.run_js("let x = pyodide.pyimport('x');\n" "console.log(x);")
selenium.run_js("let x = pyodide.globals.get('x');\n" "console.log(x);")
selenium.load_package("kiwisolver")
selenium.run("import kiwisolver")

View File

@ -51,7 +51,7 @@ def test_python2js(selenium):
let typename = proxy.type;
let x = proxy.toJs();
proxy.destroy();
return ((typename === "list") && (x instanceof window.Array) &&
return ((typename === "list") && (x instanceof window.Array) &&
(x.length === 3) && (x[0] == 1) && (x[1] == 2) && (x[2] == 3));
"""
)
@ -107,7 +107,7 @@ def test_js2python(selenium):
window.jsfalse = false;
window.jsarray0 = [];
window.jsarray1 = [1, 2, 3];
window.jspython = pyodide.pyimport("open");
window.jspython = pyodide.globals.get("open");
window.jsbytes = new Uint8Array([1, 2, 3]);
window.jsfloats = new Float32Array([1, 2, 3]);
window.jsobject = new XMLHttpRequest();
@ -270,7 +270,7 @@ def test_recursive_list_to_js(selenium_standalone):
x.append(x)
"""
)
selenium_standalone.run_js("x = pyodide.pyimport('x').toJs();")
selenium_standalone.run_js("x = pyodide.globals.get('x').toJs();")
def test_recursive_dict_to_js(selenium_standalone):
@ -280,7 +280,7 @@ def test_recursive_dict_to_js(selenium_standalone):
x[0] = x
"""
)
selenium_standalone.run_js("x = pyodide.pyimport('x').toJs();")
selenium_standalone.run_js("x = pyodide.globals.get('x').toJs();")
def test_list_js2py2js(selenium):
@ -408,7 +408,7 @@ def test_python2js_with_depth(selenium):
assert selenium.run_js(
"""
pyodide.runPython("a = [1, 2, 3]");
let res = pyodide.pyimport("a").toJs();
let res = pyodide.globals.get("a").toJs();
return (Array.isArray(res)) && JSON.stringify(res) === "[1,2,3]";
"""
)
@ -416,7 +416,7 @@ def test_python2js_with_depth(selenium):
assert selenium.run_js(
"""
pyodide.runPython("a = (1, 2, 3)");
let res = pyodide.pyimport("a").toJs();
let res = pyodide.globals.get("a").toJs();
return (Array.isArray(res)) && JSON.stringify(res) === "[1,2,3]";
"""
)
@ -424,7 +424,7 @@ def test_python2js_with_depth(selenium):
assert selenium.run_js(
"""
pyodide.runPython("a = [(1,2), (3,4), [5, 6], { 2 : 3, 4 : 9}]")
let res = pyodide.pyimport("a").toJs();
let res = pyodide.globals.get("a").toJs();
return Array.isArray(res) && \
JSON.stringify(res) === `[[1,2],[3,4],[5,6],{}]` && \
JSON.stringify(Array.from(res[3].entries())) === "[[2,3],[4,9]]";
@ -444,7 +444,7 @@ def test_python2js_with_depth(selenium):
selenium.run_js(
"""
pyodide.runPython("a = [1,[2,[3,[4,[5,[6,[7]]]]]]]")
let a = pyodide.pyimport("a");
let a = pyodide.globals.get("a");
for(let i=0; i < 7; i++){
let x = a.toJs(i);
for(let j=0; j < i; j++){
@ -464,7 +464,7 @@ def test_python2js_with_depth(selenium):
throw new Error(`Assertion failed: ${msg}`);
}
}
let a = pyodide.pyimport("a");
let a = pyodide.globals.get("a");
for(let i=0; i < 7; i++){
let x = a.toJs(i);
for(let j=0; j < i; j++){
@ -484,7 +484,7 @@ def test_python2js_with_depth(selenium):
c = [b, b, b, b, b]
`);
let total_refs = pyodide._module.hiwire.num_keys();
let res = pyodide.pyimport("c").toJs();
let res = pyodide.globals.get("c").toJs();
let new_total_refs = pyodide._module.hiwire.num_keys();
assert(total_refs === new_total_refs);
assert(res[0] === res[1]);
@ -502,7 +502,7 @@ def test_python2js_with_depth(selenium):
a.append(b)
`);
let total_refs = pyodide._module.hiwire.num_keys();
let res = pyodide.pyimport("a").toJs();
let res = pyodide.globals.get("a").toJs();
let new_total_refs = pyodide._module.hiwire.num_keys();
assert(total_refs === new_total_refs);
assert(res[0][0] === "b");
@ -690,3 +690,9 @@ def test_to_py(selenium):
`);
"""
)
def test_pyimport_deprecation(selenium):
selenium.run_js("pyodide.runPython('x = 1')")
assert selenium.run_js("return pyodide.pyimport('x') === 1")
assert "pyodide.pyimport is deprecated and will be removed" in selenium.logs