diff --git a/conftest.py b/conftest.py index 30ce01ed769..fb4acc38da2 100644 --- a/conftest.py +++ b/conftest.py @@ -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"): diff --git a/packages/matplotlib/src/wasm_backend.py b/packages/matplotlib/src/wasm_backend.py index a206b3c7276..e8a16279d3f 100644 --- a/packages/matplotlib/src/wasm_backend.py +++ b/packages/matplotlib/src/wasm_backend.py @@ -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) diff --git a/packages/matplotlib/test_matplotlib.py b/packages/matplotlib/test_matplotlib.py index b37e16fa13a..243575c80d9 100644 --- a/packages/matplotlib/test_matplotlib.py +++ b/packages/matplotlib/test_matplotlib.py @@ -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])") 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()") @@ -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()") diff --git a/packages/numpy/test_numpy.py b/packages/numpy/test_numpy.py index f4c1d3c1c98..c46a1cb9939 100644 --- a/packages/numpy/test_numpy.py +++ b/packages/numpy/test_numpy.py @@ -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): @@ -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] ) @@ -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 @@ -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 @@ -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): @@ -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): diff --git a/src/core/pyproxy.c b/src/core/pyproxy.c index fb92b96d3fe..ffb3227acd2 100644 --- a/src/core/pyproxy.c +++ b/src/core/pyproxy.c @@ -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"]; diff --git a/src/core/python2js.c b/src/core/python2js.c index 47cabfa51cf..d189a3f5fd1 100644 --- a/src/core/python2js.c +++ b/src/core/python2js.c @@ -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) { diff --git a/src/core/python2js.h b/src/core/python2js.h index cd3eb89da8c..8371112b9b1 100644 --- a/src/core/python2js.h +++ b/src/core/python2js.h @@ -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 diff --git a/src/pyodide.js b/src/pyodide.js index ea89a7ac86c..c2ad4e7567b 100644 --- a/src/pyodide.js +++ b/src/pyodide.js @@ -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; } diff --git a/src/tests/test_jsproxy.py b/src/tests/test_jsproxy.py index e41753f3440..94b2a69a3e0 100644 --- a/src/tests/test_jsproxy.py +++ b/src/tests/test_jsproxy.py @@ -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( @@ -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"] @@ -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)) @@ -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] diff --git a/src/tests/test_pyproxy.py b/src/tests/test_pyproxy.py index fb3f5b71c7b..532f5359842 100644 --- a/src/tests/test_pyproxy.py +++ b/src/tests/test_pyproxy.py @@ -55,6 +55,8 @@ def get_value(self, value): "apply", "destroy", "$$", + "deepCopyToJavascript", + "shallowCopyToJavascript", ] ) assert selenium.run("hasattr(f, 'baz')") diff --git a/src/tests/test_python.py b/src/tests/test_python.py index 9713c818a35..c19e59f8379 100644 --- a/src/tests/test_python.py +++ b/src/tests/test_python.py @@ -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] diff --git a/src/tests/test_stdlib_fixes.py b/src/tests/test_stdlib_fixes.py index b29b668fece..07fb968e100 100644 --- a/src/tests/test_stdlib_fixes.py +++ b/src/tests/test_stdlib_fixes.py @@ -30,7 +30,7 @@ def test_threading_import(selenium): selenium.run( """ import threading - threading.local() + threading.local(); pass """ ) diff --git a/src/tests/test_typeconversions.py b/src/tests/test_typeconversions.py index d475c974970..55f98b7d3a8 100644 --- a/src/tests/test_typeconversions.py +++ b/src/tests/test_typeconversions.py @@ -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) """ ) @@ -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): @@ -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):