Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't automatically copy python objects into javascript #1152

Merged
merged 15 commits into from Jan 22, 2021
24 changes: 22 additions & 2 deletions conftest.py
Expand Up @@ -95,10 +95,30 @@ def clean_logs(self):
self.driver.execute_script("window.logs = []")

def run(self, code):
return self.run_js("return pyodide.runPython({!r})".format(code))
return self.run_js(
f"""
let result = pyodide.runPython({code!r});
if(result && result.deepCopyToJavascript){{
let converted_result = result.deepCopyToJavascript();
result.destroy();
return converted_result;
}}
return result;
"""
)

def run_async(self, code):
return self.run_js("return pyodide.runPythonAsync({!r})".format(code))
return self.run_js(
f"""
let result = await pyodide.runPythonAsync({code!r});
if(result && result.deepCopyToJavascript){{
let converted_result = result.deepCopyToJavascript();
result.destroy();
return converted_result;
}}
return result;
"""
)

def run_js(self, code):
if isinstance(code, str) and code.startswith("\n"):
Expand Down
2 changes: 1 addition & 1 deletion packages/matplotlib/src/wasm_backend.py
Expand Up @@ -190,7 +190,7 @@ def ignore(event):
rubberband.addEventListener("keydown", self.onkeydown)
context = rubberband.getContext("2d")
context.strokeStyle = "#000000"
context.setLineDash([2, 2])
# context.setLineDash([2, 2])
canvas_div.appendChild(rubberband)

div.appendChild(canvas_div)
Expand Down
8 changes: 4 additions & 4 deletions packages/matplotlib/test_matplotlib.py
Expand Up @@ -2,15 +2,15 @@ def test_matplotlib(selenium_standalone):
selenium = selenium_standalone
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")
selenium.run("plt.figure()")
selenium.run("plt.plot([1,2,3])")
selenium.run("plt.figure(); pass")
selenium.run("x = plt.plot([1,2,3])")
phorward marked this conversation as resolved.
Show resolved Hide resolved
selenium.run("plt.show()")


def test_svg(selenium):
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")
selenium.run("plt.figure()")
selenium.run("plt.figure(); pass")
selenium.run("x = plt.plot([1,2,3])")
selenium.run("import io")
selenium.run("fd = io.BytesIO()")
Expand All @@ -23,7 +23,7 @@ def test_svg(selenium):
def test_pdf(selenium):
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")
selenium.run("plt.figure()")
selenium.run("plt.figure(); pass")
selenium.run("x = plt.plot([1,2,3])")
selenium.run("import io")
selenium.run("fd = io.BytesIO()")
Expand Down
39 changes: 29 additions & 10 deletions packages/numpy/test_numpy.py
Expand Up @@ -2,11 +2,17 @@ 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').length == 32")
assert selenium.run_js(
"return pyodide.pyimport('x').deepCopyToJavascript().length == 32"
)
for i in range(32):
assert selenium.run_js(f"return pyodide.pyimport('x')[{i}].length == 64")
assert selenium.run_js(
f"return pyodide.pyimport('x').deepCopyToJavascript()[{i}].length == 64"
)
for j in range(64):
assert selenium.run_js(f"return pyodide.pyimport('x')[{i}][{j}] == 1")
assert selenium.run_js(
f"return pyodide.pyimport('x').deepCopyToJavascript()[{i}][{j}] == 1"
)


def test_typed_arrays(selenium):
Expand Down Expand Up @@ -48,7 +54,9 @@ def assert_equal():
for j in range(2):
for k in range(2):
assert (
selenium.run_js(f"return pyodide.pyimport('x')[{i}][{j}][{k}]")
selenium.run_js(
f"return pyodide.pyimport('x').deepCopyToJavascript()[{i}][{j}][{k}]"
)
== expected_result[i][j][k]
)

Expand All @@ -74,7 +82,7 @@ def assert_equal():
)
assert_equal()
classname = selenium.run_js(
"return pyodide.pyimport('x')[0][0].constructor.name"
"return pyodide.pyimport('x').deepCopyToJavascript()[0][0].constructor.name"
)
if order == "C" and dtype not in ("uint64", "int64"):
# Here we expect a TypedArray subclass, such as Uint8Array, but
Expand All @@ -90,7 +98,7 @@ def assert_equal():
)
assert_equal()
classname = selenium.run_js(
"return pyodide.pyimport('x')[0][0].constructor.name"
"return pyodide.pyimport('x').deepCopyToJavascript()[0][0].constructor.name"
)
if order == "C" and dtype in ("int8", "uint8"):
# Here we expect a TypedArray subclass, such as Uint8Array, but
Expand All @@ -104,9 +112,18 @@ def assert_equal():
assert selenium.run("np.array([True, False])") == [True, False]

selenium.run("x = np.array([['string1', 'string2'], ['string3', 'string4']])")
assert selenium.run_js("return pyodide.pyimport('x').length") == 2
assert selenium.run_js("return pyodide.pyimport('x')[0][0]") == "string1"
assert selenium.run_js("return pyodide.pyimport('x')[1][1]") == "string4"
assert (
selenium.run_js("return pyodide.pyimport('x').deepCopyToJavascript().length")
== 2
)
assert (
selenium.run_js("return pyodide.pyimport('x').deepCopyToJavascript()[0][0]")
== "string1"
)
assert (
selenium.run_js("return pyodide.pyimport('x').deepCopyToJavascript()[1][1]")
== "string4"
)


def test_py2js_buffer_clear_error_flag(selenium):
Expand Down Expand Up @@ -176,7 +193,9 @@ def test_runpythonasync_numpy(selenium_standalone):
"""
)
for i in range(5):
assert selenium_standalone.run_js(f"return pyodide.pyimport('x')[{i}] == 0")
assert selenium_standalone.run_js(
f"return pyodide.pyimport('x').deepCopyToJavascript()[{i}] == 0"
)


def test_runwebworker_numpy(selenium_standalone):
Expand Down
12 changes: 12 additions & 0 deletions src/core/pyproxy.c
Expand Up @@ -210,6 +210,18 @@ EM_JS(int, pyproxy_init, (), {
Module.hiwire.decref(idargs);
return jsresult;
},
shallowCopyToJavascript : function(){
let idresult = _python2js_with_depth(_getPtr(this), depth);
let result = Module.hiwire.get_value(idresult);
Module.hiwire.decref(idresult);
return result;
},
deepCopyToJavascript : function(depth = -1){
let idresult = _python2js_with_depth(_getPtr(this), depth);
let result = Module.hiwire.get_value(idresult);
Module.hiwire.decref(idresult);
return result;
},
};

let ignoredTargetFields = ["name", "length"];
Expand Down
2 changes: 1 addition & 1 deletion src/core/python2js.c
Expand Up @@ -342,7 +342,7 @@ JsRef
python2js(PyObject* x)
{
PyObject* map = PyDict_New();
JsRef result = _python2js_cache(x, map, -1);
JsRef result = _python2js_cache(x, map, 0);
Py_DECREF(map);

if (result == NULL) {
Expand Down
10 changes: 9 additions & 1 deletion src/core/python2js.h
Expand Up @@ -16,13 +16,21 @@ void
pythonexc2js();

/** Convert a Python object to a Javascript object.
* \param The Python object
* \param x The Python object
* \return The Javascript object -- might be an Error object in the case of an
* exception.
*/
JsRef
python2js(PyObject* x);

/** Convert a Python object to a Javascript object, copying standard collections
* into javascript down to specified depth \param x The Python object \param
* depth The maximum depth to copy \return The Javascript object -- might be an
* Error object in the case of an exception.
*/
JsRef
python2js_with_depth(PyObject* x, int depth);

/** Set up the global state for this module.
*/
int
Expand Down
2 changes: 1 addition & 1 deletion src/pyodide.js
Expand Up @@ -340,7 +340,7 @@ globalThis.languagePluginLoader = new Promise((resolve, reject) => {

// clang-format off
Module.loadPackagesFromImports = async function(code, messageCallback, errorCallback) {
let imports = Module.pyodide_py.find_imports(code);
let imports = Module.pyodide_py.find_imports(code).deepCopyToJavascript();
if (imports.length === 0) {
return;
}
Expand Down
8 changes: 4 additions & 4 deletions src/tests/test_jsproxy.py
Expand Up @@ -12,7 +12,7 @@ def test_jsproxy_dir(selenium):
from js import a
from js import b
[dir(a), dir(b)]
`);
`).deepCopyToJavascript();
"""
)
jsproxy_items = set(
Expand Down Expand Up @@ -49,7 +49,7 @@ def test_jsproxy_getattr(selenium):
return pyodide.runPython(`
from js import a
[ a.x, a.y, a.typeof ]
`);
`).deepCopyToJavascript();
"""
)
== [2, "9", "object"]
Expand Down Expand Up @@ -195,7 +195,7 @@ def test_jsproxy_call(selenium):
from js import f
[f(*range(n)) for n in range(10)]
`
);
).deepCopyToJavascript();
"""
)
== list(range(10))
Expand Down Expand Up @@ -379,7 +379,7 @@ def test_mount_object(selenium):
import b
result += [a.s, dir(a), dir(b)]
result
`)
`).deepCopyToJavascript()
"""
)
assert result[:3] == ["x1", "x2", 3]
Expand Down
2 changes: 2 additions & 0 deletions src/tests/test_pyproxy.py
Expand Up @@ -55,6 +55,8 @@ def get_value(self, value):
"apply",
"destroy",
"$$",
"deepCopyToJavascript",
"shallowCopyToJavascript",
]
)
assert selenium.run("hasattr(f, 'baz')")
Expand Down
2 changes: 1 addition & 1 deletion src/tests/test_python.py
Expand Up @@ -172,5 +172,5 @@ def test_unknown_attribute(selenium):
def test_run_python_debug(selenium):
assert selenium.run_js("return pyodide._module.runPythonDebug('1+1');") == 2
assert selenium.run_js(
"return pyodide._module.runPythonDebug('[x*x + 1 for x in range(4)]');"
"return pyodide._module.runPythonDebug('[x*x + 1 for x in range(4)]').deepCopyToJavascript();"
) == [1, 2, 5, 10]
2 changes: 1 addition & 1 deletion src/tests/test_stdlib_fixes.py
Expand Up @@ -30,7 +30,7 @@ def test_threading_import(selenium):
selenium.run(
"""
import threading
threading.local()
threading.local(); pass
"""
)

Expand Down
8 changes: 4 additions & 4 deletions src/tests/test_typeconversions.py
Expand Up @@ -22,14 +22,14 @@ def test_python2js(selenium):
)
assert selenium.run_js(
"""
let x = pyodide.runPython("[1, 2, 3]");
let x = pyodide.runPython("[1, 2, 3]").deepCopyToJavascript();
return ((x instanceof window.Array) && (x.length === 3) &&
(x[0] == 1) && (x[1] == 2) && (x[2] == 3))
"""
)
assert selenium.run_js(
"""
let x = pyodide.runPython("{42: 64}");
let x = pyodide.runPython("{42: 64}").deepCopyToJavascript();
return (typeof x === "object") && (x[42] === 64)
"""
)
Expand Down Expand Up @@ -230,7 +230,7 @@ def test_recursive_list_to_js(selenium_standalone):
x.append(x)
"""
)
selenium_standalone.run_js("x = pyodide.pyimport('x');")
selenium_standalone.run_js("x = pyodide.pyimport('x').deepCopyToJavascript();")


def test_recursive_dict_to_js(selenium_standalone):
Expand All @@ -240,7 +240,7 @@ def test_recursive_dict_to_js(selenium_standalone):
x[0] = x
"""
)
selenium_standalone.run_js("x = pyodide.pyimport('x');")
selenium_standalone.run_js("x = pyodide.pyimport('x').deepCopyToJavascript();")


def test_list_from_js(selenium):
Expand Down