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

Add JsProxy.to_py method #1244

Merged
merged 11 commits into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
230 changes: 187 additions & 43 deletions src/core/js2python.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
#include "jsproxy.h"
#include "pyproxy.h"

// Since we're going *to* Python, just let any Python exceptions at conversion
// bubble out to Python

PyObject*
_js2python_allocate_string(int size, int max_code_point)
{
Expand Down Expand Up @@ -67,15 +64,27 @@ _js2python_memoryview(JsRef id)
return PyMemoryView_FromObject(jsproxy);
}

PyObject*
_js2python_jsproxy(JsRef id)
{
return JsProxy_create(id);
}
EM_JS_REF(PyObject*, js2python, (JsRef id), {
let value = Module.hiwire.get_value(id);
let result = Module.__js2python_convertImmutable(value);
// clang-format off
if (result !== 0) {
return result;
}
if (value['byteLength'] !== undefined) {
return __js2python_memoryview(id);
} else {
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
return _JsProxy_create(id);
}
// clang-format on
})

EM_JS_REF(PyObject*, js2python_convert, (JsRef id, int depth), {
return Module.__js2python_convert(id, new Map(), depth);
});

// TODO: Add some meaningful order
EM_JS_REF(PyObject*, __js2python, (JsRef id), {
function __js2python_string(value)
EM_JS_NUM(errcode, js2python_init, (), {
Module.__js2python_string = function(value)
{
// The general idea here is to allocate a Python string and then
// have Javascript write directly into its buffer. We first need
Expand Down Expand Up @@ -124,39 +133,174 @@ EM_JS_REF(PyObject*, __js2python, (JsRef id), {
}

return result;
}
};

// clang-format off
let value = Module.hiwire.get_value(id);
let type = typeof value;
if (type === 'string') {
return __js2python_string(value);
} else if (type === 'number') {
return __js2python_number(value);
} else if (value === undefined || value === null) {
return __js2python_none();
} else if (value === true) {
return __js2python_true();
} else if (value === false) {
return __js2python_false();
} else if (Module.PyProxy.isPyProxy(value)) {
return __js2python_pyproxy(Module.PyProxy._getPtr(value));
} else if (value['byteLength'] !== undefined) {
return __js2python_memoryview(id);
} else {
return __js2python_jsproxy(id);
}
// clang-format on
});
Module.__js2python_convertImmutable = function(value)
{
let type = typeof value;
// clang-format off
if (type === 'string') {
return Module.__js2python_string(value);
} else if (type === 'number') {
return __js2python_number(value);
} else if (value === undefined || value === null) {
return __js2python_none();
} else if (value === true) {
return __js2python_true();
} else if (value === false) {
return __js2python_false();
} else if (Module.PyProxy.isPyProxy(value)) {
return __js2python_pyproxy(Module.PyProxy._getPtr(value));
}
// clang-format on
return 0;
};

PyObject*
js2python(JsRef id)
{
return (PyObject*)__js2python(id);
}
Module.__js2python_convertList = function(obj, map, depth)
{
let list = _PyList_New(obj.length);
// clang-format off
if (list === 0) {
// clang-format on
return 0;
}
map.set(obj, list);
for (let i = 0; i < obj.length; i++) {
let entryid = Module.hiwire.new_value(obj[i]);
let item = Module.__js2python_convert(entryid, map, depth);
Module.hiwire.decref(entryid);
// clang-format off
if (item === 0) {
// clang-format on
_Py_DecRef(list);
return 0;
}
// PyList_SetItem steals a reference to item no matter what
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
let errcode = _PyList_SetItem(list, i, item);
// clang-format off
if (errcode === -1) {
// clang-format on
_Py_DecRef(list);
return 0;
}
}
return list;
};

Module.__js2python_convertMap = function(obj, entries, map, depth)
{
let dict = _PyDict_New();
// clang-format off
if (dict === 0) {
// clang-format on
return 0;
}
map.set(obj, dict);
for (let[key_js, value_js] of entries) {
let key_id = Module.hiwire.new_value(key_js);
let key_py = Module.__js2python_convert(key_id, map, depth);
Module.hiwire.decref(key_id);
// clang-format off
if (key_py === 0) {
// clang-format on
_Py_DecRef(dict);
return 0;
}

let value_id = Module.hiwire.new_value(value_js);
let value_py = Module.__js2python_convert(value_id, map, depth);
Module.hiwire.decref(value_id);
// clang-format off
if (value_py === 0) {
// clang-format on
_Py_DecRef(dict);
return 0;
}

// PyDict_SetItem does not steal references
let errcode = _PyDict_SetItem(dict, key_py, value_py);
_Py_DecRef(key_py);
_Py_DecRef(value_py);
// clang-format off
if (errcode === -1) {
// clang-format on
_Py_DecRef(dict);
return 0;
}
}
return dict;
};

Module.__js2python_convertSet = function(obj, map, depth)
{
let set = _PySet_New(0);
// clang-format off
if (set === 0) {
// clang-format on
return 0;
}
map.set(obj, set);
for (let key_js of obj) {
let key_id = Module.hiwire.new_value(key_js);
let key_py = Module.__js2python_convert(key_id, map, depth);
Module.hiwire.decref(key_id);
// clang-format off
if (key_py === 0) {
// clang-format on
_Py_DecRef(set);
return 0;
}
let errcode = _PySet_Add(set, key_py);
_Py_DecRef(key_py);
// clang-format off
if (errcode === -1) {
// clang-format on
_Py_DecRef(set);
return 0;
}
}
return set;
};

Module.__js2python_convertOther = function(id, value, map, depth)
{
let toStringTag = Object.prototype.toString.call(value);
// clang-format off
if (Array.isArray(value) || value === "[object HTMLCollection]" ||
value === "[object NodeList]") {
return Module.__js2python_convertList(value, map, depth);
}
if (toStringTag === "[object Map]" || value instanceof Map) {
return Module.__js2python_convertMap(value, value.entries(), map, depth);
}
if (toStringTag === "[object Set]" || value instanceof Set) {
return Module.__js2python_convertSet(value, map, depth);
}
if (toStringTag === "[object Object]" && (value.constructor === undefined || value.constructor.name === "Object")) {
return Module.__js2python_convertMap(value, Object.entries(value), map, depth);
}
// clang-format on
return _JsProxy_create(id);
};

Module.__js2python_convert = function(id, map, depth)
{
let value = Module.hiwire.get_value(id);
let result = Module.__js2python_convertImmutable(value);
// clang-format off
if (result !== 0) {
return result;
}
if (depth === 0) {
return _JsProxy_create(id);
}
result = map.get(value);
if (result !== undefined) {
return result;
}
// clang-format on
return Module.__js2python_convertOther(id, value, map, depth - 1);
};

int
js2python_init()
{
return 0;
}
})
3 changes: 3 additions & 0 deletions src/core/js2python.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
PyObject*
js2python(JsRef x);

PyObject*
js2python_convert(JsRef x, int depth);

/** Initialize any global variables used by this module. */
int
js2python_init();
Expand Down
26 changes: 25 additions & 1 deletion src/core/jsproxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,25 @@ JsProxy_Dir(PyObject* self, PyObject* _args)
return result;
}

static PyObject*
JsProxy_toPy(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
{
if (nargs > 1) {
PyErr_Format(
PyExc_TypeError, "to_py expected at most 1 argument, got %zd", nargs);
return NULL;
}
int depth = -1;
if (nargs == 1) {
int overflow;
depth = PyLong_AsLongAndOverflow(args[0], &overflow);
if (overflow == 0 && depth == -1 && PyErr_Occurred()) {
return NULL;
}
}
return js2python_convert(GET_JSREF(self), depth);
}

static int
JsProxy_Bool(PyObject* o)
{
Expand Down Expand Up @@ -382,7 +401,12 @@ static PyMethodDef JsProxy_Methods[] = {
{ "__dir__",
(PyCFunction)JsProxy_Dir,
METH_NOARGS,
"Returns a list of the members and methods on the object." },
PyDoc_STR("Returns a list of the members and methods on the object.") },
{
"to_py",
(PyCFunction)JsProxy_toPy,
METH_FASTCALL,
PyDoc_STR("Convert the JsProxy to a native Python object (as best as possible)")},
{ NULL }
};
// clang-format on
Expand Down
63 changes: 63 additions & 0 deletions src/tests/test_typeconversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,69 @@ def test_python2js_with_depth(selenium):
)


def test_to_py(selenium):
result = selenium.run_js(
"""
window.a = new Map([[1, [1,2,new Set([1,2,3])]], [2, new Map([[1,2],[2,7]])]]);
a.get(2).set("a", a);
let result = [];
for(let i = 0; i < 4; i++){
result.push(pyodide.runPython(`
from js import a
repr(a.to_py(${i}))
`));
}
return result;
"""
)
assert result == [
"[object Map]",
"{1: 1,2,[object Set], 2: [object Map]}",
"{1: [1, 2, [object Set]], 2: {1: 2, 2: 7, 'a': [object Map]}}",
"{1: [1, 2, {1, 2, 3}], 2: {1: 2, 2: 7, 'a': {...}}}",
]

result = selenium.run_js(
"""
window.a = { "x" : 2, "y" : 7, "z" : [1,2] };
a.z.push(a);
let result = [];
for(let i = 0; i < 4; i++){
result.push(pyodide.runPython(`
from js import a
repr(a.to_py(${i}))
`));
}
return result;
"""
)
assert result == [
"[object Object]",
"{'x': 2, 'y': 7, 'z': 1,2,[object Object]}",
"{'x': 2, 'y': 7, 'z': [1, 2, [object Object]]}",
"{'x': 2, 'y': 7, 'z': [1, 2, {...}]}",
]

result = selenium.run_js(
"""
class Temp {
constructor(){
this.x = 2;
this.y = 7;
}
}
window.a = new Temp();
let result = pyodide.runPython(`
from js import a
b = a.to_py()
repr(type(b))
`);
return result;
"""
)
assert result == "<class 'JsProxy'>"


hoodmane marked this conversation as resolved.
Show resolved Hide resolved
@pytest.mark.xfail
def test_py2js_set(selenium):
selenium.run("a = {1, 2, 3}")
Expand Down