feat: handle python input synchronously (#52526)

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Oliver Eyton-Williams
2023-12-18 20:22:26 +01:00
committed by GitHub
parent 8e457e7789
commit 583745e6ca
24 changed files with 760 additions and 660 deletions

View File

@@ -0,0 +1,81 @@
// We have to specify pyodide.js because we need to import that file (not .mjs)
// and 'import' defaults to .mjs
import { loadPyodide, type PyodideInterface } from 'pyodide/pyodide.js';
import pkg from 'pyodide/package.json';
const ctx: Worker & typeof globalThis = self as unknown as Worker &
typeof globalThis;
let pyodide: PyodideInterface;
interface PythonRunEvent extends MessageEvent {
data: {
code: {
contents: string;
editableContents: string;
original: { [id: string]: string };
};
};
}
async function setupPyodide() {
if (pyodide) return pyodide;
pyodide = await loadPyodide({
// TODO: host this ourselves
indexURL: `https://cdn.jsdelivr.net/pyodide/v${pkg.version}/full/`
});
// We freeze this to prevent learners from getting the worker into a
// weird state. NOTE: this has to come after pyodide is loaded, because
// pyodide modifies self while loading.
Object.freeze(self);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const str = pyodide.globals.get('str') as (x: unknown) => string;
function print(...args: unknown[]) {
const text = args.map(x => str(x)).join(' ');
postMessage({ type: 'print', text });
}
function input(text: string) {
// TODO: send unique ids to the main thread and the service worker, so we
// can have multiple concurrent input requests.
postMessage({ type: 'input', text });
const request = new XMLHttpRequest();
request.open('POST', '/python/intercept-input/', false);
request.send(null);
return request.responseText;
}
// I tried setting jsglobals here, to provide 'input' and 'print' to python,
// without having to modify the global window object. However, it didn't work
// because pyodide needs access to that object. Instead, I used
// registerJsModule when setting up runPython.
// Make print available to python
pyodide.registerJsModule('jscustom', {
print,
input
});
// TODO: use a fresh global object for each runPython call if we stop terminating
// the worker when the user input changes. (See python-test-evaluator.ts)
pyodide.runPython(`
import jscustom
from jscustom import print
from jscustom import input
`);
return pyodide;
}
void setupPyodide();
ctx.onmessage = async (e: PythonRunEvent) => {
const code = (e.data.code.contents || '').slice();
const pyodide = await setupPyodide();
// use pyodide.runPythonAsync if we want top-level await
pyodide.runPython(code);
};