Don't automatically copy python objects into javascript (#1152)

* Don't automatically copy python objects into javascript, add new PyProxy.shallowCopyToJavascript and deepCopyToJavascript apis

* Lint

* Adjust conftest to automatically deep_copy results of run / run_async

* Update conftest again

* Fix deep/shallowCopyToJavascript

* Fix shallowCopyToJavascript and deepCopyToJavascript to return js object not hiwire id

* Fix the remaining tests (hopefully)

* Lint

* Fix more tests

* Fix some numpy tests

* Lint

* Fix more tests

* Lint

* Temporarily dummy out setLineDash since it breaks test
This commit is contained in:
Hood Chatham 2021-01-22 03:28:22 -08:00 committed by GitHub
parent 1fa4b07809
commit 6a44233562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 90 additions and 29 deletions

View File

@ -95,10 +95,30 @@ class SeleniumWrapper:
self.driver.execute_script("window.logs = []") self.driver.execute_script("window.logs = []")
def run(self, code): 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): 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): def run_js(self, code):
if isinstance(code, str) and code.startswith("\n"): if isinstance(code, str) and code.startswith("\n"):

View File

@ -190,7 +190,7 @@ class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
rubberband.addEventListener("keydown", self.onkeydown) rubberband.addEventListener("keydown", self.onkeydown)
context = rubberband.getContext("2d") context = rubberband.getContext("2d")
context.strokeStyle = "#000000" context.strokeStyle = "#000000"
context.setLineDash([2, 2]) # context.setLineDash([2, 2])
canvas_div.appendChild(rubberband) canvas_div.appendChild(rubberband)
div.appendChild(canvas_div) div.appendChild(canvas_div)

View File

@ -2,15 +2,15 @@ def test_matplotlib(selenium_standalone):
selenium = selenium_standalone selenium = selenium_standalone
selenium.load_package("matplotlib") selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt") selenium.run("from matplotlib import pyplot as plt")
selenium.run("plt.figure()") selenium.run("plt.figure(); pass")
selenium.run("plt.plot([1,2,3])") selenium.run("x = plt.plot([1,2,3])")
selenium.run("plt.show()") selenium.run("plt.show()")
def test_svg(selenium): def test_svg(selenium):
selenium.load_package("matplotlib") selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt") 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("x = plt.plot([1,2,3])")
selenium.run("import io") selenium.run("import io")
selenium.run("fd = io.BytesIO()") selenium.run("fd = io.BytesIO()")
@ -23,7 +23,7 @@ def test_svg(selenium):
def test_pdf(selenium): def test_pdf(selenium):
selenium.load_package("matplotlib") selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt") 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("x = plt.plot([1,2,3])")
selenium.run("import io") selenium.run("import io")
selenium.run("fd = io.BytesIO()") selenium.run("fd = io.BytesIO()")

View File

@ -2,11 +2,17 @@ def test_numpy(selenium):
selenium.load_package("numpy") selenium.load_package("numpy")
selenium.run("import numpy") selenium.run("import numpy")
selenium.run("x = numpy.ones((32, 64))") 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): 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): 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): def test_typed_arrays(selenium):
@ -48,7 +54,9 @@ def test_python2js_numpy_dtype(selenium_standalone):
for j in range(2): for j in range(2):
for k in range(2): for k in range(2):
assert ( 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] == expected_result[i][j][k]
) )
@ -74,7 +82,7 @@ def test_python2js_numpy_dtype(selenium_standalone):
) )
assert_equal() assert_equal()
classname = selenium.run_js( 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"): if order == "C" and dtype not in ("uint64", "int64"):
# Here we expect a TypedArray subclass, such as Uint8Array, but # Here we expect a TypedArray subclass, such as Uint8Array, but
@ -90,7 +98,7 @@ def test_python2js_numpy_dtype(selenium_standalone):
) )
assert_equal() assert_equal()
classname = selenium.run_js( 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"): if order == "C" and dtype in ("int8", "uint8"):
# Here we expect a TypedArray subclass, such as Uint8Array, but # Here we expect a TypedArray subclass, such as Uint8Array, but
@ -104,9 +112,18 @@ def test_python2js_numpy_dtype(selenium_standalone):
assert selenium.run("np.array([True, False])") == [True, False] assert selenium.run("np.array([True, False])") == [True, False]
selenium.run("x = np.array([['string1', 'string2'], ['string3', 'string4']])") selenium.run("x = np.array([['string1', 'string2'], ['string3', 'string4']])")
assert selenium.run_js("return pyodide.pyimport('x').length") == 2 assert (
assert selenium.run_js("return pyodide.pyimport('x')[0][0]") == "string1" selenium.run_js("return pyodide.pyimport('x').deepCopyToJavascript().length")
assert selenium.run_js("return pyodide.pyimport('x')[1][1]") == "string4" == 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): def test_py2js_buffer_clear_error_flag(selenium):
@ -176,7 +193,9 @@ def test_runpythonasync_numpy(selenium_standalone):
""" """
) )
for i in range(5): 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): def test_runwebworker_numpy(selenium_standalone):

View File

@ -223,6 +223,18 @@ EM_JS(int, pyproxy_init, (), {
Module.hiwire.decref(idresult); Module.hiwire.decref(idresult);
return jsresult; 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"]; let ignoredTargetFields = ["name", "length"];

View File

@ -342,7 +342,7 @@ JsRef
python2js(PyObject* x) python2js(PyObject* x)
{ {
PyObject* map = PyDict_New(); PyObject* map = PyDict_New();
JsRef result = _python2js_cache(x, map, -1); JsRef result = _python2js_cache(x, map, 0);
Py_DECREF(map); Py_DECREF(map);
if (result == NULL) { if (result == NULL) {

View File

@ -16,13 +16,21 @@ void
pythonexc2js(); pythonexc2js();
/** Convert a Python object to a Javascript object. /** 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 * \return The Javascript object -- might be an Error object in the case of an
* exception. * exception.
*/ */
JsRef JsRef
python2js(PyObject* x); 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. /** Set up the global state for this module.
*/ */
int int

View File

@ -362,7 +362,7 @@ globalThis.languagePluginLoader = new Promise((resolve, reject) => {
// clang-format off // clang-format off
Module.loadPackagesFromImports = async function(code, messageCallback, errorCallback) { 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) { if (imports.length === 0) {
return; return;
} }

View File

@ -12,7 +12,7 @@ def test_jsproxy_dir(selenium):
from js import a from js import a
from js import b from js import b
[dir(a), dir(b)] [dir(a), dir(b)]
`); `).deepCopyToJavascript();
""" """
) )
jsproxy_items = set( jsproxy_items = set(
@ -49,7 +49,7 @@ def test_jsproxy_getattr(selenium):
return pyodide.runPython(` return pyodide.runPython(`
from js import a from js import a
[ a.x, a.y, a.typeof ] [ a.x, a.y, a.typeof ]
`); `).deepCopyToJavascript();
""" """
) )
== [2, "9", "object"] == [2, "9", "object"]
@ -195,7 +195,7 @@ def test_jsproxy_call(selenium):
from js import f from js import f
[f(*range(n)) for n in range(10)] [f(*range(n)) for n in range(10)]
` `
); ).deepCopyToJavascript();
""" """
) )
== list(range(10)) == list(range(10))
@ -379,7 +379,7 @@ def test_mount_object(selenium):
import b import b
result += [a.s, dir(a), dir(b)] result += [a.s, dir(a), dir(b)]
result result
`) `).deepCopyToJavascript()
""" """
) )
assert result[:3] == ["x1", "x2", 3] assert result[:3] == ["x1", "x2", 3]

View File

@ -55,6 +55,8 @@ def test_pyproxy(selenium):
"apply", "apply",
"destroy", "destroy",
"$$", "$$",
"deepCopyToJavascript",
"shallowCopyToJavascript",
] ]
) )
assert selenium.run("hasattr(f, 'baz')") assert selenium.run("hasattr(f, 'baz')")

View File

@ -172,5 +172,5 @@ def test_unknown_attribute(selenium):
def test_run_python_debug(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('1+1');") == 2
assert selenium.run_js( 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] ) == [1, 2, 5, 10]

View File

@ -22,14 +22,14 @@ def test_python2js(selenium):
) )
assert selenium.run_js( 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) && return ((x instanceof window.Array) && (x.length === 3) &&
(x[0] == 1) && (x[1] == 2) && (x[2] == 3)) (x[0] == 1) && (x[1] == 2) && (x[2] == 3))
""" """
) )
assert selenium.run_js( assert selenium.run_js(
""" """
let x = pyodide.runPython("{42: 64}"); let x = pyodide.runPython("{42: 64}").deepCopyToJavascript();
return (typeof x === "object") && (x[42] === 64) return (typeof x === "object") && (x[42] === 64)
""" """
) )
@ -230,7 +230,7 @@ def test_recursive_list_to_js(selenium_standalone):
x.append(x) 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): def test_recursive_dict_to_js(selenium_standalone):
@ -240,7 +240,7 @@ def test_recursive_dict_to_js(selenium_standalone):
x[0] = x 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): def test_list_from_js(selenium):