8000 Better experimental_create_proxy by WebReflection · Pull Request #134 · pyscript/polyscript · GitHub
[go: up one dir, main page]

Skip to content

Better experimental_create_proxy #134

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

Merged
merged 6 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/index.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/zip-1TWXRld0.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/zip-1TWXRld0.js.map

Large diffs are not rendered by default.

99 changes: 50 additions & 49 deletions esm/interpreter/pyodide.js
8000
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { create } from 'gc-hook';

import { RUNNING_IN_WORKER, createProgress, writeFile } from './_utils.js';
import { createProgress, writeFile } from './_utils.js';
import { getFormat, loader, loadProgress, registerJSModule, run, runAsync, runEvent } from './_python.js';
import { stdio } from './_io.js';
import { IDBMapSync, isArray, fixedRelative } from '../utils.js';
Expand All @@ -10,67 +8,56 @@ const toJsOptions = { dict_converter: Object.fromEntries };

const { stringify } = JSON;

const { apply } = Reflect;
const FunctionPrototype = Function.prototype;

// REQUIRES INTEGRATION TEST
/* c8 ignore start */
let overrideFunction = false;
const overrideMethod = method => (...args) => {
try {
overrideFunction = true;
return method(...args);
}
finally {
overrideFunction = false;
}
const overrideMethod = method => function (...args) {
return apply(method, this, args);
};

let overridden = false;
const applyOverride = () => {
if (overridden) return;
overridden = true;
let pyproxy, to_js;
const override = intercept => {

const proxies = new WeakMap;
const onGC = value => value.destroy();
const patchArgs = args => {
for (let i = 0; i < args.length; i++) {
const value = args[i];
if (
typeof value === 'function' &&
'copy' in value
) {
// avoid seppuku / Harakiri + speed up
overrideFunction = false;
// reuse copied value if known already
let proxy = proxies.get(value)?.deref();
if (!proxy) {
try {
// observe the copy and return a Proxy reference
proxy = create(value.copy(), onGC);
proxies.set(value, new WeakRef(proxy));
}
catch (error) {
console.error(error);

const patch = args => {
for (let arg, i = 0; i < args.length; i++) {
switch (typeof(arg = args[i])) {
case 'object':
if (arg === null) break;
// falls through
case 'function': {
if (pyproxy in arg && !arg[pyproxy].shared?.gcRegistered) {
intercept = false;
let proxy = proxies.get(arg)?.deref();
if (!proxy) {
proxy = to_js(arg);
const wr = new WeakRef(proxy);
proxies.set(arg, wr);
proxies.set(proxy, wr);
}
args[i] = proxy;
intercept = true;
}
break;
}
if (proxy) args[i] = proxy;
overrideFunction = true;
}
}
};

// trap apply to make call possible after the patch
const { call } = Function;
const apply = call.bind(call, call.apply);
// the patch
Object.defineProperties(Function.prototype, {
Object.defineProperties(FunctionPrototype, {
apply: {
value(context, args) {
if (overrideFunction) patchArgs(args);
if (intercept) patch(args);
return apply(this, context, args);
}
},
call: {
value(context, ...args) {
if (overrideFunction) patchArgs(args);
if (intercept) patch(args);
return apply(this, context, args);
}
}
Expand All @@ -82,12 +69,9 @@ const indexURLs = new WeakMap();

export default {
type,
module: (version = '0.27.5') =>
module: (version = '0.27.6') =>
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
async engine({ loadPyodide }, config, url, baseURL) {
// apply override ASAP then load foreign code
if (!RUNNING_IN_WORKER && config.experimental_create_proxy === 'auto')
applyOverride();
progress('Loading Pyodide');
let { packages, index_urls } = config;
if (packages) packages = packages.map(fixedRelative, baseURL);
Expand Down Expand Up @@ -134,13 +118,30 @@ export default {
await storage.close();
if (options.lockFileURL) URL.revokeObjectURL(options.lockFileURL);
progress('Loaded Pyodide');
if (config.experimental_create_proxy === 'auto') {
interpreter.runPython([
'import js',
'from pyodide.ffi import to_js',
'o=js.Object.fromEntries',
'js.experimental_create_proxy=lambda r:to_js(r,dict_converter=o)'
].join(';'), { globals: interpreter.toPy({}) });
to_js = globalThis.experimental_create_proxy;
delete globalThis.experimental_create_proxy;
[pyproxy] = Reflect.ownKeys(to_js).filter(
k => (
typeof k === 'symbol' &&
String(k) === 'Symbol(pyproxy.attrs)'
)
);
override(true);
}
return interpreter;
},
registerJSModule,
run: overrideMethod(run),
runAsync: overrideMethod(runAsync),
runEvent: overrideMethod(runEvent),
transform: (interpreter, value) => transform.call(interpreter, value),
transform: (interpreter, value) => apply(transform, interpreter, [value]),
writeFile: (interpreter, path, buffer, url) => {
const format = getFormat(path, url);
if (format) {
Expand Down
2 changes: 1 addition & 1 deletion esm/interpreter/webr.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const run = async (interpreter, code) => {
export default {
type,
experimental: true,
module: (version = '0.4.2') =>
module: (version = '0.4.3') =>
`https://cdn.jsdelivr.net/npm/webr@${version}/dist/webr.mjs`,
async engine(module, config, _, baseURL) {
const { get } = stdio();
Expand Down
2 changes: 1 addition & 1 deletion node.importmap
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"imports": {
"http://pyodide": "./test/mocked/pyodide.mjs",
"https://cdn.jsdelivr.net/pyodide/v0.27.5/full/pyodide.mjs": "./test/mocked/pyodide.mjs",
"https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.mjs": "./test/mocked/pyodide.mjs",
"https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@1.25.0/micropython.mjs": "./test/mocked/micropython.mjs",
"./3rd-party/toml.js": "./test/mocked/toml.mjs"
}
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "polyscript",
"version": "0.17.15",
"version": "0.17.18",
"description": "PyScript single core to rule them all",
"main": "./esm/index.js",
"types": "./types/polyscript/esm/index.d.ts",
Expand Down Expand Up @@ -50,7 +50,7 @@
"@playwright/test": "^1.52.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@zip.js/zip.js": "^2.7.60",
"@zip.js/zip.js": "^2.7.61",
"c8": "^10.1.3",
"chokidar": "^4.0.3",
"eslint": "^9.26.0",
Expand Down Expand Up @@ -88,14 +88,14 @@
"@webreflection/utils": "^0.1.0",
"basic-devtools": "^0.1.6",
"codedent": "^0.1.2",
"coincident": "^3.0.4",
"coincident": "^3.0.5",
"gc-hook": "^0.4.1",
"html-escaper": "^3.0.3",
"proxy-target": "^3.0.2",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1"
},
"worker": {
"blob": "sha256-xlgyKcR1rQj2E2TB4Pp07IZjwgkIyN9uf8HfcuUXwro="
"blob": "sha256-O+HvjdLH9ZO5EJ2AhdtN4bWQt6Cy5lFI1uvUhA7mP1M="
}
}
Loading
0