sandboxedjs 0.1.76 → 0.1.77

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
@@ -7802,7 +7802,7 @@ fi
7802
7802
  `;
7803
7803
  var MOTD = `Welcome to SandboxedJS \u2014 a Linux-like container running inside Node.js.
7804
7804
 
7805
- * Node.js, npm and CPython (Pyodide) are preinstalled.
7805
+ * Node.js, npm and CPython are preinstalled.
7806
7806
  * The filesystem is virtual: nothing here touches your host.
7807
7807
  * Run 'help' for the list of built-in commands.
7808
7808
 
@@ -19728,9 +19728,6 @@ function nodeCommands() {
19728
19728
  return [node, nodeVersionFile];
19729
19729
  }
19730
19730
 
19731
- // src/python/python.ts
19732
- init_path();
19733
-
19734
19731
  // src/python/host-abi.ts
19735
19732
  var SBX_HOST_ABI_VERSION = 1;
19736
19733
  var SBX_REQUEST_HEADER_BYTES = 16;
@@ -20201,7 +20198,7 @@ var wheels_default = {
20201
20198
 
20202
20199
  // package.json
20203
20200
  var package_default = {
20204
- version: "0.1.76"};
20201
+ version: "0.1.77"};
20205
20202
 
20206
20203
  // src/python/config.ts
20207
20204
  function runtimeModuleUrl() {
@@ -20233,11 +20230,15 @@ var embeddedWheels = {
20233
20230
  "pydantic_core-2.23.2-cp313-cp313-emscripten_5_0_6_wasm32.whl": pydantic_core_2_23_2_cp313_cp313_emscripten_5_0_6_wasm32_default,
20234
20231
  "pydantic_core-2.46.5-cp313-cp313-emscripten_5_0_6_wasm32.whl": pydantic_core_2_46_5_cp313_cp313_emscripten_5_0_6_wasm32_default
20235
20232
  };
20236
- var bundledWheelIndex = {
20237
- baseUrl: new URL("./wheels", runtimeModuleUrl()).href,
20238
- wheels: wheels_default.wheels,
20239
- files: embeddedWheels
20240
- };
20233
+ function wheelIndexFor(moduleUrl) {
20234
+ return {
20235
+ baseUrl: new URL("./wheels", moduleUrl).href,
20236
+ wheels: wheels_default.wheels,
20237
+ files: embeddedWheels
20238
+ };
20239
+ }
20240
+ var bundledWheelIndex = wheelIndexFor(runtimeModuleUrl());
20241
+ var wheelIndexIsExplicit = false;
20241
20242
  var config = {
20242
20243
  backend: "sbx-cpython-wasm",
20243
20244
  manifest: bundledManifest,
@@ -20252,9 +20253,17 @@ var config = {
20252
20253
  buildFromSource: typeof process !== "undefined" && Boolean(process.versions?.node) && import.meta.url.startsWith("file:")
20253
20254
  };
20254
20255
  function setPythonBackend(options) {
20255
- if (options.wheelIndex !== void 0) config.wheelIndex = options.wheelIndex;
20256
+ if (options.wheelIndex !== void 0) {
20257
+ config.wheelIndex = options.wheelIndex;
20258
+ wheelIndexIsExplicit = true;
20259
+ }
20256
20260
  if (options.buildFromSource !== void 0) config.buildFromSource = options.buildFromSource;
20257
- if (options.manifest !== void 0) config.manifest = validateManifest(options.manifest);
20261
+ if (options.manifest !== void 0) {
20262
+ config.manifest = validateManifest(options.manifest);
20263
+ if (!wheelIndexIsExplicit) {
20264
+ config.wheelIndex = wheelIndexFor(config.manifest.artifacts.moduleUrl);
20265
+ }
20266
+ }
20258
20267
  if (options.workerUrl !== void 0) config.workerUrl = options.workerUrl;
20259
20268
  if (options.backend !== void 0) {
20260
20269
  if (options.backend === "sbx-cpython-wasm" && !config.manifest) {
@@ -22772,6 +22781,7 @@ async function installRequirements(options) {
22772
22781
  }
22773
22782
  options.progress.downloading(distribution.name, distribution.version);
22774
22783
  const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
22784
+ requireArchive(distribution, bytes2);
22775
22785
  verifyDigest(distribution, bytes2);
22776
22786
  staged.push(...stageWheel(readZip(bytes2)));
22777
22787
  installed2.push({ name: distribution.name, version: distribution.version });
@@ -22779,6 +22789,14 @@ async function installRequirements(options) {
22779
22789
  commit(options.vfs, options.cred, staged);
22780
22790
  return { installed: installed2, skipped: solved.skipped };
22781
22791
  }
22792
+ function requireArchive(distribution, bytes2) {
22793
+ if (bytes2.length > 1 && bytes2[0] === 80 && bytes2[1] === 75) return;
22794
+ const opening = new TextDecoder().decode(bytes2.subarray(0, 80)).replace(/\s+/g, " ").trim();
22795
+ const looksLikeMarkup = /^<!doctype|^<html/i.test(opening);
22796
+ throw new Error(
22797
+ `${distribution.filename} did not arrive as a wheel from ${distribution.url}: ` + (looksLikeMarkup ? "the server answered with an HTML page, which usually means the wheel is not published at that address and the server returned its index page instead" : `expected a zip archive, got ${bytes2.length} bytes beginning ${JSON.stringify(opening)}`)
22798
+ );
22799
+ }
22782
22800
  function verifyDigest(distribution, bytes2) {
22783
22801
  if (!distribution.sha256) {
22784
22802
  throw new Error(
@@ -23063,9 +23081,6 @@ async function resolveWorkerUrl(explicit) {
23063
23081
  "the SandboxedJs Python process worker has not been built; run `npm run build` in the package, or pass configurePython({ workerUrl })."
23064
23082
  );
23065
23083
  }
23066
- function usingOwnedPython() {
23067
- return pythonBackend().backend === "sbx-cpython-wasm";
23068
- }
23069
23084
  async function runOwnedPython(ctx, argv) {
23070
23085
  const { manifest, workerUrl } = pythonBackend();
23071
23086
  if (!manifest) throw new Error("no Python runtime manifest is configured");
@@ -23554,6 +23569,75 @@ function listInstalled(ctx) {
23554
23569
  return 0;
23555
23570
  }
23556
23571
 
23572
+ // src/python/python.ts
23573
+ var PYTHON_VERSION = "3.13";
23574
+ function configurePython(options = {}) {
23575
+ setPythonBackend({
23576
+ ...options.backend !== void 0 ? { backend: options.backend } : {},
23577
+ ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
23578
+ ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
23579
+ ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {},
23580
+ ...options.buildFromSource !== void 0 ? { buildFromSource: options.buildFromSource } : {}
23581
+ });
23582
+ }
23583
+ async function isPythonAvailable() {
23584
+ return pythonBackend().manifest !== null;
23585
+ }
23586
+ function withTopLevelAwait(source) {
23587
+ if (!/\bawait\b/.test(source)) return source;
23588
+ return `import ast as _sbx_ast, inspect as _sbx_inspect
23589
+ _sbx_src = ${JSON.stringify(source)}
23590
+ try:
23591
+ _sbx_code = compile(_sbx_src, "<string>", "exec")
23592
+ except SyntaxError:
23593
+ _sbx_code = compile(_sbx_src, "<string>", "exec", _sbx_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
23594
+ _sbx_async = bool(_sbx_code.co_flags & _sbx_inspect.CO_COROUTINE)
23595
+ _sbx_globals = globals()
23596
+ for _sbx_name in ("_sbx_ast", "_sbx_inspect", "_sbx_src", "_sbx_name"):
23597
+ _sbx_globals.pop(_sbx_name, None)
23598
+ if _sbx_async:
23599
+ import asyncio as _sbx_asyncio
23600
+ _sbx_asyncio.run(eval(_sbx_code, _sbx_globals))
23601
+ else:
23602
+ exec(_sbx_code, _sbx_globals)
23603
+ `;
23604
+ }
23605
+ function withTopLevelAwaitArgs(args) {
23606
+ for (let i = 0; i < args.length; i++) {
23607
+ const arg = args[i];
23608
+ if (arg === "-c") {
23609
+ if (i + 1 >= args.length) return args;
23610
+ const rewritten = withTopLevelAwait(args[i + 1]);
23611
+ if (rewritten === args[i + 1]) return args;
23612
+ const copy = [...args];
23613
+ copy[i + 1] = rewritten;
23614
+ return copy;
23615
+ }
23616
+ if (arg === "-m" || !arg.startsWith("-") || arg === "-") return args;
23617
+ }
23618
+ return args;
23619
+ }
23620
+ var python = defineCommand({
23621
+ name: "python3",
23622
+ path: "/usr/bin/python3",
23623
+ aliases: ["python"],
23624
+ summary: "run CPython",
23625
+ usage: "python3 [-c command | -m module | script.py] [arguments]",
23626
+ manual: `CPython built for WebAssembly, one interpreter per process. Scripts
23627
+ use the container's virtual filesystem, modules in /workspace are importable,
23628
+ and packages are installed with pip.`,
23629
+ async run(ctx) {
23630
+ try {
23631
+ return await runOwnedPython(ctx, withTopLevelAwaitArgs(ctx.args));
23632
+ } catch (error) {
23633
+ return ctx.fail(error.message ?? String(error));
23634
+ }
23635
+ }
23636
+ });
23637
+ function pythonCommands() {
23638
+ return [python, pipCommand];
23639
+ }
23640
+
23557
23641
  // src/fs/emscripten-fs.ts
23558
23642
  init_errno();
23559
23643
  init_path();
@@ -23809,1301 +23893,6 @@ function mountContainerFs(FS, opts) {
23809
23893
  }
23810
23894
  }
23811
23895
 
23812
- // src/python/cpython.ts
23813
- init_binary();
23814
-
23815
- // src/python/python-syscalls.ts
23816
- var EMPTY = new Uint8Array(0);
23817
- function createStdinHost(ctx) {
23818
- let pending = EMPTY;
23819
- let eof = false;
23820
- const take = (size) => {
23821
- const n = Math.min(size, pending.length);
23822
- const out = pending.subarray(0, n);
23823
- pending = pending.subarray(n);
23824
- return out;
23825
- };
23826
- return {
23827
- /**
23828
- * Drain a non-interactive stdin up front.
23829
- *
23830
- * A pipe ends, so reading it eagerly costs nothing and leaves every read
23831
- * answerable without suspending — which is what keeps piped input working
23832
- * on hosts with no stack-switching. A terminal never ends, so priming one
23833
- * would hang before the program had printed its prompt.
23834
- */
23835
- async prime() {
23836
- if (ctx.stdin.isTTY || ctx.stdin.interactive) return;
23837
- try {
23838
- pending = await ctx.stdin.readAll();
23839
- } catch {
23840
- pending = EMPTY;
23841
- }
23842
- eof = true;
23843
- },
23844
- buffered(size) {
23845
- if (pending.length > 0) return take(size);
23846
- return eof ? EMPTY : void 0;
23847
- },
23848
- async read(size) {
23849
- if (pending.length > 0) return take(size);
23850
- if (eof) return EMPTY;
23851
- const chunk = await ctx.stdin.read(size);
23852
- if (chunk === null || chunk.length === 0) {
23853
- eof = true;
23854
- return EMPTY;
23855
- }
23856
- pending = chunk;
23857
- return take(size);
23858
- },
23859
- isatty() {
23860
- return Boolean(ctx.stdin.isTTY);
23861
- }
23862
- };
23863
- }
23864
- var BRIDGE_PY = `
23865
- import builtins, io, sys
23866
- import _sbx_host as _host
23867
- from pyodide.ffi import can_run_sync, run_sync
23868
-
23869
-
23870
- def _to_bytes(value):
23871
- if value is None:
23872
- return b""
23873
- to_bytes = getattr(value, "to_bytes", None)
23874
- if to_bytes is not None:
23875
- return to_bytes()
23876
- return bytes(value.to_py())
23877
-
23878
-
23879
- class _SbxStdin(io.RawIOBase):
23880
- """The container's standard input, as a blocking raw stream."""
23881
-
23882
- def readable(self):
23883
- return True
23884
-
23885
- def fileno(self):
23886
- return 0
23887
-
23888
- def isatty(self):
23889
- return bool(_host.isatty())
23890
-
23891
- def readinto(self, target):
23892
- want = len(target)
23893
- if want == 0:
23894
- return 0
23895
- ready = _host.buffered(want)
23896
- if ready is None:
23897
- if not can_run_sync():
23898
- raise OSError(
23899
- "this host cannot wait for interactive input: WebAssembly "
23900
- "stack switching (JSPI) is unavailable. Pipe the input in, "
23901
- "or use a browser or Node build that supports it."
23902
- )
23903
- ready = run_sync(_host.read(want))
23904
- data = _to_bytes(ready)
23905
- target[: len(data)] = data
23906
- return len(data)
23907
-
23908
-
23909
- def _install():
23910
- stdin = io.TextIOWrapper(
23911
- io.BufferedReader(_SbxStdin()), encoding="utf-8", errors="replace", line_buffering=True
23912
- )
23913
- sys.stdin = stdin
23914
- sys.__stdin__ = stdin
23915
-
23916
- def input(prompt=""):
23917
- # CPython writes the prompt to stdout and strips exactly one trailing
23918
- # newline; anything else changes what a program reads back.
23919
- if prompt != "":
23920
- sys.stdout.write(str(prompt))
23921
- sys.stdout.flush()
23922
- line = sys.stdin.readline()
23923
- if not line:
23924
- raise EOFError("EOF when reading a line")
23925
- if line.endswith("\\n"):
23926
- line = line[:-1]
23927
- if line.endswith("\\r"):
23928
- line = line[:-1]
23929
- return line
23930
-
23931
- builtins.input = input
23932
-
23933
-
23934
- _install()
23935
- `;
23936
- var PROCESS_PY = `
23937
- import io, os, shlex, subprocess, sys
23938
- import _sbx_host as _host
23939
- from pyodide.ffi import can_run_sync, run_sync
23940
-
23941
- _PIPE = subprocess.PIPE
23942
- _DEVNULL = subprocess.DEVNULL
23943
- _STDOUT = subprocess.STDOUT
23944
-
23945
-
23946
- def _argv_for(args, shell):
23947
- if shell:
23948
- line = args if isinstance(args, str) else " ".join(str(a) for a in args)
23949
- return ["sh", "-c", line]
23950
- if isinstance(args, (str, bytes)):
23951
- return shlex.split(args if isinstance(args, str) else args.decode())
23952
- return [str(a) for a in args]
23953
-
23954
-
23955
- def _require_blocking(what):
23956
- if not can_run_sync():
23957
- raise OSError(
23958
- "cannot " + what + " on this host: WebAssembly stack switching "
23959
- "(JSPI) is unavailable."
23960
- )
23961
-
23962
-
23963
- def _to_bytes_result(value):
23964
- if value is None:
23965
- return b""
23966
- to_bytes = getattr(value, "to_bytes", None)
23967
- return to_bytes() if to_bytes is not None else bytes(value.to_py())
23968
-
23969
-
23970
- class _ChildReader(io.RawIOBase):
23971
- """One of a running child's output streams.
23972
-
23973
- Reads suspend only for as long as the child takes to produce the next
23974
- bytes, so \`for line in p.stdout\` follows a child that is still running
23975
- instead of waiting for it to exit.
23976
- """
23977
-
23978
- def __init__(self, child_id, which):
23979
- self._id = child_id
23980
- self._which = which
23981
-
23982
- def readable(self):
23983
- return True
23984
-
23985
- def readinto(self, target):
23986
- want = len(target)
23987
- if want == 0:
23988
- return 0
23989
- _require_blocking("read from a child process")
23990
- data = _to_bytes_result(run_sync(_host.proc_read(self._id, self._which, want)))
23991
- target[: len(data)] = data
23992
- return len(data)
23993
-
23994
-
23995
- class _ChildWriter(io.RawIOBase):
23996
- """A running child's standard input."""
23997
-
23998
- def __init__(self, child_id):
23999
- self._id = child_id
24000
-
24001
- def writable(self):
24002
- return True
24003
-
24004
- def write(self, data):
24005
- if isinstance(data, str):
24006
- text = data
24007
- else:
24008
- text = bytes(data).decode("utf-8", "replace")
24009
- _host.proc_write(self._id, text)
24010
- return len(data)
24011
-
24012
- def close(self):
24013
- if not self.closed:
24014
- _host.proc_close_stdin(self._id)
24015
- super().close()
24016
-
24017
-
24018
- class SbxPopen:
24019
- """A child process, run through the container's kernel.
24020
-
24021
- The child is a real, live process: it starts when \`Popen\` is constructed
24022
- and runs on the host's event loop while Python carries on. The parent only
24023
- suspends when it actually reads, waits, or communicates \u2014 which is what
24024
- makes streaming from a long-running child work rather than deadlocking on
24025
- a single interpreter stack.
24026
- """
24027
-
24028
- def __init__(self, args, bufsize=-1, executable=None, stdin=None, stdout=None,
24029
- stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None,
24030
- env=None, universal_newlines=None, startupinfo=None, creationflags=0,
24031
- restore_signals=True, start_new_session=False, pass_fds=(), *,
24032
- text=None, encoding=None, errors=None, **kwargs):
24033
- self.args = args
24034
- self.returncode = None
24035
- self._text = bool(text or encoding or errors or universal_newlines)
24036
- self._encoding = encoding or "utf-8"
24037
- self._errors = errors or "replace"
24038
- self._merge_err = stderr == _STDOUT
24039
- self._closed = False
24040
-
24041
- argv = _argv_for(args, shell)
24042
- env_map = None if env is None else {str(k): str(v) for k, v in dict(env).items()}
24043
- self._id = _host.proc_start(
24044
- argv, None if cwd is None else str(cwd), env_map, self._merge_err
24045
- )
24046
- self.pid = int(_host.proc_pid(self._id))
24047
-
24048
- self.stdin = self._wrap_writer() if stdin == _PIPE else None
24049
- self.stdout = self._wrap_reader(1) if stdout == _PIPE else None
24050
- # When merged there is only one stream, and it is stream 1.
24051
- self.stderr = self._wrap_reader(2) if stderr == _PIPE and not self._merge_err else None
24052
-
24053
- # Output nobody captured belongs on the parent's own streams, or a
24054
- # program's diagnostics would vanish rather than being seen.
24055
- self._drain_out = stdout is None
24056
- self._drain_err = stderr is None and not self._merge_err
24057
-
24058
- def _wrap_reader(self, which):
24059
- raw = io.BufferedReader(_ChildReader(self._id, which))
24060
- if self._text:
24061
- return io.TextIOWrapper(raw, encoding=self._encoding, errors=self._errors)
24062
- return raw
24063
-
24064
- def _wrap_writer(self):
24065
- raw = io.BufferedWriter(_ChildWriter(self._id))
24066
- if self._text:
24067
- return io.TextIOWrapper(raw, encoding=self._encoding, errors=self._errors)
24068
- return raw
24069
-
24070
- def _read_all(self, which):
24071
- _require_blocking("read from a child process")
24072
- parts = []
24073
- while True:
24074
- chunk = _to_bytes_result(run_sync(_host.proc_read(self._id, which, 65536)))
24075
- if not chunk:
24076
- break
24077
- parts.append(chunk)
24078
- return b"".join(parts)
24079
-
24080
- def _decode(self, raw):
24081
- return raw.decode(self._encoding, self._errors) if self._text else raw
24082
-
24083
- def communicate(self, input=None, timeout=None):
24084
- if input is not None and self.stdin is not None:
24085
- if isinstance(input, bytes):
24086
- input = input.decode(self._encoding, "replace")
24087
- self.stdin.write(input)
24088
- if self.stdin is not None:
24089
- try:
24090
- self.stdin.close()
24091
- except Exception:
24092
- pass
24093
-
24094
- out = self.stdout.read() if self.stdout is not None else None
24095
- err = self.stderr.read() if self.stderr is not None else None
24096
- self.wait()
24097
- self._flush_uncaptured()
24098
- return out, err
24099
-
24100
- def _flush_uncaptured(self):
24101
- if self._drain_out:
24102
- text = self._decode_raw(self._read_all(1))
24103
- if text:
24104
- sys.stdout.write(text)
24105
- if self._drain_err:
24106
- text = self._decode_raw(self._read_all(2))
24107
- if text:
24108
- sys.stderr.write(text)
24109
- self._drain_out = False
24110
- self._drain_err = False
24111
-
24112
- def _decode_raw(self, raw):
24113
- return raw.decode(self._encoding, "replace")
24114
-
24115
- def poll(self):
24116
- code = _host.proc_poll(self._id)
24117
- if code is not None:
24118
- self.returncode = int(code)
24119
- return self.returncode
24120
-
24121
- def wait(self, timeout=None):
24122
- _require_blocking("wait for a child process")
24123
- self.returncode = int(run_sync(_host.proc_wait(self._id)))
24124
- return self.returncode
24125
-
24126
- def kill(self):
24127
- self.send_signal("SIGKILL")
24128
-
24129
- def terminate(self):
24130
- self.send_signal("SIGTERM")
24131
-
24132
- def send_signal(self, sig):
24133
- _host.proc_kill(self._id, sig if isinstance(sig, str) else "SIGTERM")
24134
-
24135
- def __del__(self):
24136
- try:
24137
- _host.proc_release(self._id)
24138
- except Exception:
24139
- pass
24140
-
24141
- def __enter__(self):
24142
- return self
24143
-
24144
- def __exit__(self, *exc):
24145
- if self.stdin is not None:
24146
- try:
24147
- self.stdin.close()
24148
- except Exception:
24149
- pass
24150
- self.wait()
24151
- self._flush_uncaptured()
24152
- for stream in (self.stdout, self.stderr):
24153
- if stream is not None:
24154
- try:
24155
- stream.close()
24156
- except Exception:
24157
- pass
24158
- # The host holds the process and its pipes until it is told the child
24159
- # is finished with; a loop spawning children would otherwise keep every
24160
- # one of them alive. Safe here only because the child has been waited
24161
- # for and everything anyone wanted has been read.
24162
- _host.proc_release(self._id)
24163
- return False
24164
-
24165
-
24166
- def _system(command):
24167
- with SbxPopen(command, shell=True) as child:
24168
- pass
24169
- # os.system returns a wait status, not an exit code.
24170
- return (child.returncode or 0) << 8
24171
-
24172
-
24173
- def _popen(command, mode="r", buffering=-1):
24174
- if "w" in mode:
24175
- raise OSError("os.popen with mode 'w' is not supported in this container")
24176
- child = SbxPopen(command, shell=True, stdout=_PIPE, text=True)
24177
- stream = child.stdout
24178
- inner = stream.close
24179
-
24180
- def close():
24181
- inner()
24182
- code = child.wait()
24183
- return None if code == 0 else code << 8
24184
-
24185
- stream.close = close
24186
- return stream
24187
-
24188
-
24189
- def _install_processes():
24190
- subprocess.Popen = SbxPopen
24191
- os.system = _system
24192
- os.popen = _popen
24193
-
24194
-
24195
- _install_processes()
24196
- `;
24197
- var ASYNCIO_PY = `
24198
- import asyncio
24199
- from pyodide.ffi import can_run_sync, run_sync
24200
-
24201
- _orig_run = asyncio.run
24202
-
24203
-
24204
- def _run(main, *, debug=None, loop_factory=None):
24205
- if not asyncio.iscoroutine(main) and not asyncio.isfuture(main):
24206
- raise ValueError("a coroutine was expected, got {!r}".format(main))
24207
- if not can_run_sync():
24208
- # No stack switching: the original at least raises a comprehensible
24209
- # error rather than this shim inventing a new one.
24210
- return _orig_run(main, debug=debug, loop_factory=loop_factory)
24211
- return run_sync(main)
24212
-
24213
-
24214
- def _run_until_complete(self, future):
24215
- if not can_run_sync():
24216
- raise RuntimeError(
24217
- "cannot wait for a coroutine on this host: WebAssembly stack "
24218
- "switching (JSPI) is unavailable."
24219
- )
24220
- return run_sync(future)
24221
-
24222
-
24223
- async def _create_server(self, *args, **kwargs):
24224
- # Pyodide's loop raises a bare NotImplementedError from deep inside
24225
- # asyncio, which tells a reader nothing about why their server will not
24226
- # start. Naming the limit is the least this can do until the container can
24227
- # route connections to Python.
24228
- raise NotImplementedError(
24229
- "this container cannot yet accept network connections from Python: "
24230
- "asyncio's create_server is not implemented on Pyodide's event loop, "
24231
- "so an ASGI/WSGI server such as uvicorn will install and import but "
24232
- "not bind a port. Outbound requests do work."
24233
- )
24234
-
24235
-
24236
- def _install_asyncio():
24237
- asyncio.run = _run
24238
- asyncio.runners.run = _run
24239
- loop_type = type(asyncio.get_event_loop())
24240
- loop_type.run_until_complete = _run_until_complete
24241
- loop_type.create_server = _create_server
24242
-
24243
-
24244
- _install_asyncio()
24245
- `;
24246
- var NETWORK_PY = `
24247
- import builtins, email.message, io, sys, urllib.error, urllib.request, urllib.response
24248
- import _sbx_host as _host
24249
- from pyodide.ffi import can_run_sync, run_sync
24250
-
24251
-
24252
- def _fetch(request):
24253
- if not can_run_sync():
24254
- raise urllib.error.URLError(
24255
- "cannot make a request on this host: WebAssembly stack switching "
24256
- "(JSPI) is unavailable."
24257
- )
24258
- body = request.data
24259
- if isinstance(body, str):
24260
- body = body.encode()
24261
- try:
24262
- result = run_sync(
24263
- _host.http(
24264
- request.full_url,
24265
- request.get_method(),
24266
- [[k, v] for k, v in request.header_items()],
24267
- body,
24268
- )
24269
- )
24270
- except Exception as error:
24271
- raise urllib.error.URLError(str(error)) from None
24272
-
24273
- headers = email.message.Message()
24274
- for pair in result.headers.to_py():
24275
- headers[str(pair[0])] = str(pair[1])
24276
- payload = result.body.to_bytes()
24277
- response = urllib.response.addinfourl(
24278
- io.BytesIO(payload), headers, str(result.url), int(result.status)
24279
- )
24280
- response.msg = str(result.statusText)
24281
- return response
24282
-
24283
-
24284
- class _SbxHTTPHandler(urllib.request.HTTPHandler):
24285
- """Both schemes, from one handler.
24286
-
24287
- Pyodide builds urllib without \`ssl\`, so \`HTTPSHandler\` does not exist to
24288
- subclass \u2014 and https is the scheme most callers actually want. Serving
24289
- both from a subclass of the one handler that does exist keeps
24290
- \`build_opener\` treating this as a replacement rather than stacking it
24291
- alongside the original.
24292
- """
24293
-
24294
- def http_open(self, request):
24295
- return _fetch(request)
24296
-
24297
- def https_open(self, request):
24298
- return _fetch(request)
24299
-
24300
- https_request = urllib.request.HTTPHandler.do_request_
24301
-
24302
-
24303
- def _raw_fetch(url, method, headers, body):
24304
- """One request, through the container's network path."""
24305
- if not can_run_sync():
24306
- raise urllib.error.URLError(
24307
- "cannot make a request on this host: WebAssembly stack switching "
24308
- "(JSPI) is unavailable."
24309
- )
24310
- if isinstance(body, str):
24311
- body = body.encode()
24312
- result = run_sync(
24313
- _host.http(url, method, [[str(k), str(v)] for k, v in dict(headers).items()], body)
24314
- )
24315
- return {
24316
- "status": int(result.status),
24317
- "reason": str(result.statusText),
24318
- "headers": [(str(p[0]), str(p[1])) for p in result.headers.to_py()],
24319
- "body": result.body.to_bytes(),
24320
- "url": str(result.url),
24321
- }
24322
-
24323
-
24324
- # \u2500\u2500 Routing third-party HTTP stacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
24325
- #
24326
- # A library that brings its own transport does not go through urllib, and in
24327
- # Pyodide several of them reach JavaScript's fetch directly \u2014 which leaves the
24328
- # container's network policy behind entirely. They cannot all be patched at
24329
- # boot either, because pip installs them later.
24330
- #
24331
- # So: adapters are registered by module name and applied the moment that module
24332
- # is first imported, whenever that happens. Supporting another stack is a small
24333
- # function registered here, not a change to the machinery.
24334
-
24335
- _ADAPTERS = {}
24336
-
24337
-
24338
- def _register_adapter(name):
24339
- def decorate(fn):
24340
- _ADAPTERS[name] = fn
24341
- return fn
24342
-
24343
- return decorate
24344
-
24345
-
24346
- def _apply_adapter(name):
24347
- fn = _ADAPTERS.pop(name, None)
24348
- if fn is None:
24349
- return
24350
- try:
24351
- fn()
24352
- except Exception:
24353
- # A stack we cannot adapt must not stop the import that triggered it.
24354
- pass
24355
-
24356
-
24357
- def _install_import_hook():
24358
- real_import = builtins.__import__
24359
-
24360
- def hooked(name, globals=None, locals=None, fromlist=(), level=0):
24361
- module = real_import(name, globals, locals, fromlist, level)
24362
- root = name.split(".")[0] if level == 0 else None
24363
- if root in _ADAPTERS:
24364
- _apply_adapter(root)
24365
- return module
24366
-
24367
- builtins.__import__ = hooked
24368
-
24369
-
24370
- @_register_adapter("requests")
24371
- def _adapt_requests():
24372
- """Replace the transport, not the API.
24373
-
24374
- \`HTTPAdapter.send\` is the single seam every requests call passes through,
24375
- below sessions, redirects, cookies and retries and above the urllib3
24376
- transport that would otherwise reach the network on its own. Replacing it
24377
- leaves everything callers actually use intact.
24378
- """
24379
- import requests
24380
- from requests.adapters import HTTPAdapter
24381
- from requests.structures import CaseInsensitiveDict
24382
-
24383
- native_send = HTTPAdapter.send
24384
-
24385
- def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
24386
- if not can_run_sync():
24387
- # Without stack switching this transport cannot run at all. Falling
24388
- # back to the library's own is better than breaking requests on
24389
- # such a host \u2014 but the container's network policy is not the part
24390
- # that degrades, so a forbidden host is still refused.
24391
- if not _host.policy_allows(request.url):
24392
- raise requests.exceptions.ConnectionError(
24393
- "outbound network access is disabled for this container "
24394
- "(enable with network: { allowOutbound: true })"
24395
- )
24396
- return native_send(self, request, stream, timeout, verify, cert, proxies)
24397
- try:
24398
- result = _raw_fetch(request.url, request.method, request.headers, request.body)
24399
- except Exception as error:
24400
- raise requests.exceptions.ConnectionError(str(error)) from None
24401
- response = requests.Response()
24402
- response.status_code = result["status"]
24403
- response.reason = result["reason"]
24404
- response.headers = CaseInsensitiveDict(result["headers"])
24405
- response.url = result["url"]
24406
- response.request = request
24407
- response.raw = io.BytesIO(result["body"])
24408
- response.encoding = requests.utils.get_encoding_from_headers(response.headers)
24409
- return response
24410
-
24411
- HTTPAdapter.send = send
24412
-
24413
-
24414
- def _install_network():
24415
- urllib.request.HTTPHandler = _SbxHTTPHandler
24416
- urllib.request.HTTPSHandler = _SbxHTTPHandler
24417
- urllib.request.install_opener(urllib.request.build_opener(_SbxHTTPHandler))
24418
- _install_import_hook()
24419
- for already_imported in [n for n in _ADAPTERS if n in sys.modules]:
24420
- _apply_adapter(already_imported)
24421
-
24422
-
24423
- _install_network()
24424
- `;
24425
- async function httpForPython(slot, url, method, headers, body) {
24426
- const current = slot.current;
24427
- if (!current) throw new Error("no program is bound");
24428
- const result = await performRequest(current.ctx, new URL(url), {
24429
- method,
24430
- headers: Object.fromEntries(headers),
24431
- ...body ? { body } : {}
24432
- });
24433
- return {
24434
- status: result.status,
24435
- statusText: result.statusText,
24436
- headers: Object.entries(result.headers),
24437
- body: result.body,
24438
- url: result.url
24439
- };
24440
- }
24441
- async function resuming(py, slot, work) {
24442
- const parent = slot.current;
24443
- try {
24444
- return await work();
24445
- } finally {
24446
- if (parent && slot.current !== parent) bindProgram(py, parent);
24447
- }
24448
- }
24449
- function installSyscallBridge(py) {
24450
- const slot = py.__sbxSlot ??= { current: null };
24451
- const children = /* @__PURE__ */ new Map();
24452
- let lastChildId = 0;
24453
- py.registerJsModule("_sbx_host", {
24454
- /* Not `?.` and `??`: `buffered` returns undefined to mean "you will have
24455
- * to wait", and a nullish default would quietly turn that into end-of-file —
24456
- * which is exactly the silent EOF this bridge exists to remove. */
24457
- buffered: (size) => slot.current ? slot.current.stdin.buffered(size) : EMPTY,
24458
- read: (size) => slot.current ? slot.current.stdin.read(size) : Promise.resolve(EMPTY),
24459
- isatty: () => slot.current?.stdin.isatty() ?? false,
24460
- proc_start: (argv, cwd, env2, merge) => {
24461
- const current = slot.current;
24462
- if (!current) throw new Error("no program is bound");
24463
- const { ctx } = current;
24464
- const stdin = new Pipe();
24465
- stdin.interactive = true;
24466
- const stdout = new Pipe();
24467
- const stderr = merge ? stdout : new Pipe();
24468
- const proc = ctx.kernel.spawn(argv, {
24469
- cwd: cwd ?? ctx.cwd,
24470
- env: env2 ?? ctx.env,
24471
- cred: ctx.cred,
24472
- stdin,
24473
- stdout,
24474
- stderr
24475
- });
24476
- void proc.wait().then(() => {
24477
- stdout.end();
24478
- if (stderr !== stdout) stderr.end();
24479
- });
24480
- const id = ++lastChildId;
24481
- children.set(id, { proc, stdin, stdout, stderr });
24482
- return id;
24483
- },
24484
- proc_read: (id, which2, size) => {
24485
- const child = children.get(id);
24486
- if (!child) return Promise.resolve(EMPTY);
24487
- const pipe = which2 === 2 ? child.stderr : child.stdout;
24488
- return resuming(py, slot, async () => await pipe.read(size) ?? EMPTY);
24489
- },
24490
- proc_write: (id, text2) => {
24491
- children.get(id)?.stdin.write(text2);
24492
- },
24493
- proc_close_stdin: (id) => {
24494
- children.get(id)?.stdin.end();
24495
- },
24496
- /* Non-blocking on purpose: `poll()` must be able to say "still running". */
24497
- proc_poll: (id) => children.get(id)?.proc.exitCode ?? void 0,
24498
- proc_pid: (id) => children.get(id)?.proc.pid ?? 0,
24499
- proc_wait: (id) => {
24500
- const child = children.get(id);
24501
- if (!child) return Promise.resolve(0);
24502
- return resuming(py, slot, () => child.proc.wait());
24503
- },
24504
- proc_kill: (id, signal) => {
24505
- const child = children.get(id);
24506
- if (!child) return;
24507
- const current = slot.current;
24508
- current?.ctx.kernel.procs.signal(child.proc.pid, signal);
24509
- },
24510
- proc_release: (id) => {
24511
- children.delete(id);
24512
- },
24513
- http: (url, method, headers, body) => resuming(py, slot, () => httpForPython(slot, url, method, headers, body)),
24514
- /* So Python can enforce the container's policy even on a host where it
24515
- * cannot route the request itself. */
24516
- policy_allows: (url) => {
24517
- const current = slot.current;
24518
- if (!current) return false;
24519
- const net = current.ctx.kernel.net;
24520
- try {
24521
- return net.isLocal(new URL(url).hostname) || net.outboundAllowed(url);
24522
- } catch {
24523
- return false;
24524
- }
24525
- }
24526
- });
24527
- py.runPython(BRIDGE_PY);
24528
- py.runPython(PROCESS_PY);
24529
- py.runPython(NETWORK_PY);
24530
- py.runPython(ASYNCIO_PY);
24531
- }
24532
- function bindProgram(py, binding) {
24533
- py.__sbxSlot.current = binding;
24534
- const { ctx, stdin } = binding;
24535
- const decoder9 = new TextDecoder();
24536
- py.setStdout({
24537
- write: (buffer) => (ctx.write(decoder9.decode(buffer)), buffer.length)
24538
- });
24539
- py.setStderr({
24540
- write: (buffer) => (ctx.stderr.write(decoder9.decode(buffer)), buffer.length)
24541
- });
24542
- py.setStdin({
24543
- read: (buffer) => {
24544
- const ready = stdin.buffered(buffer.length);
24545
- if (!ready || ready.length === 0) return 0;
24546
- buffer.set(ready);
24547
- return ready.length;
24548
- }
24549
- });
24550
- }
24551
-
24552
- // src/python/cpython.ts
24553
- var RESERVED_FOR_INTERPRETER = /* @__PURE__ */ new Set(["lib", "dev", "proc"]);
24554
- var pyodideModule = null;
24555
- var indexUrl;
24556
- var moduleUrl;
24557
- var DEFAULT_BROWSER_INDEX_URL = "https://cdn.jsdelivr.net/pyodide/v0.28.3/full/";
24558
- var isNode3 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
24559
- function configureCPython(options = {}) {
24560
- if (options.indexURL !== void 0) indexUrl = options.indexURL;
24561
- if (options.moduleURL !== void 0) {
24562
- moduleUrl = options.moduleURL;
24563
- pyodideModule = null;
24564
- }
24565
- }
24566
- function importPyodide() {
24567
- if (!pyodideModule) {
24568
- if (moduleUrl) {
24569
- pyodideModule = import(
24570
- /* @vite-ignore */
24571
- /* webpackIgnore: true */
24572
- moduleUrl
24573
- );
24574
- } else if (isNode3) {
24575
- pyodideModule = nodeOnlyModule("pyodide");
24576
- } else {
24577
- pyodideModule = import(
24578
- /* @vite-ignore */
24579
- /* webpackIgnore: true */
24580
- `${DEFAULT_BROWSER_INDEX_URL}pyodide.mjs`
24581
- );
24582
- }
24583
- }
24584
- return pyodideModule;
24585
- }
24586
- async function resolveIndexUrl() {
24587
- if (indexUrl) return indexUrl;
24588
- if (!isNode3) return DEFAULT_BROWSER_INDEX_URL;
24589
- try {
24590
- const { createRequire } = await nodeBuiltin("module");
24591
- const path = await nodeBuiltin("path");
24592
- const require2 = createRequire(path.join(process.cwd(), "index.js"));
24593
- return `${path.dirname(require2.resolve("pyodide/package.json"))}/`;
24594
- } catch {
24595
- return void 0;
24596
- }
24597
- }
24598
- async function isCPythonAvailable() {
24599
- try {
24600
- await importPyodide();
24601
- return true;
24602
- } catch {
24603
- return false;
24604
- }
24605
- }
24606
- var interpreters = /* @__PURE__ */ new WeakMap();
24607
- async function interpreterFor(ctx) {
24608
- const existing = interpreters.get(ctx.vfs);
24609
- if (existing) return existing;
24610
- const starting = (async () => {
24611
- const { loadPyodide } = await importPyodide();
24612
- const resolved = await resolveIndexUrl();
24613
- const py = await loadPyodide({
24614
- ...resolved ? { indexURL: resolved } : {},
24615
- /* Output is rebound per program; these only catch anything printed
24616
- * while the interpreter is still starting. */
24617
- stdout: () => {
24618
- },
24619
- stderr: () => {
24620
- }
24621
- });
24622
- mountContainerDirs(py, ctx);
24623
- installRunner(py);
24624
- installSyscallBridge(py);
24625
- return py;
24626
- })();
24627
- interpreters.set(ctx.vfs, starting);
24628
- try {
24629
- return await starting;
24630
- } catch (error) {
24631
- interpreters.delete(ctx.vfs);
24632
- throw error;
24633
- }
24634
- }
24635
- function installRunner(py) {
24636
- py.__sbxRun = py.runPython(`
24637
- from pyodide.code import eval_code_async
24638
-
24639
- async def __sbx_run(source, scope):
24640
- try:
24641
- await eval_code_async(source, globals=scope)
24642
- return 0
24643
- except SystemExit as exit:
24644
- code = exit.code
24645
- if code is None:
24646
- return 0
24647
- return code if isinstance(code, int) else 1
24648
-
24649
- __sbx_run
24650
- `);
24651
- }
24652
- function mountContainerDirs(py, ctx) {
24653
- const mounted = py.__sbxMounted ??= /* @__PURE__ */ new Set();
24654
- let entries;
24655
- try {
24656
- entries = ctx.vfs.readdirWithTypes("/", ctx.cred);
24657
- } catch {
24658
- return;
24659
- }
24660
- for (const entry of entries) {
24661
- if (entry.kind !== "directory") continue;
24662
- if (RESERVED_FOR_INTERPRETER.has(entry.name)) continue;
24663
- if (mounted.has(entry.name)) continue;
24664
- try {
24665
- mountContainerFs(py.FS, {
24666
- vfs: ctx.vfs,
24667
- cred: ctx.cred,
24668
- mountAt: `/${entry.name}`
24669
- });
24670
- mounted.add(entry.name);
24671
- } catch {
24672
- }
24673
- }
24674
- }
24675
- function bootstrap(py, ctx, argv, scriptDir) {
24676
- const paths = [
24677
- ...scriptDir ? [scriptDir] : [""],
24678
- "/workspace",
24679
- "/usr/lib/python3",
24680
- "/usr/lib/python3/site-packages",
24681
- "/usr/local/lib/python3/site-packages"
24682
- ];
24683
- py.runPython(`
24684
- import sys, os, importlib
24685
- sys.argv[:] = ${JSON.stringify(argv)}
24686
- for __p in reversed(${JSON.stringify(paths)}):
24687
- if __p and __p not in sys.path:
24688
- sys.path.insert(0, __p)
24689
- elif __p == "" and "" not in sys.path:
24690
- sys.path.insert(0, "")
24691
- try:
24692
- del __p
24693
- except NameError:
24694
- pass
24695
- os.environ.clear()
24696
- os.environ.update(${JSON.stringify(ctx.env)})
24697
- importlib.invalidate_caches()
24698
- `);
24699
- try {
24700
- py.FS.chdir(ctx.cwd);
24701
- } catch {
24702
- }
24703
- }
24704
- function reportError(ctx, error) {
24705
- const message = error instanceof Error ? error.message : String(error);
24706
- const systemExit = /SystemExit:?\s*(-?\d+)?/.exec(message);
24707
- if (systemExit) {
24708
- return systemExit[1] !== void 0 ? Number(systemExit[1]) & 255 : 0;
24709
- }
24710
- if (/KeyboardInterrupt/.test(message)) {
24711
- ctx.stderr.write("KeyboardInterrupt\n");
24712
- return 130;
24713
- }
24714
- const text2 = message.replace(/^PythonError:\s*/, "");
24715
- ctx.stderr.write(text2.endsWith("\n") ? text2 : `${text2}
24716
- `);
24717
- return 1;
24718
- }
24719
- async function runCPythonProgram(ctx, source, argv, scriptDir = null) {
24720
- let py;
24721
- try {
24722
- py = await interpreterFor(ctx);
24723
- } catch (error) {
24724
- ctx.stderr.write(
24725
- `python3: CPython is unavailable in this host (${error instanceof Error ? error.message : String(error)})
24726
- `
24727
- );
24728
- return { exitCode: 127 };
24729
- }
24730
- mountContainerDirs(py, ctx);
24731
- try {
24732
- await py.loadPackagesFromImports(source, {
24733
- messageCallback: () => {
24734
- },
24735
- errorCallback: () => {
24736
- }
24737
- });
24738
- } catch {
24739
- }
24740
- const stdinHost = createStdinHost(ctx);
24741
- await stdinHost.prime();
24742
- bindProgram(py, { ctx, stdin: stdinHost });
24743
- try {
24744
- bootstrap(py, ctx, argv, scriptDir);
24745
- } catch (error) {
24746
- return { exitCode: reportError(ctx, error) };
24747
- }
24748
- const globals = py.globals.get("dict")();
24749
- try {
24750
- globals.set("__name__", "__main__");
24751
- const exitCode = await py.__sbxRun(source, globals);
24752
- return { exitCode: Number(exitCode) & 255 };
24753
- } catch (error) {
24754
- return { exitCode: reportError(ctx, error) };
24755
- } finally {
24756
- try {
24757
- globals.destroy();
24758
- } catch {
24759
- }
24760
- }
24761
- }
24762
- async function cpythonVersion(ctx) {
24763
- try {
24764
- const py = await interpreterFor(ctx);
24765
- return String(py.runPython("import sys; sys.version.split()[0]"));
24766
- } catch {
24767
- return null;
24768
- }
24769
- }
24770
- async function runCPythonRepl(ctx) {
24771
- let py;
24772
- try {
24773
- py = await interpreterFor(ctx);
24774
- } catch (error) {
24775
- ctx.stderr.write(`python3: Pyodide is unavailable (${error instanceof Error ? error.message : String(error)})
24776
- `);
24777
- return 127;
24778
- }
24779
- mountContainerDirs(py, ctx);
24780
- bootstrap(py, ctx, [""], null);
24781
- bindProgram(py, { ctx, stdin: createStdinHost(ctx) });
24782
- ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
24783
- ctx.line('Type "help()" for more information.');
24784
- let source = "";
24785
- for (; ; ) {
24786
- ctx.write(source ? "... " : ">>> ");
24787
- const line = await ctx.stdin.readLine();
24788
- if (line === null) {
24789
- ctx.line("");
24790
- break;
24791
- }
24792
- if (!source && ["exit()", "quit()"].includes(line.trim())) break;
24793
- source += `${source ? "\n" : ""}${line}`;
24794
- if (/[:\\]\s*$/.test(line) || source.includes("\n") && line.trim() !== "" && /^\s+/.test(line)) continue;
24795
- try {
24796
- await py.runPythonAsync(source);
24797
- } catch (error) {
24798
- reportError(ctx, error);
24799
- }
24800
- source = "";
24801
- }
24802
- return 0;
24803
- }
24804
- var PIP_PY = `
24805
- import json, re
24806
-
24807
- import micropip
24808
-
24809
- _MISSING = re.compile(r"Can't find a pure Python 3 wheel for '([^']+)'")
24810
- _EXTRAS = re.compile(r"^\\s*([A-Za-z0-9._-]+)\\s*\\[([^\\]]+)\\](.*)$")
24811
-
24812
-
24813
- def _name_of(spec):
24814
- return re.split(r"[\\[<>=!~;\\s]", spec.strip(), 1)[0]
24815
-
24816
-
24817
- def _installed_version(name):
24818
- try:
24819
- found = micropip.list()[name.replace("_", "-").lower()]
24820
- return getattr(found, "version", None)
24821
- except Exception:
24822
- return None
24823
-
24824
-
24825
- async def _install_one(spec):
24826
- """Install one requirement, retrying without extras if they are impossible."""
24827
- attempt = spec
24828
- dropped = None
24829
- while True:
24830
- try:
24831
- await micropip.install(attempt)
24832
- except ValueError as error:
24833
- message = str(error)
24834
- missing = _MISSING.findall(message)
24835
- extras = _EXTRAS.match(attempt)
24836
- if missing and extras and dropped is None:
24837
- dropped = [e.strip() for e in extras.group(2).split(",")]
24838
- attempt = extras.group(1) + extras.group(3)
24839
- continue
24840
- return {
24841
- "spec": spec, "ok": False, "missing": missing,
24842
- "reason": message.strip().split("\\n")[0],
24843
- }
24844
- except Exception as error:
24845
- return {"spec": spec, "ok": False, "missing": [], "reason": str(error).strip().split("\\n")[0]}
24846
-
24847
- name = _name_of(attempt)
24848
- return {
24849
- "spec": spec, "ok": True, "name": name,
24850
- "version": _installed_version(name), "dropped": dropped,
24851
- }
24852
-
24853
-
24854
- async def __sbx_pip(specs_json):
24855
- results = []
24856
- for spec in json.loads(specs_json):
24857
- try:
24858
- results.append(await _install_one(spec))
24859
- except Exception as error:
24860
- results.append({"spec": spec, "ok": False, "missing": [], "reason": str(error)})
24861
- return json.dumps(results)
24862
-
24863
- __sbx_pip
24864
- `;
24865
- var micropip = defineCommand({
24866
- name: "micropip",
24867
- path: "/usr/bin/micropip",
24868
- aliases: ["pip", "pip3"],
24869
- summary: "install Python packages into the running interpreter",
24870
- usage: "micropip install <package>...",
24871
- async run(ctx) {
24872
- if (usingOwnedPython()) {
24873
- ctx.warn(
24874
- "pip here is micropip, which installs into the Pyodide interpreter \u2014 not the source-built CPython this container is running, so the packages would not be importable.\nRun the container with the pyodide backend if you need micropip, or wait for venv and pip support on the owned runtime (see docs/python/release-gates.md, M4)."
24875
- );
24876
- return 1;
24877
- }
24878
- const [action, ...args] = ctx.args;
24879
- if (action === "--version" || action === "-V") {
24880
- ctx.line("pip (micropip, Pyodide)");
24881
- return 0;
24882
- }
24883
- if (action !== "install") {
24884
- ctx.line("usage: pip install [-r requirements.txt] <package>...");
24885
- return action === void 0 ? 1 : 0;
24886
- }
24887
- const packages = args.filter((arg) => !arg.startsWith("-"));
24888
- const requirementIndex = args.findIndex((arg) => arg === "-r" || arg === "--requirement");
24889
- if (requirementIndex >= 0) {
24890
- const file3 = args[requirementIndex + 1];
24891
- if (!file3) {
24892
- ctx.stderr.write("pip: option -r requires a file\n");
24893
- return 2;
24894
- }
24895
- try {
24896
- packages.splice(packages.indexOf(file3), 1);
24897
- packages.push(...ctx.vfs.readText(ctx.path(file3), ctx.cred).split(/\r?\n/).map((line) => line.replace(/#.*$/, "").trim()).filter(Boolean));
24898
- } catch {
24899
- ctx.stderr.write(`pip: could not open requirements file '${file3}'
24900
- `);
24901
- return 1;
24902
- }
24903
- }
24904
- if (packages.length === 0) {
24905
- ctx.stderr.write("pip: no packages specified\n");
24906
- return 1;
24907
- }
24908
- if (!ctx.kernel.net.outboundAllowed("https://pypi.org")) {
24909
- ctx.stderr.write(
24910
- "pip: outbound network access is disabled for this container (enable with network: { allowOutbound: true })\n"
24911
- );
24912
- return 1;
24913
- }
24914
- let py;
24915
- try {
24916
- py = await interpreterFor(ctx);
24917
- } catch {
24918
- ctx.stderr.write("pip: Pyodide is unavailable in this host\n");
24919
- return 127;
24920
- }
24921
- let outcomes;
24922
- try {
24923
- await py.loadPackage("micropip");
24924
- for (const name of packages) ctx.line(`Collecting ${name}`);
24925
- const install2 = py.runPython(PIP_PY);
24926
- outcomes = JSON.parse(String(await install2(JSON.stringify(packages))));
24927
- } catch (error) {
24928
- ctx.stderr.write(
24929
- `pip: installation failed: ${error instanceof Error ? error.message : String(error)}
24930
- `
24931
- );
24932
- return 1;
24933
- }
24934
- const installed2 = [];
24935
- for (const outcome of outcomes) {
24936
- if (outcome.ok) {
24937
- installed2.push(
24938
- outcome.version ? `${outcome.name}-${outcome.version}` : String(outcome.name)
24939
- );
24940
- if (outcome.dropped?.length) {
24941
- ctx.stderr.write(
24942
- ` WARNING: ${outcome.name}: the ${outcome.dropped.map((extra) => `'${extra}'`).join(", ")} extra needs native code with no WebAssembly build; installed ${outcome.name} without it
24943
- `
24944
- );
24945
- }
24946
- continue;
24947
- }
24948
- const missing = outcome.missing ?? [];
24949
- ctx.stderr.write(
24950
- missing.length > 0 ? `ERROR: could not install ${outcome.spec}: no WebAssembly build exists for ${missing.map((name) => name.split(/[<>=!~;\s]/)[0]).join(", ")}
24951
- ` : `ERROR: could not install ${outcome.spec}: ${outcome.reason ?? "unknown error"}
24952
- `
24953
- );
24954
- }
24955
- if (installed2.length > 0) ctx.line(`Successfully installed ${installed2.join(" ")}`);
24956
- return outcomes.every((outcome) => outcome.ok) ? 0 : 1;
24957
- }
24958
- });
24959
-
24960
- // src/python/python.ts
24961
- var PYTHON_VERSION = "3.13";
24962
- function configurePython(options = {}) {
24963
- configureCPython({ indexURL: options.indexURL, moduleURL: options.pyodideURL });
24964
- setPythonBackend({
24965
- ...options.backend !== void 0 ? { backend: options.backend } : {},
24966
- ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
24967
- ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
24968
- ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {},
24969
- ...options.buildFromSource !== void 0 ? { buildFromSource: options.buildFromSource } : {}
24970
- });
24971
- }
24972
- var isPythonAvailable = isCPythonAvailable;
24973
- var python = defineCommand({
24974
- name: "python3",
24975
- path: "/usr/bin/python3",
24976
- aliases: ["python"],
24977
- summary: "run Python using CPython (Pyodide)",
24978
- usage: "python3 [-c command | -m module | script.py] [arguments]",
24979
- manual: `Python is CPython compiled to WebAssembly by Pyodide. Scripts use
24980
- the container's virtual filesystem, modules in /workspace are importable, and
24981
- compatible packages can be installed with pip (micropip).`,
24982
- async run(ctx) {
24983
- if (usingOwnedPython()) {
24984
- try {
24985
- return await runOwnedPython(ctx, withTopLevelAwaitArgs(ctx.args));
24986
- } catch (error) {
24987
- return ctx.fail(error.message ?? String(error));
24988
- }
24989
- }
24990
- const argv = ctx.args;
24991
- let i = 0;
24992
- let command;
24993
- let moduleName;
24994
- let script;
24995
- for (; i < argv.length; i++) {
24996
- const arg = argv[i];
24997
- if (arg === "-V" || arg === "--version") {
24998
- ctx.line(`Python ${await cpythonVersion(ctx) ?? PYTHON_VERSION}`);
24999
- return 0;
25000
- }
25001
- if (arg === "-h" || arg === "--help") {
25002
- printHelp2(ctx);
25003
- return 0;
25004
- }
25005
- if (arg === "-c") {
25006
- command = argv[++i] ?? "";
25007
- i++;
25008
- break;
25009
- }
25010
- if (arg === "-m") {
25011
- moduleName = argv[++i] ?? "";
25012
- i++;
25013
- break;
25014
- }
25015
- if (arg === "-") {
25016
- script = "-";
25017
- i++;
25018
- break;
25019
- }
25020
- if (["-i", "-u", "-B", "-E", "-s", "-S", "-O"].includes(arg)) continue;
25021
- if (arg.startsWith("-")) continue;
25022
- script = arg;
25023
- i++;
25024
- break;
25025
- }
25026
- const rest = argv.slice(i);
25027
- if (command !== void 0) {
25028
- return (await runCPythonProgram(ctx, withTopLevelAwait(command), ["-c", ...rest], null)).exitCode;
25029
- }
25030
- if (moduleName !== void 0) {
25031
- const program = `
25032
- import runpy, sys
25033
- sys.argv = ${JSON.stringify([moduleName, ...rest])}
25034
- try:
25035
- runpy.run_module(${JSON.stringify(moduleName)}, run_name="__main__", alter_sys=True)
25036
- except ImportError:
25037
- print("/usr/bin/python3: No module named " + ${JSON.stringify(moduleName)}, file=sys.stderr)
25038
- raise SystemExit(1)
25039
- `;
25040
- return (await runCPythonProgram(ctx, program, [moduleName, ...rest], null)).exitCode;
25041
- }
25042
- if (script === void 0) {
25043
- if (ctx.stdin.isTTY) return runCPythonRepl(ctx);
25044
- const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
25045
- return source2.trim() ? (await runCPythonProgram(ctx, source2, ["", ...rest])).exitCode : 0;
25046
- }
25047
- if (script === "-") {
25048
- const source2 = new TextDecoder().decode(await ctx.stdin.readAll());
25049
- return (await runCPythonProgram(ctx, source2, ["-", ...rest])).exitCode;
25050
- }
25051
- const abs = ctx.path(script);
25052
- let source;
25053
- try {
25054
- source = ctx.vfs.readText(abs, ctx.cred);
25055
- } catch {
25056
- ctx.stderr.write(`${ctx.name}: can't open file '${abs}': [Errno 2] No such file or directory
25057
- `);
25058
- return 2;
25059
- }
25060
- return (await runCPythonProgram(ctx, source, [abs, ...rest], dirname(abs))).exitCode;
25061
- }
25062
- });
25063
- function withTopLevelAwait(source) {
25064
- if (!/\bawait\b/.test(source)) return source;
25065
- return `import ast as _sbx_ast, inspect as _sbx_inspect
25066
- _sbx_src = ${JSON.stringify(source)}
25067
- try:
25068
- _sbx_code = compile(_sbx_src, "<string>", "exec")
25069
- except SyntaxError:
25070
- _sbx_code = compile(_sbx_src, "<string>", "exec", _sbx_ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
25071
- _sbx_async = bool(_sbx_code.co_flags & _sbx_inspect.CO_COROUTINE)
25072
- _sbx_globals = globals()
25073
- for _sbx_name in ("_sbx_ast", "_sbx_inspect", "_sbx_src", "_sbx_name"):
25074
- _sbx_globals.pop(_sbx_name, None)
25075
- if _sbx_async:
25076
- import asyncio as _sbx_asyncio
25077
- _sbx_asyncio.run(eval(_sbx_code, _sbx_globals))
25078
- else:
25079
- exec(_sbx_code, _sbx_globals)
25080
- `;
25081
- }
25082
- function withTopLevelAwaitArgs(args) {
25083
- for (let i = 0; i < args.length; i++) {
25084
- const arg = args[i];
25085
- if (arg === "-c") {
25086
- if (i + 1 >= args.length) return args;
25087
- const rewritten = withTopLevelAwait(args[i + 1]);
25088
- if (rewritten === args[i + 1]) return args;
25089
- const copy = [...args];
25090
- copy[i + 1] = rewritten;
25091
- return copy;
25092
- }
25093
- if (arg === "-m" || !arg.startsWith("-") || arg === "-") return args;
25094
- }
25095
- return args;
25096
- }
25097
- function printHelp2(ctx) {
25098
- ctx.line("usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ...");
25099
- ctx.line("-c cmd : program passed in as string");
25100
- ctx.line("-m mod : run library module as a script");
25101
- ctx.line("-V : print the Python version and exit");
25102
- }
25103
- function pythonCommands() {
25104
- return usingOwnedPython() ? [python, pipCommand] : [python, micropip];
25105
- }
25106
-
25107
23896
  // src/tools/ffmpeg.ts
25108
23897
  init_binary();
25109
23898
  var INSTALL_HINT = "ffmpeg is not installed in this container.\nThe FFmpeg runtime ships separately because it is a ~31MB WebAssembly build:\n npm install @ffmpeg/core";
@@ -25795,8 +24584,8 @@ function renameShadowedExports(source) {
25795
24584
  for (const [key, value] of Object.entries(node2)) {
25796
24585
  if (key === "type" || key === "start" || key === "end" || skip.includes(key)) continue;
25797
24586
  if (Array.isArray(value)) {
25798
- for (const item of value) if (isNode4(item)) visit(item, childScope);
25799
- } else if (isNode4(value)) {
24587
+ for (const item of value) if (isNode3(item)) visit(item, childScope);
24588
+ } else if (isNode3(value)) {
25800
24589
  visit(value, childScope);
25801
24590
  }
25802
24591
  }
@@ -25820,9 +24609,9 @@ function blockNames2(body) {
25820
24609
  for (const declarator of node2.declarations) {
25821
24610
  collectPattern2(declarator.id, names);
25822
24611
  }
25823
- } else if (node2.type === "ClassDeclaration" && isNode4(node2.id)) {
24612
+ } else if (node2.type === "ClassDeclaration" && isNode3(node2.id)) {
25824
24613
  names.add(node2.id.name);
25825
- } else if (node2.type === "FunctionDeclaration" && isNode4(node2.id)) {
24614
+ } else if (node2.type === "FunctionDeclaration" && isNode3(node2.id)) {
25826
24615
  names.add(node2.id.name);
25827
24616
  }
25828
24617
  }
@@ -25833,10 +24622,10 @@ function collectVars2(nodes, names) {
25833
24622
  for (const item of nodes) collectVars2(item, names);
25834
24623
  return;
25835
24624
  }
25836
- if (!isNode4(nodes)) return;
24625
+ if (!isNode3(nodes)) return;
25837
24626
  const node2 = nodes;
25838
24627
  if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression" || node2.type === "ArrowFunctionExpression") {
25839
- if (isNode4(node2.id)) names.add(node2.id.name);
24628
+ if (isNode3(node2.id)) names.add(node2.id.name);
25840
24629
  return;
25841
24630
  }
25842
24631
  if (node2.type === "VariableDeclaration" && node2.kind === "var") {
@@ -25850,7 +24639,7 @@ function collectVars2(nodes, names) {
25850
24639
  }
25851
24640
  }
25852
24641
  function collectPattern2(node2, names) {
25853
- if (!isNode4(node2)) return;
24642
+ if (!isNode3(node2)) return;
25854
24643
  switch (node2.type) {
25855
24644
  case "Identifier":
25856
24645
  names.add(node2.name);
@@ -25874,7 +24663,7 @@ function collectPattern2(node2, names) {
25874
24663
  }
25875
24664
  }
25876
24665
  function isExports(value) {
25877
- return isNode4(value) && value.type === "Identifier" && value.name === NAME;
24666
+ return isNode3(value) && value.type === "Identifier" && value.name === NAME;
25878
24667
  }
25879
24668
  function freshName(source) {
25880
24669
  let name = "__sandboxedjs_exports";
@@ -25882,7 +24671,7 @@ function freshName(source) {
25882
24671
  while (source.includes(name)) name = `__sandboxedjs_exports${++suffix}`;
25883
24672
  return name;
25884
24673
  }
25885
- function isNode4(value) {
24674
+ function isNode3(value) {
25886
24675
  return typeof value === "object" && value !== null && typeof value.type === "string";
25887
24676
  }
25888
24677
  var NOTHING2 = [];
@@ -26413,7 +25202,7 @@ var apt = defineCommand({
26413
25202
  const provided = {
26414
25203
  nodejs: { version: NODE_VERSION.replace(/^v/, ""), description: "Node.js event-based server-side JavaScript engine" },
26415
25204
  npm: { version: NPM_VERSION, description: "package manager for Node.js" },
26416
- python3: { version: PYTHON_VERSION, description: "CPython interpreter powered by Pyodide" },
25205
+ python3: { version: PYTHON_VERSION, description: "CPython interpreter" },
26417
25206
  "python3-pip": { version: "24.0", description: "Python package installer" },
26418
25207
  coreutils: { version: "9.4", description: "GNU core utilities" },
26419
25208
  grep: { version: "3.11", description: "GNU grep, egrep and fgrep" },
@@ -26517,7 +25306,7 @@ var dpkg = defineCommand({
26517
25306
  ctx.line("||/ Name Version Architecture Description");
26518
25307
  ctx.line("+++-==============-============-============-=================================");
26519
25308
  ctx.line(`ii nodejs ${NODE_VERSION.replace(/^v/, "").padEnd(12)} amd64 Node.js JavaScript runtime`);
26520
- ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython (Pyodide) interpreter`);
25309
+ ctx.line(`ii python3 ${PYTHON_VERSION.padEnd(12)} wasm32 CPython interpreter`);
26521
25310
  return 0;
26522
25311
  }
26523
25312
  ctx.line("dpkg 1.22.6 (amd64)");
@@ -35429,6 +34218,6 @@ function createFrontendPlaywright(options = {}) {
35429
34218
  // src/index.ts
35430
34219
  var src_default = createContainer;
35431
34220
 
35432
- export { ArithError, BinaryExecutionRegistry, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, FrontendAutomationUnsupported, FrontendLocator, FrontendPage, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MANIFEST_FORMAT, MANIFEST_SCHEMA_VERSION, MemoryVolume, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, WasiError, WasiExit, WasiHost, WorkerRuntimePod, allCommands, applyChmod, binaryDigest, braceExpand, buildRootfs, builtinNames, captureStdio, configureCPython, configurePython, containerTarget, createChildProcessModule, createContainer, createContext, createCoreModules, createFrontendPlaywright, createGitCommand, createOriginalX64Backends, createPreview, createSqlCommand, createTranslationBackend, createWasmCompatibilityBackend, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, extractNpmTarball, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, inspectElf, installUserland, installWasmCommands, isBuiltinName, isCPythonAvailable, isElfBinary, isPythonAvailable, isSysError, isWasmBinary, looksLikeEsm, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, renderInto, resetPidCounter, runWasi, shellQuote, startRuntimeWorker, strerror, syncChannelSupported, transformEsm, unameInfo, validateManifest, wasi as wasiCommand };
34221
+ export { ArithError, BinaryExecutionRegistry, BufferSink, CallbackSink, CleanPackageInstaller, CommandRegistry, CommonJsEngine, Container, ContainerFs, ERRNO, FileInput, FileOutput, FrontendAutomationUnsupported, FrontendLocator, FrontendPage, IncompleteInputError, KERNEL_NAME, KERNEL_RELEASE, Kernel, LocalRuntimePod, MANIFEST_FORMAT, MANIFEST_SCHEMA_VERSION, MemoryVolume, NODE_VERSION, NPM_VERSION, NetworkStack, NullInput, NullOutput, OS_RELEASE, PYTHON_VERSION, Pipe, Process, ProcessTable, ROOT_CRED, SIGNALS, SIGNAL_NAMES, Session, Shell, ShellExit, Lexer as ShellLexer, ShellSyntaxError, Stats, SysError, TeeOutput, Terminal, UserDatabase, Variables, Vfs, VirtualHttpRouter, VirtualHttpServer, VirtualIncomingMessage, VirtualServerResponse, WASM_ALIASES, WasiError, WasiExit, WasiHost, WorkerRuntimePod, allCommands, applyChmod, binaryDigest, braceExpand, buildRootfs, builtinNames, captureStdio, configurePython, containerTarget, createChildProcessModule, createContainer, createContext, createCoreModules, createFrontendPlaywright, createGitCommand, createOriginalX64Backends, createPreview, createSqlCommand, createTranslationBackend, createWasmCompatibilityBackend, src_default as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, extractNpmTarball, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, inspectElf, installUserland, installWasmCommands, isBuiltinName, isElfBinary, isPythonAvailable, isSysError, isWasmBinary, looksLikeEsm, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path_exports as posixPath, renderInto, resetPidCounter, runWasi, shellQuote, startRuntimeWorker, strerror, syncChannelSupported, transformEsm, unameInfo, validateManifest, wasi as wasiCommand };
35433
34222
  //# sourceMappingURL=index.js.map
35434
34223
  //# sourceMappingURL=index.js.map