Skip to content

python literals via pyodide #293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bin/resolve-dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ const mains = ["unpkg", "jsdelivr", "browser", "main"];
const package = await resolve("leaflet");
console.log(`export const leaflet = dependency("${package.name}", "${package.version}", "${package.export.replace(/-src\.js$/, ".js")}");`);
}
{
const package = await resolve("pyodide");
console.log(`export const pyodide = "https://cdn.jsdelivr.net/pyodide/v${package.version}/full/pyodide.js";`);
}
})();

async function resolve(specifier) {
Expand Down
1 change: 1 addition & 0 deletions src/dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export const topojson = dependency("topojson-client", "3.1.0", "dist/topojson-cl
export const exceljs = dependency("exceljs", "4.3.0", "dist/exceljs.min.js");
export const mermaid = dependency("mermaid", "9.1.1", "dist/mermaid.min.js");
export const leaflet = dependency("leaflet", "1.8.0", "dist/leaflet.js");
export const pyodide = "https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js";
2 changes: 2 additions & 0 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import mermaid from "./mermaid.js";
import Mutable from "./mutable.js";
import now from "./now.js";
import Promises from "./promises/index.js";
import py from "./py.js";
import resolve from "./resolve.js";
import requirer, {requireDefault, setDefaultRequire} from "./require.js";
import SQLite, {SQLiteDatabaseClient} from "./sqlite.js";
Expand Down Expand Up @@ -44,6 +45,7 @@ export default Object.assign(Object.defineProperties(function Library(resolver)
Inputs: () => require(inputs.resolve()).then(Inputs => ({...Inputs, file: Inputs.fileOf(AbstractFile)})),
L: () => leaflet(require),
mermaid: () => mermaid(require),
py: () => py(require),
Plot: () => require(plot.resolve()),
require: () => require,
resolve: () => resolve, // deprecated; use async require.resolve instead
Expand Down
68 changes: 68 additions & 0 deletions src/py.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {pyodide as Pyodide} from "./dependencies.js";

export default async function py(require) {
const pyodide = await (await require(Pyodide)).loadPyodide();
let patch; // a promise for patching matplotlib (if needed)
return async function py(strings) {
const globals = {};
let code = strings[0];
for (let i = 1, n = arguments.length; i < n; ++i) {
const name = `_${i}`;
globals[name] = arguments[i];
code += name + strings[i];
}
const imports = findImports(pyodide, code);
if (imports.includes("matplotlib") && !patch) await (patch = patchMatplotlib(require, pyodide));
if (imports.length) await pyodide.loadPackagesFromImports(code);
const value = await pyodide.runPythonAsync(code, {globals: pyodide.toPy(globals)});
return pyodide.isPyProxy(value) ? value.toJs() : value;
};
}

// https://github.com/pyodide/pyodide/blob/1624e4a62445876a2d810fdbfc9ddb69a8321a8e/src/js/api.ts#L119-L125
function findImports(pyodide, code) {
const imports = pyodide.pyodide_py.find_imports(code);
try {
return imports.toJs();
} finally {
imports.destroy();
}
}

// Overrides matplotlib’s show function to return a DIV such that when used as
// the last expression in an Observable cell, the inspector will display it.
async function patchMatplotlib(require, pyodide) {
require.resolve("[email protected]/css/font-awesome.min.css").then(href => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
document.head.appendChild(link);
});
await pyodide.loadPackage("matplotlib");
await pyodide.runPythonAsync(`from matplotlib import pyplot as plt
from js import document

_show = plt.show

def create_root_element(self):
div = document.createElement("div")
document.body.appendChild(div)
return div

def show(self):
f = plt.gcf()
c = f.canvas
c.create_root_element = create_root_element.__get__(c, c.__class__)
_show()
plt.close(f)
top = c.get_element("top")
if (top):
top.remove()
div = c.get_element("")
if (div.parentNode == document.body):
div.remove()
return div

plt.show = show.__get__(plt, plt.__class__)
`);
}
1 change: 1 addition & 0 deletions test/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ test("new Library returns a library with the expected keys", async t => {
"now",
"olympians",
"penguins",
"py",
"require",
"resolve",
"svg",
Expand Down