Skip to content

Commit

Permalink
Add JsProxy bindings system (#4699)
Browse files Browse the repository at this point in the history
We've gotten a lot of complaints about how hard it is to call certain JS
functions, but it's hard to do much to improve the situation without knowing
anything about the specific function being called.

However, if we know about the API, we can choose how to convert to/from JS as
appropriate and make things a lot easier. This adds a method to JsProxy called
`bind_sig` which adjusts how __getattr__ and __call__ work on the proxy so we
can choose our converters. I added a class called JsSignature which
guides what we do when we call the function. I completely rewrote
JsProxy_Vectorcall to use this. I added handling for Promise results so we can
automatically bind a signature to the result of the Promise.

There's a lot leftover to do:
* Generator and AsyncGenerator types
* Alternatives A | B (try A converter and if it raises a TypeError fall back to B)
* TypedDict converters
* Make calling certain objects call `Reflect.construct`
* Cleaner __getattr__ handling, __setattr__ handling
* A way to raise AttributeError if the attribute is missing from the signature
  even if it's present in the JS object (important for backwards compatibility)
* Signatures for JS stdlib functions

My idea is that the signatures should simultaneously function as mypy typehints
as much as this is practical.
  • Loading branch information
hoodmane committed May 9, 2024
1 parent 7c6f671 commit 902453a
Show file tree
Hide file tree
Showing 16 changed files with 1,724 additions and 95 deletions.
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -96,6 +96,7 @@ src/core/libpyodide.a: \
src/core/pyproxy.o \
src/core/python2js_buffer.o \
src/core/jslib.o \
src/core/jsbind.o \
src/core/jslib_asm.o \
src/core/python2js.o \
src/core/pyodide_pre.o \
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
@@ -1,5 +1,5 @@
[tool.mypy]
python_version = "3.11"
python_version = "3.12"
mypy_path = ["src/py", "pyodide-build"]
show_error_codes = true
warn_unreachable = true
Expand Down Expand Up @@ -62,7 +62,7 @@ lint.select = [
"PLE", # pylint errors
"UP", # pyupgrade
]
lint.ignore = ["E402", "E501", "E731", "E741"]
lint.ignore = ["E402", "E501", "E731", "E741", "UP038"]
# line-length = 219 # E501: Recommended goal is 88 to match black
target-version = "py311"

Expand Down Expand Up @@ -110,6 +110,7 @@ norecursedirs = [
addopts = '''
--doctest-modules
--ignore-glob="**/dist/"
--ignore-glob="src/py/_pyodide/jsbind.py"
--ignore-glob="packages/**/extras/"
--tb=short
--dist-dir=dist'''
Expand Down
12 changes: 7 additions & 5 deletions src/core/_pyodide_core.c
Expand Up @@ -102,18 +102,20 @@ PyInit__pyodide_core(void)
if (core_module == NULL) {
FATAL_ERROR("Failed to create core module.");
}
PyObject* module_dict = PyImport_GetModuleDict(); /* borrowed */
if (PyDict_SetItemString(module_dict, "_pyodide_core", core_module)) {
FATAL_ERROR("Failed to add '_pyodide_core' module to modules dict.");
FAIL();
}

TRY_INIT_WITH_CORE_MODULE(error_handling);
TRY_INIT(jslib);
TRY_INIT(docstring);
TRY_INIT_WITH_CORE_MODULE(python2js);
TRY_INIT_WITH_CORE_MODULE(jsproxy);
TRY_INIT_WITH_CORE_MODULE(jsproxy_call);
TRY_INIT_WITH_CORE_MODULE(pyproxy);

PyObject* module_dict = PyImport_GetModuleDict(); /* borrowed */
if (PyDict_SetItemString(module_dict, "_pyodide_core", core_module)) {
FATAL_ERROR("Failed to add '_pyodide_core' module to modules dict.");
}
TRY_INIT_WITH_CORE_MODULE(jsbind);

if (init_pyodide_proxy() == -1) {
FATAL_ERROR("Failed to create _pyodide proxy.");
Expand Down
2 changes: 1 addition & 1 deletion src/core/js2python.js
Expand Up @@ -296,7 +296,7 @@ function js2python_convert_with_context(value, context) {
if (result !== undefined) {
return result;
}
if (context.defaultConverter === undefined) {
if (!context.defaultConverter) {
return _JsProxy_create(value);
}
let result_js = context.defaultConverter(
Expand Down

0 comments on commit 902453a

Please sign in to comment.