|
'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 }; |
|
|
|
|
|
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 |
|
) { |
|
|
|
overrideFunction = false; |
|
|
|
let proxy = proxies.get(value)?.deref(); |
|
if (!proxy) { |
|
try { |
|
|
|
proxy = create(value.copy(), onGC); |
|
proxies.set(value, new WeakRef(proxy)); |
|
} |
|
catch (error) { |
|
console.error(error); |
|
} |
|
} |
|
if (proxy) args[i] = proxy; |
|
overrideFunction = true; |
|
} |
|
} |
|
}; |
|
|
|
|
|
const { call } = Function; |
|
const apply = call.bind(call, call.apply); |
|
|
|
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); |
|
} |
|
} |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
module.exports = { |
|
type, |
|
module: (version = '0.25.1') => |
|
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`, |
|
async engine({ loadPyodide }, config, url) { |
|
|
|
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); |
|
}, |
|
}; |
|
|
|
|
|
async function importPackages(packages) { |
|
await this.loadPackage('micropip'); |
|
const micropip = this.pyimport('micropip'); |
|
await micropip.install(packages, { keep_going: true }); |
|
micropip.destroy(); |
|
} |
|
|
|
|