-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
webassembly: Add proxying between Python and JavaScript objects, and change top-level interface for PyScript use #13583
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
Conversation
@ntoll @WebReflection FYI |
Code size report:
|
8ef716d
to
a6c9e62
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #13583 +/- ##
=======================================
Coverage 98.39% 98.39%
=======================================
Files 161 161
Lines 21200 21200
=======================================
Hits 20860 20860
Misses 340 340 ☔ View full report in Codecov by Sentry. |
a6c9e62
to
ab00c8b
Compare
//console.debug("APPLY", target, argumentsList); | ||
let args = 0; | ||
|
||
// TODO: is this the correct thing to do, strip trailing "undefined" args? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while (argumentList.length && argumentList.at(-1) === void 0) // or undefined
argumentList.pop();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alternatively ...
let { length } = argumentList;
while (length && argumentList[length - 1] === undefined) length--;
// drop all at once if needed
if (length !== argumentList.length)
argumentList.splice(length);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... or even terser ...
let { length } = argumentList;
while (length-- && argumentList[length] === undefined);
// drop all at once if needed
if (++length !== argumentList.length)
argumentList.splice(length);
you pick whatever you prefer :-)
return true; | ||
}, | ||
has(target, prop) { | ||
throw Error("has not implemented"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would still throw for "test" in pyObject
brand checks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a bit confused (but hadn't had my coffee yet ...) ... no change was made to the Proxy traps and this specific error has been reported all over the place ... is this code meant to actually go or something still need to be changed? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sorry, I meant to say that this bit was still to come.
The PR here is the current version that's published to npm.
But, that said, I've just now pushed a commit to implement has
, along with a new test for this feature. Let me know what you think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have a look, thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've no idea where that is though ... 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at the most recent commits, the 4 most recent ones implement PyProxy.has
and PyProxy.ownKeys
and add tests for these.
419ffd1
to
cabe26e
Compare
@@ -0,0 +1,19 @@ | |||
// Test polyfill of a method on a built-in. | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this reminded me I should explicitly put a license in here 🤣
jokes a part, glad my poly helped testing this stuff as we do use it all over the place 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We take licensing/copyright seriously. Should I add anything here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
those 3 LOC aren't anything special and the purpose of that polyfill is to die ASAP (once all browsers support it) so don't worry about it, I haven't invented anything and the pattern is pretty simple too, the only trick there is the usage of var
(on purpose) but that's also just part of the specs.
We're good, and you have the author of that poly stating that in here 👍
beside the terser suggestion to split all at once arguments (usually faster than N pop() but I don't think that's critical path for performance anyway) my only hint, from a JS maintainer point of view, is to somehow normalize the usage of Thanks! |
Yes that would be a good thing to do. What style do you use? Can you recommend a light-weight linter? |
we use eslint but not because we chose it (or I did) because it was there already and it's somehow the industry standard. Although, I think these days I would instead consider quick-lint-js or biome as these are both faster and less opinionated or easier to configure / bootstrap. As this project is used in polyscript I can at least point at how we configured it ... then again, mostly comes from original PyScript config: https://github.com/pyscript/polyscript/blob/main/.eslintrc.json edit P.S. I don't have any strong opinion about any linting rule ... make yourself comfortable with whatever choice you have, my hint was about consistency and an easier way for possible contributors to not make the code look too diverse over time. |
@dpgeorge so I've built latest and things are pretty exciting ... but still there's something missing on the MicroPython side ... assuming the <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MicroPython - Latest</title>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.1.3/core.css">
<script type="module" src="https://pyscript.net/releases/2024.1.3/core.js"></script>
</head>
<body>
<script>
function ownKeys(py_or_js) {
console.log("a" in py_or_js);
console.log(Reflect.ownKeys(py_or_js));
}
</script>
<mpy-config>
interpreter = "http://localhost:8080/micropython.mjs"
</mpy-config>
<script type="mpy" worker>
from pyscript import window
import sys
print(sys.version)
window.ownKeys({ "a": 1 })
# true
# ["length", "a"]
# "length" should not be there but it's OK
</script>
</body>
</html> but as soon as I remove the
Which is somehow expected as I get all the dictionary keys but I don't have a way to convert via Python directly to JS objects, which happens instead automatically with workers, as those references are converted before being proxied around via the Is there any plan or chance that the Thank you! |
e00250f
to
0f0cb62
Compare
@WebReflection I have added formatting and linting using Biome (with indenting set to 4 spaces... at least that matches the Python and C code, and PyScript's style!). But Biome lint is complaining about your promise with resolvers code:
Do you know a good way to fix that? (I already converted the |
0f0cb62
to
c821cbf
Compare
I have now implemented As part of this I added a new
Hopefully that change (removal of |
@WebReflection regarding your test above with In Python you have:
They are quite different although can be confused with each other. If the subject object is a dict, then In the webassembly port at the moment, I implemented the latter, ie Should I instead implement |
@dpgeorge the
Reflect.ownKeys returns keys that belong to the current object, not keys inherited via class or anything else ... this is still about keys. I think |
@dpgeorge sorry for late reply here too ...
edit btw this is why foreign modules should be imported and not copy/pasted ... if imported no linter/tool would bother you ever about foreign code as every code might have different tools behind to produce still valid code your linter or toolchain should not / ever care about. edit 2 let a, b;
const c = new this(function (resolve, reject) {
a = resolve;
b = reject;
});
return {resolve: a, reject: b, promise: c}; This code should work without linting complains ... please let me know if it doesn't. |
fb08e33
to
6f58010
Compare
do you mind publishing it to npm otherwise I can't grab latest automatically from there and current state is not ideal? Thanks! |
c1a82f9
to
557c4ba
Compare
Enabled by MICROPY_COMPILE_ALLOW_TOP_LEVEL_AWAIT. When enabled, this means that scope such as module-level functions and REPL statements can yield. The outer C code must then handle this yielded generator. Signed-off-by: Damien George <damien@micropython.org>
Following other ports. Signed-off-by: Damien George <damien@micropython.org>
This eliminates the need for wrapper.js to run to set up the time. Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
All output is now handled by Emscripten's stdio facility. Signed-off-by: Damien George <damien@micropython.org>
When enabled the GC will not reclaim any memory on a call to `gc_collect()`. Instead it will grow the heap. Signed-off-by: Damien George <damien@micropython.org>
This commit cleans up and generalises the Makefile, adds support for variants (following the unix port) and adds the "standard" variant as the default variant. Signed-off-by: Damien George <damien@micropython.org>
This commit improves the webassembly port by adding: - Proxying of Python objects to JavaScript with a PyProxy type that lives on the JavaScript side. PyProxy implements JavaScript Proxy traps such as has, get, set and ownKeys, to make Python objects have functionality on the JavaScript side. - Proxying of JavaScript objects to Python with a JsProxy type that lives on the Python side. JsProxy passes through calls, attributes, subscription and iteration from Python to JavaScript. - A top-level API on the JavaScript side to construct a MicroPython interpreter instance via `loadMicroPython()`. That function returns an object that can be used to execute Python code, access the Python globals dict, access the Emscripten filesystem, and other things. This API is based on the API provided by Pyodide (https://pyodide.org/). As part of this, the top-level file is changed from `micropython.js` to `micropython.mjs`. - A Python `js` module which can be used to access all JavaScript-side symbols, for example the DOM when run within a browser. - A Python `jsffi` module with various helper functions like `create_proxy()` and `to_js()`. - A dedenting lexer which automatically dedents Python source code if every non-empty line in that source starts with a common whitespace prefix. This is very helpful when Python source code is indented within a string within HTML or JavaScript for formatting reasons. Signed-off-by: Damien George <damien@micropython.org>
With this commit, `interpreter.runPythonAsync(code)` can now be used to run Python code that uses `await` at the top level. That will yield up to JavaScript and produce a thenable, which the JavaScript runtime can then resume. Also implemented is the ability for Python code to await on JavaScript promises/thenables. For example, outer JavaScript code can await on `runPythonAsync(code)` which then runs Python code that does `await js.fetch(url)`. The entire chain of calls will be suspended until the fetch completes. Signed-off-by: Damien George <damien@micropython.org>
This allows running MicroPython webassembly from the command line using: node micropython.mjs Signed-off-by: Damien George <damien@micropython.org>
This is the JavaScript API for starting and running a REPL. Signed-off-by: Damien George <damien@micropython.org>
This commit adds a pyscript variant for use in https://pyscript.net/. The configuration is: - No ASYNCIFY, in order to keep the WASM size down and have good performance. - MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES to enable most features. - Custom manifest that includes many of the python-stdlib libraries. - MICROPY_GC_SPLIT_HEAP_AUTO to increase GC heap size instead of doing a collection when memory is exhausted. This is needed because ASYNCIFY is disabled. Instead the GC collection is run at the top-level before executing any Python code. - No MICROPY_VARIANT_ENABLE_JS_HOOK because there is no asynchronous keyboard input to interrupt a running script. Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
This allows running tests with a .js/.mjs suffix, and also .py tests using node and the webassembly port. Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Signed-off-by: Damien George <damien@micropython.org>
Enable Biome on all of webassembly port and tests. Signed-off-by: Damien George <damien@micropython.org>
9eb2731
to
35b2edf
Compare
This PR extends, improves and changes the webassembly port so that it's suitable for use in PyScript, (https://pyscript.net/, https://github.com/pyscript/pyscript).
The main things in this PR are:
micropython.mjs
module, rather thanmicropython.js
. This module exposes aloadMicroPython()
function which returns an object that can be used to execute Python, access the Python globals dict, and other things.run-tests.py
so it can automatically run webassembly tests via node, both .py tests and .js/.mjs tests.random
andtime
modules on the webassembly port.A lot of the proxying and top-level interface is made to match the API of Pyodide (https://github.com/pyodide/pyodide/).
TODO: