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 */