File size: 4,379 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 130 |
'use strict';
const { create } = require('gc-hook');
const { RUNNING_IN_WORKER, fetchFiles, fetchJSModules, fetchPaths, writeFile } = require('./_utils.js');
const { getFormat, loader, registerJSModule, run, runAsync, runEvent } = require('./_python.js');
const { stdio } = require('./_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 */
module.exports = {
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 */
|