starboard-python
Version:
Python cells for Starboard Notebook
129 lines (113 loc) • 4.38 kB
text/typescript
import type { Runtime } from "../../starboard-notebook/src/types";
import { flatPromise } from "./flatPromise";
import {
loadPyodide,
runPythonAsync,
setGlobalPythonOutputElement as setGlobalPythonHtmlOutputElement,
setupPythonSupport,
} from "./global";
// This is used as a mutex to prevent double execution.
let pythonRunChain: Promise<any> = Promise.resolve();
const isPyProxy = function (jsobj: any) {
return !!jsobj && jsobj.$$ !== undefined && jsobj.$$.type === "PyProxy";
};
export async function runStarboardPython(
runtime: Runtime,
codeToRun: string,
renderOutputIntoElement: HTMLElement
): Promise<any> {
setupPythonSupport();
const pyoPromise = loadPyodide(runtime);
const done = flatPromise();
const alreadyRunningPythonCodeDone = pythonRunChain.catch((_) => 0);
pythonRunChain = pythonRunChain.finally(() => done.promise);
// await any already executing python cells.
await alreadyRunningPythonCodeDone;
const outputElement = new runtime.exports.elements.ConsoleOutputElement();
outputElement.hook(runtime.consoleCatcher);
const htmlOutput = document.createElement("div");
const lit = runtime.exports.libraries.lit;
const html = lit.html;
lit.render(html`${outputElement}${htmlOutput}`, renderOutputIntoElement);
setGlobalPythonHtmlOutputElement(htmlOutput);
(globalThis as any).pyodide = await pyoPromise;
let val = undefined;
let error: any = undefined;
try {
pythonRunChain = runPythonAsync(codeToRun, runtime);
val = await pythonRunChain;
if (val !== undefined) {
// The result can be multiple types
if (val instanceof HTMLElement) { // A plain HTML element
htmlOutput.appendChild(val);
} else if (typeof val === "object" && val.name === "PythonError" && val.__error_address) { // A python error
error = val;
outputElement.addEntry({
method: "error",
data: [`${val.toString()}`],
});
}
else if (isPyProxy(val)) { // Something that is proxied
let hadHTMLOutput = false;
if (val._repr_html_ !== undefined) { // Has a HTML representation (e.g. a Pandas table)
let result = val._repr_html_();
if (typeof result === "string") {
let div = document.createElement("div");
div.className = "rendered_html cell-output-html";
div.appendChild(new DOMParser().parseFromString(result, "text/html").body.firstChild as any);
htmlOutput.appendChild(div);
hadHTMLOutput = true;
}
} else if (val._repr_latex_ !== undefined) { // It has a LateX representation (e.g. Sympy output)
let result = val._repr_latex_();
if (typeof result === "string") {
let div = document.createElement("div");
div.className = "rendered_html cell-output-html";
const katex = await runtime.exports.libraries.async.KaTeX();
if (result.startsWith("$$")) {
result = result.substr(2, result.length - 3);
katex.render(result, div, {
throwOnError: false,
errorColor: " #cc0000",
displayMode: true,
});
} else if (result.startsWith("$")) {
result = result.substr(1, result.length - 2);
katex.render(result, div, {
throwOnError: false,
errorColor: " #cc0000",
displayMode: false,
});
}
htmlOutput.appendChild(div);
hadHTMLOutput = true;
}
}
if (!hadHTMLOutput) {
outputElement.addEntry({
method: "result",
data: [val],
});
}
} else {
outputElement.addEntry({
method: "result",
data: [val],
});
}
}
} catch (e: any) {
error = e;
outputElement.addEntry({
method: "error",
data: [`${e.name} ${e.message}`],
});
}
// Not entirely sure this has to be awaited, is any output delayed by a tick from pyodide?
await outputElement.unhookAfterOneTick(runtime.consoleCatcher);
done.resolve();
if (error !== undefined) {
throw error;
}
return val;
}