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.js CHANGED
@@ -18168,6 +18168,55 @@ def _install_processes():
18168
18168
 
18169
18169
  _install_processes()
18170
18170
  `;
18171
+ var ASYNCIO_PY = `
18172
+ import asyncio
18173
+ from pyodide.ffi import can_run_sync, run_sync
18174
+
18175
+ _orig_run = asyncio.run
18176
+
18177
+
18178
+ def _run(main, *, debug=None, loop_factory=None):
18179
+ if not asyncio.iscoroutine(main) and not asyncio.isfuture(main):
18180
+ raise ValueError("a coroutine was expected, got {!r}".format(main))
18181
+ if not can_run_sync():
18182
+ # No stack switching: the original at least raises a comprehensible
18183
+ # error rather than this shim inventing a new one.
18184
+ return _orig_run(main, debug=debug, loop_factory=loop_factory)
18185
+ return run_sync(main)
18186
+
18187
+
18188
+ def _run_until_complete(self, future):
18189
+ if not can_run_sync():
18190
+ raise RuntimeError(
18191
+ "cannot wait for a coroutine on this host: WebAssembly stack "
18192
+ "switching (JSPI) is unavailable."
18193
+ )
18194
+ return run_sync(future)
18195
+
18196
+
18197
+ async def _create_server(self, *args, **kwargs):
18198
+ # Pyodide's loop raises a bare NotImplementedError from deep inside
18199
+ # asyncio, which tells a reader nothing about why their server will not
18200
+ # start. Naming the limit is the least this can do until the container can
18201
+ # route connections to Python.
18202
+ raise NotImplementedError(
18203
+ "this container cannot yet accept network connections from Python: "
18204
+ "asyncio's create_server is not implemented on Pyodide's event loop, "
18205
+ "so an ASGI/WSGI server such as uvicorn will install and import but "
18206
+ "not bind a port. Outbound requests do work."
18207
+ )
18208
+
18209
+
18210
+ def _install_asyncio():
18211
+ asyncio.run = _run
18212
+ asyncio.runners.run = _run
18213
+ loop_type = type(asyncio.get_event_loop())
18214
+ loop_type.run_until_complete = _run_until_complete
18215
+ loop_type.create_server = _create_server
18216
+
18217
+
18218
+ _install_asyncio()
18219
+ `;
18171
18220
  var NETWORK_PY = `
18172
18221
  import builtins, email.message, io, sys, urllib.error, urllib.request, urllib.response
18173
18222
  import _sbx_host as _host
@@ -18452,6 +18501,7 @@ function installSyscallBridge(py) {
18452
18501
  py.runPython(BRIDGE_PY);
18453
18502
  py.runPython(PROCESS_PY);
18454
18503
  py.runPython(NETWORK_PY);
18504
+ py.runPython(ASYNCIO_PY);
18455
18505
  }
18456
18506
  function bindProgram(py, binding) {
18457
18507
  py.__sbxSlot.current = binding;
@@ -18725,6 +18775,67 @@ async function runCPythonRepl(ctx) {
18725
18775
  }
18726
18776
  return 0;
18727
18777
  }
18778
+ var PIP_PY = `
18779
+ import json, re
18780
+
18781
+ import micropip
18782
+
18783
+ _MISSING = re.compile(r"Can't find a pure Python 3 wheel for '([^']+)'")
18784
+ _EXTRAS = re.compile(r"^\\s*([A-Za-z0-9._-]+)\\s*\\[([^\\]]+)\\](.*)$")
18785
+
18786
+
18787
+ def _name_of(spec):
18788
+ return re.split(r"[\\[<>=!~;\\s]", spec.strip(), 1)[0]
18789
+
18790
+
18791
+ def _installed_version(name):
18792
+ try:
18793
+ found = micropip.list()[name.replace("_", "-").lower()]
18794
+ return getattr(found, "version", None)
18795
+ except Exception:
18796
+ return None
18797
+
18798
+
18799
+ async def _install_one(spec):
18800
+ """Install one requirement, retrying without extras if they are impossible."""
18801
+ attempt = spec
18802
+ dropped = None
18803
+ while True:
18804
+ try:
18805
+ await micropip.install(attempt)
18806
+ except ValueError as error:
18807
+ message = str(error)
18808
+ missing = _MISSING.findall(message)
18809
+ extras = _EXTRAS.match(attempt)
18810
+ if missing and extras and dropped is None:
18811
+ dropped = [e.strip() for e in extras.group(2).split(",")]
18812
+ attempt = extras.group(1) + extras.group(3)
18813
+ continue
18814
+ return {
18815
+ "spec": spec, "ok": False, "missing": missing,
18816
+ "reason": message.strip().split("\\n")[0],
18817
+ }
18818
+ except Exception as error:
18819
+ return {"spec": spec, "ok": False, "missing": [], "reason": str(error).strip().split("\\n")[0]}
18820
+
18821
+ name = _name_of(attempt)
18822
+ return {
18823
+ "spec": spec, "ok": True, "name": name,
18824
+ "version": _installed_version(name), "dropped": dropped,
18825
+ }
18826
+
18827
+
18828
+ async def __sbx_pip(specs_json):
18829
+ results = []
18830
+ for spec in json.loads(specs_json):
18831
+ try:
18832
+ results.append(await _install_one(spec))
18833
+ except Exception as error:
18834
+ results.append({"spec": spec, "ok": False, "missing": [], "reason": str(error)})
18835
+ return json.dumps(results)
18836
+
18837
+ __sbx_pip
18838
+ `;
18728
18839
  var micropip = defineCommand({
18729
18840
  name: "micropip",
18730
18841
  path: "/usr/bin/micropip",
@@ -18762,8 +18873,7 @@ var micropip = defineCommand({
18762
18873
  ctx.stderr.write("pip: no packages specified\n");
18763
18874
  return 1;
18764
18875
  }
18765
- const index = "https://pypi.org";
18766
- if (!ctx.kernel.net.outboundAllowed(index)) {
18876
+ if (!ctx.kernel.net.outboundAllowed("https://pypi.org")) {
18767
18877
  ctx.stderr.write(
18768
18878
  "pip: outbound network access is disabled for this container (enable with network: { allowOutbound: true })\n"
18769
18879
  );
@@ -18776,22 +18886,42 @@ var micropip = defineCommand({
18776
18886
  ctx.stderr.write("pip: Pyodide is unavailable in this host\n");
18777
18887
  return 127;
18778
18888
  }
18889
+ let outcomes;
18779
18890
  try {
18780
18891
  await py.loadPackage("micropip");
18781
- const micropipModule = py.pyimport("micropip");
18782
- for (const name of packages) {
18783
- ctx.line(`Collecting ${name}`);
18784
- await micropipModule.install(name);
18785
- ctx.line(`Successfully installed ${name}`);
18786
- }
18787
- return 0;
18892
+ for (const name of packages) ctx.line(`Collecting ${name}`);
18893
+ const install2 = py.runPython(PIP_PY);
18894
+ outcomes = JSON.parse(String(await install2(JSON.stringify(packages))));
18788
18895
  } catch (error) {
18789
18896
  ctx.stderr.write(
18790
- `Package installation failed: ${error instanceof Error ? error.message : String(error)}
18897
+ `pip: installation failed: ${error instanceof Error ? error.message : String(error)}
18791
18898
  `
18792
18899
  );
18793
18900
  return 1;
18794
18901
  }
18902
+ const installed2 = [];
18903
+ for (const outcome of outcomes) {
18904
+ if (outcome.ok) {
18905
+ installed2.push(
18906
+ outcome.version ? `${outcome.name}-${outcome.version}` : String(outcome.name)
18907
+ );
18908
+ if (outcome.dropped?.length) {
18909
+ ctx.stderr.write(
18910
+ ` WARNING: ${outcome.name}: the ${outcome.dropped.map((extra) => `'${extra}'`).join(", ")} extra needs native code with no WebAssembly build; installed ${outcome.name} without it
18911
+ `
18912
+ );
18913
+ }
18914
+ continue;
18915
+ }
18916
+ const missing = outcome.missing ?? [];
18917
+ ctx.stderr.write(
18918
+ missing.length > 0 ? `ERROR: could not install ${outcome.spec}: no WebAssembly build exists for ${missing.map((name) => name.split(/[<>=!~;\s]/)[0]).join(", ")}
18919
+ ` : `ERROR: could not install ${outcome.spec}: ${outcome.reason ?? "unknown error"}
18920
+ `
18921
+ );
18922
+ }
18923
+ if (installed2.length > 0) ctx.line(`Successfully installed ${installed2.join(" ")}`);
18924
+ return outcomes.every((outcome) => outcome.ok) ? 0 : 1;
18795
18925
  }
18796
18926
  });
18797
18927