UNPKG

starboard-python

Version:
129 lines (113 loc) 4.38 kB
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; }