File size: 4,343 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
import { create } from 'gc-hook';
import { RUNNING_IN_WORKER, fetchFiles, fetchJSModules, fetchPaths, writeFile } from './_utils.js';
import { getFormat, loader, registerJSModule, run, runAsync, runEvent } from './_python.js';
import { stdio } from './_io.js';
const type = 'pyodide';
const toJsOptions = { dict_converter: Object.fromEntries };
/* c8 ignore start */
let overrideFunction = false;
const overrideMethod = method => (...args) => {
try {
overrideFunction = true;
return method(...args);
}
finally {
overrideFunction = false;
}
};
let overridden = false;
const applyOverride = () => {
if (overridden) return;
overridden = true;
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);
}
}
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, {
apply: {
value(context, args) {
if (overrideFunction) patchArgs(args);
return apply(this, context, args);
}
},
call: {
value(context, ...args) {
if (overrideFunction) patchArgs(args);
return apply(this, context, args);
}
}
});
};
/* c8 ignore stop */
// REQUIRES INTEGRATION TEST
/* c8 ignore start */
export default {
type,
module: (version = '0.25.1') =>
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
async engine({ loadPyodide }, config, url) {
// apply override ASAP then load foreign code
if (!RUNNING_IN_WORKER && config.experimental_create_proxy === 'auto')
applyOverride();
const { stderr, stdout, get } = stdio();
const indexURL = url.slice(0, url.lastIndexOf('/'));
const interpreter = await get(
loadPyodide({ stderr, stdout, indexURL }),
);
const py_imports = importPackages.bind(interpreter);
loader.set(interpreter, py_imports);
if (config.files) await fetchFiles(this, interpreter, config.files);
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
if (config.js_modules) await fetchJSModules(config.js_modules);
if (config.packages) await py_imports(config.packages);
return interpreter;
},
registerJSModule,
run: overrideMethod(run),
runAsync: overrideMethod(runAsync),
runEvent: overrideMethod(runEvent),
transform: ({ ffi: { PyProxy } }, value) => (
value instanceof PyProxy ?
value.toJs(toJsOptions) :
value
),
writeFile: (interpreter, path, buffer, url) => {
const format = getFormat(path, url);
if (format) {
return interpreter.unpackArchive(buffer, format, {
extractDir: path.slice(0, -1)
});
}
const { FS, PATH, _module: { PATH_FS } } = interpreter;
return writeFile({ FS, PATH, PATH_FS }, path, buffer);
},
};
// exposed utility to import packages via polyscript.lazy_py_modules
async function importPackages(packages) {
await this.loadPackage('micropip');
const micropip = this.pyimport('micropip');
await micropip.install(packages, { keep_going: true });
micropip.destroy();
}
/* c8 ignore stop */
|