sandboxedjs 0.1.33 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -18185,6 +18185,55 @@ def _install_processes():
18185
18185
 
18186
18186
  _install_processes()
18187
18187
  `;
18188
+ var ASYNCIO_PY = `
18189
+ import asyncio
18190
+ from pyodide.ffi import can_run_sync, run_sync
18191
+
18192
+ _orig_run = asyncio.run
18193
+
18194
+
18195
+ def _run(main, *, debug=None, loop_factory=None):
18196
+ if not asyncio.iscoroutine(main) and not asyncio.isfuture(main):
18197
+ raise ValueError("a coroutine was expected, got {!r}".format(main))
18198
+ if not can_run_sync():
18199
+ # No stack switching: the original at least raises a comprehensible
18200
+ # error rather than this shim inventing a new one.
18201
+ return _orig_run(main, debug=debug, loop_factory=loop_factory)
18202
+ return run_sync(main)
18203
+
18204
+
18205
+ def _run_until_complete(self, future):
18206
+ if not can_run_sync():
18207
+ raise RuntimeError(
18208
+ "cannot wait for a coroutine on this host: WebAssembly stack "
18209
+ "switching (JSPI) is unavailable."
18210
+ )
18211
+ return run_sync(future)
18212
+
18213
+
18214
+ async def _create_server(self, *args, **kwargs):
18215
+ # Pyodide's loop raises a bare NotImplementedError from deep inside
18216
+ # asyncio, which tells a reader nothing about why their server will not
18217
+ # start. Naming the limit is the least this can do until the container can
18218
+ # route connections to Python.
18219
+ raise NotImplementedError(
18220
+ "this container cannot yet accept network connections from Python: "
18221
+ "asyncio's create_server is not implemented on Pyodide's event loop, "
18222
+ "so an ASGI/WSGI server such as uvicorn will install and import but "
18223
+ "not bind a port. Outbound requests do work."
18224
+ )
18225
+
18226
+
18227
+ def _install_asyncio():
18228
+ asyncio.run = _run
18229
+ asyncio.runners.run = _run
18230
+ loop_type = type(asyncio.get_event_loop())
18231
+ loop_type.run_until_complete = _run_until_complete
18232
+ loop_type.create_server = _create_server
18233
+
18234
+
18235
+ _install_asyncio()
18236
+ `;
18188
18237
  var NETWORK_PY = `
18189
18238
  import builtins, email.message, io, sys, urllib.error, urllib.request, urllib.response
18190
18239
  import _sbx_host as _host
@@ -18469,6 +18518,7 @@ function installSyscallBridge(py) {
18469
18518
  py.runPython(BRIDGE_PY);
18470
18519
  py.runPython(PROCESS_PY);
18471
18520
  py.runPython(NETWORK_PY);
18521
+ py.runPython(ASYNCIO_PY);
18472
18522
  }
18473
18523
  function bindProgram(py, binding) {
18474
18524
  py.__sbxSlot.current = binding;
@@ -18742,6 +18792,67 @@ async function runCPythonRepl(ctx) {
18742
18792
  }
18743
18793
  return 0;
18744
18794
  }
18795
+ var PIP_PY = `
18796
+ import json, re
18797
+
18798
+ import micropip
18799
+
18800
+ _MISSING = re.compile(r"Can't find a pure Python 3 wheel for '([^']+)'")
18801
+ _EXTRAS = re.compile(r"^\\s*([A-Za-z0-9._-]+)\\s*\\[([^\\]]+)\\](.*)$")
18802
+
18803
+
18804
+ def _name_of(spec):
18805
+ return re.split(r"[\\[<>=!~;\\s]", spec.strip(), 1)[0]
18806
+
18807
+
18808
+ def _installed_version(name):
18809
+ try:
18810
+ found = micropip.list()[name.replace("_", "-").lower()]
18811
+ return getattr(found, "version", None)
18812
+ except Exception:
18813
+ return None
18814
+
18815
+
18816
+ async def _install_one(spec):
18817
+ """Install one requirement, retrying without extras if they are impossible."""
18818
+ attempt = spec
18819
+ dropped = None
18820
+ while True:
18821
+ try:
18822
+ await micropip.install(attempt)
18823
+ except ValueError as error:
18824
+ message = str(error)
18825
+ missing = _MISSING.findall(message)
18826
+ extras = _EXTRAS.match(attempt)
18827
+ if missing and extras and dropped is None:
18828
+ dropped = [e.strip() for e in extras.group(2).split(",")]
18829
+ attempt = extras.group(1) + extras.group(3)
18830
+ continue
18831
+ return {
18832
+ "spec": spec, "ok": False, "missing": missing,
18833
+ "reason": message.strip().split("\\n")[0],
18834
+ }
18835
+ except Exception as error:
18836
+ return {"spec": spec, "ok": False, "missing": [], "reason": str(error).strip().split("\\n")[0]}
18837
+
18838
+ name = _name_of(attempt)
18839
+ return {
18840
+ "spec": spec, "ok": True, "name": name,
18841
+ "version": _installed_version(name), "dropped": dropped,
18842
+ }
18843
+
18844
+
18845
+ async def __sbx_pip(specs_json):
18846
+ results = []
18847
+ for spec in json.loads(specs_json):
18848
+ try:
18849
+ results.append(await _install_one(spec))
18850
+ except Exception as error:
18851
+ results.append({"spec": spec, "ok": False, "missing": [], "reason": str(error)})
18852
+ return json.dumps(results)
18853
+
18854
+ __sbx_pip
18855
+ `;
18745
18856
  var micropip = defineCommand({
18746
18857
  name: "micropip",
18747
18858
  path: "/usr/bin/micropip",
@@ -18779,8 +18890,7 @@ var micropip = defineCommand({
18779
18890
  ctx.stderr.write("pip: no packages specified\n");
18780
18891
  return 1;
18781
18892
  }
18782
- const index = "https://pypi.org";
18783
- if (!ctx.kernel.net.outboundAllowed(index)) {
18893
+ if (!ctx.kernel.net.outboundAllowed("https://pypi.org")) {
18784
18894
  ctx.stderr.write(
18785
18895
  "pip: outbound network access is disabled for this container (enable with network: { allowOutbound: true })\n"
18786
18896
  );
@@ -18793,22 +18903,42 @@ var micropip = defineCommand({
18793
18903
  ctx.stderr.write("pip: Pyodide is unavailable in this host\n");
18794
18904
  return 127;
18795
18905
  }
18906
+ let outcomes;
18796
18907
  try {
18797
18908
  await py.loadPackage("micropip");
18798
- const micropipModule = py.pyimport("micropip");
18799
- for (const name of packages) {
18800
- ctx.line(`Collecting ${name}`);
18801
- await micropipModule.install(name);
18802
- ctx.line(`Successfully installed ${name}`);
18803
- }
18804
- return 0;
18909
+ for (const name of packages) ctx.line(`Collecting ${name}`);
18910
+ const install2 = py.runPython(PIP_PY);
18911
+ outcomes = JSON.parse(String(await install2(JSON.stringify(packages))));
18805
18912
  } catch (error) {
18806
18913
  ctx.stderr.write(
18807
- `Package installation failed: ${error instanceof Error ? error.message : String(error)}
18914
+ `pip: installation failed: ${error instanceof Error ? error.message : String(error)}
18808
18915
  `
18809
18916
  );
18810
18917
  return 1;
18811
18918
  }
18919
+ const installed2 = [];
18920
+ for (const outcome of outcomes) {
18921
+ if (outcome.ok) {
18922
+ installed2.push(
18923
+ outcome.version ? `${outcome.name}-${outcome.version}` : String(outcome.name)
18924
+ );
18925
+ if (outcome.dropped?.length) {
18926
+ ctx.stderr.write(
18927
+ ` WARNING: ${outcome.name}: the ${outcome.dropped.map((extra) => `'${extra}'`).join(", ")} extra needs native code with no WebAssembly build; installed ${outcome.name} without it
18928
+ `
18929
+ );
18930
+ }
18931
+ continue;
18932
+ }
18933
+ const missing = outcome.missing ?? [];
18934
+ ctx.stderr.write(
18935
+ missing.length > 0 ? `ERROR: could not install ${outcome.spec}: no WebAssembly build exists for ${missing.map((name) => name.split(/[<>=!~;\s]/)[0]).join(", ")}
18936
+ ` : `ERROR: could not install ${outcome.spec}: ${outcome.reason ?? "unknown error"}
18937
+ `
18938
+ );
18939
+ }
18940
+ if (installed2.length > 0) ctx.line(`Successfully installed ${installed2.join(" ")}`);
18941
+ return outcomes.every((outcome) => outcome.ok) ? 0 : 1;
18812
18942
  }
18813
18943
  });
18814
18944