-
-
Notifications
You must be signed in to change notification settings - Fork 888
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
Deno support #3420
Comments
@rth know if this issue is still moving forward? I'm really interested in the Deno server side sandboxing use case as well. |
I experimented with a different direction and it looks really promising! Deno has been adding npm package support so I tried loading pyodide in Deno as a CommonJS module. I had to make a patch to Looks like the But with that patch I can run pyodide in Deno 🎉 One can reproduce it with the current
During debugging in var ENVIRONMENT_IS_NODE = typeof process == 'object' && typeof process.versions == 'object' && typeof process.versions.node == 'string'; 💭 @rth Would y'all be open to changing the My ultimate goal however was to have a full local copy of pyodide I can run sandboxed and I found a partial pattern for that. Fundamentally I wanted to use Deno's npm support to load a local pyodide CommonJS module and several approaches I tried didn't work out:
The workaround I did find does the following:
The import { fromFileUrl } from 'https://deno.land/[email protected]/path/mod.ts';
// Manually load pyodide.asm.js first so it registers itself on the global
// The pyodide.js file tries to load it automatically if it's not on the global
// but the way it does it seems to cause it to run outside of Deno's npm mode
await import("npm:pyodide/pyodide.asm.js");
const {loadPyodide} = await import("npm:pyodide/pyodide.js");
const pyodide = await loadPyodide({
// The full release download of pyodide aligned to the npm imported version
// saved to a directory named pyodide-local
indexURL: fromFileUrl(new URL('./pyodide-local/', import.meta.url).href)
});
await pyodide.loadPackage('micropip');
const micropip = pyodide.pyimport("micropip");
await micropip.install('numpy');
const result = await pyodide.runPythonAsync(`
import numpy as np
a = np.arange(15).reshape(3, 5)
a
`);
console.log(result.toString());
// ⚠️Make sure to use the patches described in the other workflow if they are still needed
// 🎉 Run python in the deno sandbox! 🐍🦖
// deno run --node-modules-dir --allow-read=. main.ts I don't have a super clear direction for removing the workarounds in the local full pyodide workflow. I think getting the CommonJS |
Sure we can change the node detection. We should also really allow the runtime to be controlled explicitly via an argument to loadPyodide... |
That's great @rajsite ! Yes, please make a PR to change it in If you manage to make it run with
Are you sure? The runtime is a property where one runs. IMO it doesn't make sense to allow running in the browser and using the code written for Node. |
Yes, I agree that this would be end goal for most people interested in server sandboxing. But so if you are loading pyodide.js and pyodide.asm.js from npm, doesn't it mean that you need to allow network access to npm and disable the network sanboxing? One can't whitelist URLs as far as I understand?
I think once deno support is merged, we may want to add a separate documentation section on sandboxing. Even if it has such workarounds initially it's probably not a major issue. The other thing people would likely care about is startup time, particularly when compared to other server side wasm solutions. In the case for instance, where one would start a new deno process with Pyodide for each untrusted code chunk one would want to run in a sanbdox. |
Though this still doesn't tell us how to load a locally built copy of pyodide that's not uploaded to npm. If it works when loaded from npm, I imagine it should also work if served from a local server, or is npm loader doing something special? But to load it all from local files, I guess more work would be necessary? |
Tried applying that patch to main and I think the esbuild change is causing Deno to treat the export from import pyodideModule from "npm:pyodide/pyodide.js"; /* local build of main with patch*/
const {loadPyodide} = pyodideModule; I'm not exactly sure if its a Deno-specific issue or if that would be a break in behavior elsewhere. Doesn't seem to have changed node behavior via |
Done! Created a PR.
Sure! I made it a separate Draft PR since there is probably some bike shedding needed and I'll add the docs discussed: #3810
Part of Deno's shtick is that statically analyzable imports can be done safely by the deno process outside of script execution. So all the static imports and the statically analyzable dynamic imports are fetched by Deno outside of the context of the permission sandbox. The permission sandbox is for the actual code that runs doing fetch of network resources and dynamic imports of non-statically analyzable import names. See Integrity Checking and Vendoring Dependencies. I think the integrity checking on npm packages is good today; npm packages show up in the Deno.lock file. But vendoring for npm packages is still on the roadmap. My guess is npm vendoring will just end up looking like the Also in the testing draft PR above notice how the
The That's the workflow used in the smoke test draft PR. It's a little janky if, for example, the dependencies needed to change in the package that you are validating against, I don't think you'll get those dependencies updated. But it's probably workable enough for the pyodide CI use-case. At least until improved local package support comes around in one of the issues linked in the previous comment.
Deno added support for a custom npm registry server configured via NPM_CONFIG_REGISTRY. I didn't explore that direction further but it should probably be documented as an option. |
Still need some docs but for some example snippets of Deno with pyodide: These examples use the Simple example// example.ts
import pyodideModule from "npm:pyodide/pyodide.js";
const { loadPyodide } = pyodideModule;
const pyodide = await loadPyodide();
const result = await pyodide.runPythonAsync(`
3+4
`);
console.log("result:", result.toString()); Which you can run with
Example with additional packagesThis example show the workflow with packages fetched over the network: // numpy.ts
import pyodideModule from "npm:pyodide/pyodide.js";
const { loadPyodide } = pyodideModule;
const pyodide = await loadPyodide();
await pyodide.loadPackage("numpy");
const result = await pyodide.runPythonAsync(`
import numpy as np
a = np.arange(15).reshape(3, 5)
a
`);
console.log("result:", result.toString()); The first run we have to give permissions for pyodide to write to fetch the packages over the network and write to it's cache with
Once everything is cached locally we can run with a stricter sandbox like above: with
|
@rajsite thanks for that example, I wrote up some notes on an experiment that it inspired: https://til.simonwillison.net/deno/pyodide-sandbox |
Hey! I'm trying to use pyodide in an Edge environment (Deno Deploy), and unfortunately, it doesn't support using window.document = {};
const {loadPyodide} = await import("https://www.unpkg.com/pyodide/pyodide.mjs");
const pyodide = await loadPyodide();
const result = await pyodide.runPythonAsync("3+4");
console.log("result:", result.toString()); // expected -> `result: 7` However, this will load the module dynamically every time instead of doing a pre-cache of the module, considerably increasing the execution time. But then I thought, if the above code works, why not just consider Deno runtime and Browser as the same in |
@vfssantos I was able to get pyodide to work in deno deploy here: https://x.com/rajsite/status/1701328734567956824?s=20 Deno deploy recently added support for npm specifiers: https://deno.com/blog/npm-on-deno-deploy Deno deploy examples with pyodide: Playground: https://dash.deno.com/playground/pyodide-example Which has snippet: import pyodideModule from "npm:pyodide/pyodide.js";
const { loadPyodide } = pyodideModule;
const pyodide = await loadPyodide();
Deno.serve(async (req: Request) => {
const url = new URL(req.url);
const multiply = parseInt(url.searchParams.get('multiply') ?? 7, 10);
pyodide.globals.set('x', multiply)
const result = await pyodide.runPythonAsync(`x * x`);
return new Response("Hello World: " + result.toString());
}); The issue I ran into is that the Deno Deploy filesystem is readonly so trying to import additional packages fails as it tries to write to the filesystem. Asked about workarounds but haven't heard much discussion yet: #3950 (comment) Can re-ask here though, any thoughts on the right workaround to get packages to load in memory instead of from disk? |
@vfssantos From what I remember the mjs imports didn't work previously! At the time mjs didn't work but I found with minor tweaks the |
This was indeed suggested several times, but the problem is that the packages Pyodide distributes are only a tiny fraction of all packages people use in Pyodide from PyPI (and we aim to upload binary wheels to PyPI in the long term as well). It wouldn't make sense to put all of PyPI (or even a reasonable subset) to npm, so some other solution needs to be found. Though once you have an application with a fixed set of dependencies, maybe deploying it as an npm package to some private repository could indeed make this use case easier |
Hey @rajsite ! Regarding loading python packages in Deno Deploy, I figured a hack that kind of works, but without using the // One key catch is to statically import the pyodide.asm.js file, because otherwise
// pyodide would try to import it dynamically from a template string,
// which Deno Deploy does not support at the moment.
import "https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyodide.asm.js" // Statically import the pyodide WASM file
// hacks for tricking pyodide into thinking it's running on browsers
globalThis.document={};
globalThis.location= new URL(import.meta.url);
// Load pyodide ESM
const { loadPyodide } = await import("https://cdn.jsdelivr.net/pyodide/v0.24.0/full/pyodide.mjs");
const pyodide = await loadPyodide();
// Load Numpy module
await pyodide.loadPackage("numpy");
// Server
Deno.serve(async req=>{
const res = await pyodide.runPython("import numpy as np\nnp.sum([10,1])");
return new Response(res)
}) The basic ideia is that, if it works on browsers, it should work on Deno. The problem here is the following lines of code that are needed, and that prevent pyodide to be statically imported, and therefore increasing CPU time and overall latency in Deno Deploy. globalThis.document={};
globalThis.location= new URL(import.meta.url); However, I imagine that there might be a possibly simple fix in I hope to be able to look into it soon :) Cheers |
Deno support was discussed in #1477 (comment) and I think it would be good to add it and have some minimal tests in CI. As discussed in the linked PR only a few minor fixes are needed. I'll open a PR shortly.
Personally, the use case I'm interested in that could benefit from running in Deno is server side sandboxing #869 (comment)
What's left is:
src/js/streams.ts
for Denopytest-pyodide
The text was updated successfully, but these errors were encountered: