Use proper duck typing for JsProxy (#1186)

This commit is contained in:
Hood Chatham 2021-03-11 13:32:14 -08:00 committed by GitHub
parent 29639e0541
commit ab249a0a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1196 additions and 244 deletions

View File

@ -12,7 +12,10 @@ except ImportError:
class _module:
class packages:
dependencies = [] # type: ignore
class dependencies:
@staticmethod
def object_entries():
return []
import hashlib
@ -140,7 +143,9 @@ class _PackageManager:
def __init__(self):
self.builtin_packages = {}
self.builtin_packages.update(js_pyodide._module.packages.dependencies)
self.builtin_packages.update(
js_pyodide._module.packages.dependencies.object_entries()
)
self.installed_packages = {}
def install(

View File

@ -6,6 +6,9 @@
#include "hiwire.h"
#define ERROR_REF (0)
#define ERROR_NUM (-1)
const JsRef Js_undefined = ((JsRef)(2));
const JsRef Js_true = ((JsRef)(4));
const JsRef Js_false = ((JsRef)(6));
@ -217,6 +220,18 @@ EM_JS(void _Py_NO_RETURN, hiwire_throw_error, (JsRef iderr), {
throw Module.hiwire.pop_value(iderr);
});
EM_JS_NUM(bool, hiwire_is_array, (JsRef idobj), {
let obj = Module.hiwire.get_value(idobj);
if (Array.isArray(obj)) {
return true;
}
let result = Object.prototype.toString.call(obj);
// We want to treat some standard array-like objects as Array.
// clang-format off
return result === "[object HTMLCollection]" || result === "[object NodeList]";
// clang-format on
});
EM_JS_REF(JsRef, hiwire_array, (), { return Module.hiwire.new_value([]); });
EM_JS_NUM(errcode, hiwire_push_array, (JsRef idarr, JsRef idval), {
@ -237,21 +252,25 @@ EM_JS_NUM(errcode,
EM_JS_REF(JsRef, hiwire_get_global, (const char* ptrname), {
let jsname = UTF8ToString(ptrname);
if (jsname in self) {
return Module.hiwire.new_value(self[jsname]);
} else {
return Module.hiwire.ERROR;
let result = globalThis[jsname];
// clang-format off
if (result === undefined && !(jsname in globalThis)) {
// clang-format on
return ERROR_REF;
}
return Module.hiwire.new_value(result);
});
EM_JS_REF(JsRef, hiwire_get_member_string, (JsRef idobj, const char* ptrkey), {
let jsobj = Module.hiwire.get_value(idobj);
let jskey = UTF8ToString(ptrkey);
if (jskey in jsobj) {
return Module.hiwire.new_value(jsobj[jskey]);
} else {
return Module.hiwire.ERROR;
let result = jsobj[jskey];
// clang-format off
if (result === undefined && !(jskey in jsobj)) {
// clang-format on
return ERROR_REF;
}
return Module.hiwire.new_value(result);
});
EM_JS_NUM(errcode,
@ -274,22 +293,40 @@ EM_JS_NUM(errcode,
});
EM_JS_REF(JsRef, hiwire_get_member_int, (JsRef idobj, int idx), {
let jsobj = Module.hiwire.get_value(idobj);
return Module.hiwire.new_value(jsobj[idx]);
let obj = Module.hiwire.get_value(idobj);
let result = obj[idx];
// clang-format off
if (result === undefined && !(idx in obj)) {
// clang-format on
return ERROR_REF;
}
return Module.hiwire.new_value(result);
});
EM_JS_NUM(errcode, hiwire_set_member_int, (JsRef idobj, int idx, JsRef idval), {
Module.hiwire.get_value(idobj)[idx] = Module.hiwire.get_value(idval);
});
EM_JS_NUM(errcode, hiwire_delete_member_int, (JsRef idobj, int idx), {
let obj = Module.hiwire.get_value(idobj);
// Weird edge case: allow deleting an empty entry, but we raise a key error if
// access is attempted.
if (idx < 0 || idx >= obj.length) {
return ERROR_NUM;
}
obj.splice(idx, 1);
});
EM_JS_REF(JsRef, hiwire_get_member_obj, (JsRef idobj, JsRef ididx), {
let jsobj = Module.hiwire.get_value(idobj);
let jsidx = Module.hiwire.get_value(ididx);
if (jsidx in jsobj) {
return Module.hiwire.new_value(jsobj[jsidx]);
} else {
return Module.hiwire.ERROR;
let result = jsobj[jsidx];
// clang-format off
if (result === undefined && !(jsidx in jsobj)) {
// clang-format on
return ERROR_REF;
}
return Module.hiwire.new_value(result);
});
EM_JS_NUM(errcode,
@ -362,8 +399,25 @@ EM_JS_REF(JsRef, hiwire_new, (JsRef idobj, JsRef idargs), {
return Module.hiwire.new_value(Reflect.construct(jsobj, jsargs));
});
EM_JS_NUM(bool, hiwire_has_length, (JsRef idobj), {
let val = Module.hiwire.get_value(idobj);
// clang-format off
return (typeof val.size === "number") ||
(typeof val.length === "number" && typeof val !== "function");
// clang-format on
});
EM_JS_NUM(int, hiwire_get_length, (JsRef idobj), {
return Module.hiwire.get_value(idobj).length;
let val = Module.hiwire.get_value(idobj);
// clang-format off
if (typeof val.size === "number") {
return val.size;
}
if (typeof val.length === "number") {
return val.length;
}
// clang-format on
return ERROR_NUM;
});
EM_JS_NUM(bool, hiwire_get_bool, (JsRef idobj), {
@ -383,12 +437,88 @@ EM_JS_NUM(bool, hiwire_get_bool, (JsRef idobj), {
// clang-format on
});
EM_JS_NUM(bool, hiwire_is_pyproxy, (JsRef idobj), {
EM_JS_NUM(bool, hiwire_has_has_method, (JsRef idobj), {
// clang-format off
return Module.PyProxy.isPyProxy(Module.hiwire.get_value(idobj));
let obj = Module.hiwire.get_value(idobj);
return obj && typeof obj.has === "function";
// clang-format on
});
EM_JS_NUM(bool, hiwire_call_has_method, (JsRef idobj, JsRef idkey), {
// clang-format off
let obj = Module.hiwire.get_value(idobj);
let key = Module.hiwire.get_value(idkey);
return obj.has(key);
// clang-format on
});
EM_JS_NUM(bool, hiwire_has_includes_method, (JsRef idobj), {
// clang-format off
let obj = Module.hiwire.get_value(idobj);
return obj && typeof obj.includes === "function";
// clang-format on
});
EM_JS_NUM(bool, hiwire_call_includes_method, (JsRef idobj, JsRef idval), {
let obj = Module.hiwire.get_value(idobj);
let val = Module.hiwire.get_value(idval);
return obj.includes(val);
});
EM_JS_NUM(bool, hiwire_has_get_method, (JsRef idobj), {
// clang-format off
let obj = Module.hiwire.get_value(idobj);
return obj && typeof obj.get === "function";
// clang-format on
});
EM_JS_REF(JsRef, hiwire_call_get_method, (JsRef idobj, JsRef idkey), {
let obj = Module.hiwire.get_value(idobj);
let key = Module.hiwire.get_value(idkey);
let result = obj.get(key);
// clang-format off
if (result === undefined) {
// Try to distinguish between undefined and missing:
// If the object has a "has" method and it returns false for this key, the
// key is missing. Otherwise, assume key present and value was undefined.
// TODO: in absence of a "has" method, should we return None or KeyError?
if (obj.has && typeof obj.has === "function" && !obj.has(key)) {
return ERROR_REF;
}
}
// clang-format on
return Module.hiwire.new_value(result);
});
EM_JS_NUM(bool, hiwire_has_set_method, (JsRef idobj), {
// clang-format off
let obj = Module.hiwire.get_value(idobj);
return obj && typeof obj.set === "function";
// clang-format on
});
EM_JS_NUM(errcode,
hiwire_call_set_method,
(JsRef idobj, JsRef idkey, JsRef idval),
{
let obj = Module.hiwire.get_value(idobj);
let key = Module.hiwire.get_value(idkey);
let val = Module.hiwire.get_value(idval);
let result = obj.set(key, val);
});
EM_JS_NUM(errcode, hiwire_call_delete_method, (JsRef idobj, JsRef idkey), {
let obj = Module.hiwire.get_value(idobj);
let key = Module.hiwire.get_value(idkey);
if (!obj.delete(key)) {
return -1;
}
});
EM_JS_NUM(bool, hiwire_is_pyproxy, (JsRef idobj), {
return Module.PyProxy.isPyProxy(Module.hiwire.get_value(idobj));
});
EM_JS_NUM(bool, hiwire_is_function, (JsRef idobj), {
// clang-format off
return typeof Module.hiwire.get_value(idobj) === 'function';
@ -448,34 +578,40 @@ MAKE_OPERATOR(not_equal, !==);
MAKE_OPERATOR(greater_than, >);
MAKE_OPERATOR(greater_than_equal, >=);
EM_JS_REF(int, hiwire_next, (JsRef idobj, JsRef* result_ptr), {
// clang-format off
EM_JS_REF(JsRef, hiwire_is_iterator, (JsRef idobj), {
let jsobj = Module.hiwire.get_value(idobj);
// clang-format off
return typeof jsobj.next === 'function';
// clang-format on
});
EM_JS_NUM(int, hiwire_next, (JsRef idobj, JsRef* result_ptr), {
let jsobj = Module.hiwire.get_value(idobj);
// clang-format off
let { done, value } = jsobj.next();
// clang-format on
let result_id = Module.hiwire.new_value(value);
setValue(result_ptr, result_id, "i32");
return done;
});
EM_JS_REF(JsRef, hiwire_is_iterable, (JsRef idobj), {
let jsobj = Module.hiwire.get_value(idobj);
// clang-format off
return typeof jsobj[Symbol.iterator] === 'function';
// clang-format on
});
EM_JS_REF(JsRef, hiwire_get_iterator, (JsRef idobj), {
// clang-format off
if (idobj === Module.hiwire.UNDEFINED) {
return Module.hiwire.ERROR;
}
let jsobj = Module.hiwire.get_value(idobj);
if (typeof jsobj.next === 'function') {
return Module.hiwire.new_value(jsobj);
} else if (typeof jsobj[Symbol.iterator] === 'function') {
return Module.hiwire.new_value(jsobj[Symbol.iterator]());
} else {
return Module.hiwire.new_value(Object.entries(jsobj)[Symbol.iterator]());
}
return Module.hiwire.ERROR;
// clang-format on
return Module.hiwire.new_value(jsobj[Symbol.iterator]());
})
EM_JS_REF(JsRef, hiwire_object_entries, (JsRef idobj), {
let jsobj = Module.hiwire.get_value(idobj);
return Module.hiwire.new_value(Object.entries(jsobj));
});
EM_JS_NUM(bool, hiwire_is_typedarray, (JsRef idobj), {
let jsobj = Module.hiwire.get_value(idobj);
// clang-format off
@ -541,7 +677,7 @@ EM_JS_REF(JsRef, hiwire_subarray, (JsRef idarr, int start, int end), {
return Module.hiwire.new_value(jssub);
});
EM_JS_NUM(JsRef, JsMap_New, (), { return Module.hiwire.new_value(new Map()); })
EM_JS_REF(JsRef, JsMap_New, (), { return Module.hiwire.new_value(new Map()); })
EM_JS_NUM(errcode, JsMap_Set, (JsRef mapid, JsRef keyid, JsRef valueid), {
let map = Module.hiwire.get_value(mapid);
@ -550,7 +686,7 @@ EM_JS_NUM(errcode, JsMap_Set, (JsRef mapid, JsRef keyid, JsRef valueid), {
map.set(key, value);
})
EM_JS_NUM(JsRef, JsSet_New, (), { return Module.hiwire.new_value(new Set()); })
EM_JS_REF(JsRef, JsSet_New, (), { return Module.hiwire.new_value(new Set()); })
EM_JS_NUM(errcode, JsSet_Add, (JsRef mapid, JsRef keyid), {
let set = Module.hiwire.get_value(mapid);

View File

@ -240,6 +240,9 @@ hiwire_float64array(f64* ptr, int len);
JsRef
hiwire_bool(bool boolean);
bool
hiwire_is_array(JsRef idobj);
/**
* Create a new Javascript Array.
*
@ -306,7 +309,6 @@ hiwire_set_member_string(JsRef idobj, const char* ptrname, JsRef idval);
/**
* Delete an object member by string.
*
*/
errcode
hiwire_delete_member_string(JsRef idobj, const char* ptrname);
@ -314,8 +316,6 @@ hiwire_delete_member_string(JsRef idobj, const char* ptrname);
/**
* Get an object member by integer.
*
* The integer is a C integer, not an id reference to a Javascript integer.
*
* Returns: New reference
*/
JsRef
@ -323,13 +323,13 @@ hiwire_get_member_int(JsRef idobj, int idx);
/**
* Set an object member by integer.
*
* The integer is a C integer, not an id reference to a Javascript integer.
*
*/
errcode
hiwire_set_member_int(JsRef idobj, int idx, JsRef idval);
errcode
hiwire_delete_member_int(JsRef idobj, int idx);
/**
* Get an object member by object.
*
@ -398,35 +398,112 @@ JsRef
hiwire_new(JsRef idobj, JsRef idargs);
/**
* Returns the value of the `length` member on a Javascript object.
*
* Returns: C int
* Test if the object has a `size` or `length` member which is a number. As a
* special case, if the object is a function the `length` field is ignored.
*/
bool
hiwire_has_length(JsRef idobj);
/**
* Returns the value of the `size` or `length` member on a Javascript object.
* Prefers the `size` member if present and a number to the `length` field. If
* both `size` and `length` are missing or not a number, returns `-1` to
* indicate error.
*/
int
hiwire_get_length(JsRef idobj);
/**
* Returns the boolean value of a Javascript object.
*
* Returns: C int
*/
bool
hiwire_get_bool(JsRef idobj);
/**
* Check whether `typeof obj.has === "function"`
*/
bool
hiwire_has_has_method(JsRef idobj);
/**
* Does `obj.has(val)`. Doesn't check type of return value, if it isn't a
* boolean or an integer it will get coerced to false.
*/
bool
hiwire_call_has_method(JsRef idobj, JsRef idval);
/**
* Check whether `typeof obj.includes === "function"`.
*/
bool
hiwire_has_includes_method(JsRef idobj);
/**
* Does `obj.includes(val)`. Doesn't check type of return value, if it isn't a
* boolean or an integer it will get coerced to `false`.
*/
bool
hiwire_call_includes_method(JsRef idobj, JsRef idval);
/**
* Check whether `typeof obj.get === "function"`.
*/
bool
hiwire_has_get_method(JsRef idobj);
/**
* Call `obj.get(key)`. If the result is `undefined`, we check for a `has`
* method and if one is present call `obj.has(key)`. If this returns false we
* return `NULL` to signal a `KeyError` otherwise we return `Js_Undefined`. If
* no `has` method is present, we return `Js_Undefined`.
*/
JsRef
hiwire_call_get_method(JsRef idobj, JsRef idkey);
/**
* Check whether `typeof obj.set === "function"`.
*/
bool
hiwire_has_set_method(JsRef idobj);
/**
* Call `obj.set(key, value)`. Javascript standard is that `set` returns `false`
* to indicate an error condition, but we ignore the return value.
*/
errcode
hiwire_call_set_method(JsRef idobj, JsRef idkey, JsRef idval);
/**
* Call `obj.delete(key)`. Javascript standard is that `delete` returns `false`
* to indicate an error condition, if `false` is returned we return `-1` to
* indicate the error.
*/
errcode
hiwire_call_delete_method(JsRef idobj, JsRef idkey);
/**
* Check whether the object is a PyProxy.
*/
bool
hiwire_is_pyproxy(JsRef idobj);
/**
* Returns 1 if the object is a function.
*
* Returns: C int
* Check if the object is a function.
*/
bool
hiwire_is_function(JsRef idobj);
/**
* Check if the object is an error.
*/
bool
hiwire_is_error(JsRef idobj);
/**
* Check if the function supports kwargs. A fairly involved check which parses
* func.toString() to determine if the last argument does object destructuring.
* Actual implementation in pyodide.js.
*/
bool
hiwire_function_supports_kwargs(JsRef idfunc);
@ -453,7 +530,7 @@ JsRef
hiwire_to_string(JsRef idobj);
/**
* Gets the "typeof" string for a value.
* Gets the `typeof` string for a value.
*
* Returns: New reference to Javascript string
*/
@ -461,7 +538,7 @@ JsRef
hiwire_typeof(JsRef idobj);
/**
* Gets "value.constructor.name".
* Gets `value.constructor.name`.
*
* Returns: New reference to Javascript string
*/
@ -504,21 +581,40 @@ hiwire_greater_than(JsRef ida, JsRef idb);
bool
hiwire_greater_than_equal(JsRef ida, JsRef idb);
/**
* Check if `typeof obj.next === "function"`
*/
JsRef
hiwire_is_iterator(JsRef idobj);
/**
* Calls the `next` function on an iterator.
*
* Returns -1 if an error occurs.
* Stores "value" into argument "result", returns "done".
* Returns -1 if an error occurs. Otherwise, `next` should return an object with
* `value` and `done` fields. We store `value` into the argument `result` and
* return `done`.
*/
int
hiwire_next(JsRef idobj, JsRef* result);
/**
* Check if `typeof obj[Symbol.iterator] === "function"`
*/
JsRef
hiwire_is_iterable(JsRef idobj);
/**
* Returns the iterator associated with the given object, if any.
*/
JsRef
hiwire_get_iterator(JsRef idobj);
/**
* Returns `Object.entries(obj)`
*/
JsRef
hiwire_object_entries(JsRef idobj);
/**
* Returns 1 if the value is a typedarray.
*/
@ -532,10 +628,10 @@ bool
hiwire_is_on_wasm_heap(JsRef idobj);
/**
* Returns the value of obj.byteLength.
* Returns the value of `obj.byteLength`.
*
* There is no error checking. Caller must ensure that hiwire_is_typedarray is
* true.
* true. If these conditions are not met, returns `0`.
*/
int
hiwire_get_byteLength(JsRef idobj);
@ -544,7 +640,8 @@ hiwire_get_byteLength(JsRef idobj);
* Returns the value of obj.byteOffset.
*
* There is no error checking. Caller must ensure that hiwire_is_typedarray is
* true and hiwire_is_on_wasm_heap is true.
* true and hiwire_is_on_wasm_heap is true. If these conditions are not met,
* returns `0`.
*/
int
hiwire_get_byteOffset(JsRef idobj);
@ -568,15 +665,27 @@ hiwire_get_dtype(JsRef idobj, char** format_ptr, Py_ssize_t* size_ptr);
JsRef
hiwire_subarray(JsRef idarr, int start, int end);
/**
* Create a new Map.
*/
JsRef
JsMap_New();
/**
* Does map.set(key, value).
*/
errcode
JsMap_Set(JsRef mapid, JsRef keyid, JsRef valueid);
/**
* Create a new Set.
*/
JsRef
JsSet_New();
/**
* Does set.add(key).
*/
errcode
JsSet_Add(JsRef mapid, JsRef keyid);

View File

@ -61,6 +61,9 @@ PyObject*
_js2python_memoryview(JsRef id)
{
PyObject* jsproxy = JsProxy_create(id);
if (jsproxy == NULL) {
return NULL;
}
return PyMemoryView_FromObject(jsproxy);
}

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,9 @@
PyObject*
JsProxy_create(JsRef v);
PyObject*
JsProxy_create_with_this(JsRef object, JsRef this);
/** Check if a Python object is a JsProxy object.
* \param x The Python object
* \return true if the object is a JsProxy object.

View File

@ -231,7 +231,8 @@ _python2js_bytes(PyObject* x)
// and PySequence_Check returns 1 for classes with a __getitem__ method that
// don't subclass dict. For this reason, I think we should stick to subclasses.
/** WARNING: This function is not suitable for fallbacks. If this function
/**
* WARNING: This function is not suitable for fallbacks. If this function
* returns NULL, we must assume that the cache has been corrupted and bail out.
*/
static JsRef
@ -266,7 +267,8 @@ finally:
return jsarray;
}
/** WARNING: This function is not suitable for fallbacks. If this function
/**
* WARNING: This function is not suitable for fallbacks. If this function
* returns NULL, we must assume that the cache has been corrupted and bail out.
*/
static JsRef

View File

@ -2,7 +2,7 @@
import platform
if platform.system() == "Emscripten":
from _pyodide_core import JsProxy, JsMethod, JsException, JsBuffer
from _pyodide_core import JsProxy, JsException, JsBuffer
else:
# Can add shims here if we are so inclined.
class JsException(Exception):
@ -17,15 +17,10 @@ else:
# Defined in jsproxy.c
class JsMethod:
"""A proxy to make it possible to call Javascript bound methods from Python."""
# Defined in jsproxy.c
class JsBuffer:
"""A proxy to make it possible to call Javascript typed arrays from Python."""
# Defined in jsproxy.c
__all__ = [JsProxy, JsMethod, JsException]
__all__ = [JsProxy, JsException]

View File

@ -22,7 +22,6 @@ def test_jsproxy_dir(selenium):
"__defineGetter__",
"__defineSetter__",
"__delattr__",
"__delitem__",
"constructor",
"toString",
"typeof",
@ -88,28 +87,25 @@ def test_jsproxy(selenium):
assert (
selenium.run(
"""
from js import TEST
del TEST.y
hasattr(TEST, 'y')"""
from js import TEST
del TEST.y
hasattr(TEST, 'y')
"""
)
is False
)
selenium.run_js(
"""
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
window.TEST = new Point(42, 43);"""
window.TEST = new Map([["x", 42], ["y", 43]]);
"""
)
assert (
selenium.run(
"""
from js import TEST
del TEST['y']
'y' in TEST"""
from js import TEST
del TEST['y']
'y' in TEST
"""
)
is False
)
@ -134,7 +130,7 @@ def test_jsproxy(selenium):
selenium.run(
"""
from js import TEST
dict(TEST) == {'foo': 'bar', 'baz': 'bap'}
dict(TEST.object_entries()) == {'foo': 'bar', 'baz': 'bap'}
"""
)
is True
@ -440,12 +436,11 @@ def test_unregister_jsmodule(selenium):
pyodide.registerJsModule("a", a);
pyodide.registerJsModule("a", b);
pyodide.unregisterJsModule("a")
pyodide.runPython(`
try:
await pyodide.runPythonAsync(`
from unittest import TestCase
raises = TestCase().assertRaises
with raises(ImportError):
import a
assert False
except ImportError:
pass
`)
"""
)
@ -508,3 +503,214 @@ def test_register_jsmodule_docs_example(selenium):
assert c == 2
"""
)
def test_mixins_feature_presence(selenium):
result = selenium.run_js(
"""
let fields = [
[{ [Symbol.iterator](){} }, "__iter__"],
[{ next(){} }, "__next__", "__iter__"],
[{ length : 1 }, "__len__"],
[{ get(){} }, "__getitem__"],
[{ set(){} }, "__setitem__", "__delitem__"],
[{ has(){} }, "__contains__"],
[{ then(){} }, "__await__"]
];
let test_object = pyodide.runPython(`
from js import console
def test_object(obj, keys_expected):
for [key, expected_val] in keys_expected.object_entries():
actual_val = hasattr(obj, key)
if actual_val != expected_val:
console.log(obj)
console.log(key)
console.log(actual_val)
assert False
test_object
`);
for(let flags = 0; flags < (1 << fields.length); flags ++){
let o = {};
let keys_expected = {};
for(let [idx, [obj, ...keys]] of fields.entries()){
if(flags & (1<<idx)){
Object.assign(o, obj);
}
for(let key of keys){
keys_expected[key] = keys_expected[key] || !!(flags & (1<<idx));
}
}
test_object(o, keys_expected);
}
"""
)
def test_mixins_calls(selenium):
result = selenium.run_js(
"""
window.testObjects = {};
testObjects.iterable = { *[Symbol.iterator](){
yield 3; yield 5; yield 7;
} };
testObjects.iterator = testObjects.iterable[Symbol.iterator]();
testObjects.has_len1 = { length : 7, size : 10 };
testObjects.has_len2 = { length : 7 };
testObjects.has_get = { get(x){ return x; } };
testObjects.has_getset = new Map();
testObjects.has_has = { has(x){ return typeof(x) === "string" && x.startsWith("x") } };
testObjects.has_includes = { includes(x){ return typeof(x) === "string" && x.startsWith("a") } };
testObjects.has_has_includes = {
includes(x){ return typeof(x) === "string" && x.startsWith("a") },
has(x){ return typeof(x) === "string" && x.startsWith("x") }
};
testObjects.awaitable = { then(cb){ cb(7); } };
let result = await pyodide.runPythonAsync(`
from js import testObjects as obj
result = []
result.append(["iterable1", list(iter(obj.iterable)), [3, 5, 7]])
result.append(["iterable2", [*obj.iterable], [3, 5, 7]])
it = obj.iterator
result.append(["iterator", [next(it), next(it), next(it)], [3, 5, 7]])
result.append(["has_len1", len(obj.has_len1), 10])
result.append(["has_len2", len(obj.has_len2), 7])
result.append(["has_get1", obj.has_get[10], 10])
result.append(["has_get2", obj.has_get[11], 11])
m = obj.has_getset
m[1] = 6
m[2] = 77
m[3] = 9
m[2] = 5
del m[3]
result.append(["has_getset", [x.to_py() for x in m.entries()], [[1, 6], [2, 5]]])
result.append(["has_has", [n in obj.has_has for n in ["x9", "a9"]], [True, False]])
result.append(["has_includes", [n in obj.has_includes for n in ["x9", "a9"]], [False, True]])
result.append(["has_has_includes", [n in obj.has_has_includes for n in ["x9", "a9"]], [True, False]])
result.append(["awaitable", await obj.awaitable, 7])
result
`);
return result.toJs();
"""
)
for [desc, a, b] in result:
assert a == b, desc
def test_mixins_errors(selenium):
selenium.run_js(
"""
window.a = [];
window.b = {
has(){ return false; },
get(){ return undefined; },
set(){ return false; },
delete(){ return false; },
};
await pyodide.runPythonAsync(`
from unittest import TestCase
raises = TestCase().assertRaises
from js import a, b
with raises(IndexError):
a[0]
with raises(IndexError):
del a[0]
with raises(KeyError):
b[0]
with raises(KeyError):
del b[0]
`);
window.c = {
next(){},
length : 1,
get(){},
set(){},
has(){},
then(){}
};
window.d = {
[Symbol.iterator](){},
};
pyodide.runPython("from js import c, d");
delete c.next;
delete c.length;
delete c.get;
delete c.set;
delete c.has;
delete c.then;
delete d[Symbol.iterator];
await pyodide.runPythonAsync(`
from contextlib import contextmanager
from unittest import TestCase
@contextmanager
def raises(exc, match=None):
with TestCase().assertRaisesRegex(exc, match) as e:
yield e
from pyodide import JsException
msg = "^TypeError:.* is not a function$"
with raises(JsException, match=msg):
next(c)
with raises(JsException, match=msg):
iter(d)
with raises(TypeError, match="object does not have a valid length"):
len(c)
with raises(JsException, match=msg):
c[0]
with raises(JsException, match=msg):
c[0] = 7
with raises(JsException, match=msg):
del c[0]
with raises(TypeError, match="can't be used in 'await' expression"):
await c
`);
window.l = [0, false, NaN, undefined, null];
window.l[6] = 7;
await pyodide.runPythonAsync(`
from unittest import TestCase
raises = TestCase().assertRaises
from js import l
with raises(IndexError):
l[10]
with raises(IndexError):
l[5]
assert len(l) == 7
l[0]; l[1]; l[2]; l[3]
l[4]; l[6]
del l[1]
with raises(IndexError):
l[4]
l[5]
del l[4]
l[3]; l[4]
`);
window.l = [0, false, NaN, undefined, null];
window.l[6] = 7;
let a = Array.from(window.l.entries());
console.log(a);
a.splice(5, 1);
window.m = new Map(a);
console.log(m.size);
await pyodide.runPythonAsync(`
from js import m
from unittest import TestCase
raises = TestCase().assertRaises
with raises(KeyError):
m[10]
with raises(KeyError):
m[5]
assert len(m) == 6
m[0]; m[1]; m[2]; m[3]
m[4]; m[6]
del m[1]
with raises(KeyError):
m[1]
assert len(m) == 5
`);
"""
)

View File

@ -182,10 +182,12 @@ def test_hiwire_is_promise(selenium):
"new Map()",
"new Set()",
]:
assert not selenium.run_js(f"return pyodide._module.hiwire.isPromise({s})")
assert selenium.run_js(
f"return pyodide._module.hiwire.isPromise({s}) === false;"
)
assert selenium.run_js(
"return pyodide._module.hiwire.isPromise(Promise.resolve());"
"return pyodide._module.hiwire.isPromise(Promise.resolve()) === true;"
)
assert selenium.run_js(

View File

@ -158,12 +158,12 @@ def test_eval_nothing(selenium):
def test_unknown_attribute(selenium):
selenium.run(
selenium.run_async(
"""
from unittest import TestCase
raises = TestCase().assertRaisesRegex
import js
try:
with raises(AttributeError, "asdf"):
js.asdf
except AttributeError as e:
assert "asdf" in str(e)
"""
)

View File

@ -55,6 +55,8 @@ def test_capture_exception(selenium):
run_with_resolve(
selenium,
"""
from unittest import TestCase
raises = TestCase().assertRaises
from js import resolve
class MyException(Exception):
pass
@ -62,12 +64,9 @@ def test_capture_exception(selenium):
raise MyException('oops')
def capture_exception(fut):
try:
with raises(MyException):
fut.result()
except MyException:
resolve()
else:
raise Exception("Expected fut.result() to raise MyException")
resolve()
import asyncio
fut = asyncio.ensure_future(foo(998))
fut.add_done_callback(capture_exception)
@ -131,16 +130,15 @@ def test_asyncio_exception(selenium):
run_with_resolve(
selenium,
"""
from unittest import TestCase
raises = TestCase().assertRaises
from js import resolve
async def dummy_task():
raise ValueError("oops!")
async def capture_exception():
try:
with raises(ValueError):
await dummy_task()
except ValueError:
resolve()
else:
raise Exception("Expected ValueError")
resolve()
import asyncio
asyncio.ensure_future(capture_exception())
""",