AphrodyBun GitHub

Runtime docs 路 Runtime 路 Process & System

Python

Run Python files and the python CLI through a CPython host, embed CPython in the JavaScript process with bun:python, and compile Python with bun compile.

Bun runs Python through a separately installed CPython host library (bun_python_host, ABI 1) that it loads on demand. The host is built from packages/bun-python-native with cargo build -p bun-python-host --release. JavaScript that never touches Python never loads it. Once the host is configured, bun app.py and bun python run CPython in the Bun process, and bun:python calls the same interpreter from JavaScript. Python packages and projects are managed with the embedded UV through bun uv (see Buv and PyJS).

export BUN_PYTHON_HOST_LIBRARY=/opt/bun-python/lib/libbun_python_host.so
export BUN_PYTHON_LIBPYTHON=/opt/python/lib/libpython3.13.so
bun app.py --flag            # a Python file, arguments passed through
bun python -c "print('hi')"  # the python CLI: -c, -m, - (stdin)

Configure the host

Python dispatch is enabled only when BUN_PYTHON_HOST_LIBRARY is set. Without it, .py files go through the normal Bun resolution and JavaScript keeps its usual startup.

VariablePurpose
BUN_PYTHON_HOST_LIBRARYPath to the bun_python_host shared library. Required. Its bun_py_abi_version() must return 1.
VIRTUAL_ENVAn activated virtual environment. Takes precedence over a project .venv and over the default interpreter below.
BUN_PYTHON_LIBPYTHONPath to the CPython shared library (libpython3.x.so, .dylib or python3x.dll) of the default interpreter. Optional inside a virtual environment.
BUN_PYTHON_EXECUTABLEsys.executable of the default interpreter. Takes precedence over BUV_RUNTIME.
BUV_RUNTIMEA CPython prefix (bin/python3, or python.exe on Windows). On Linux and macOS it is otherwise derived from the lib/ directory of BUN_PYTHON_LIBPYTHON.

On Windows, outside a virtual environment, set BUV_RUNTIME or BUN_PYTHON_EXECUTABLE: the prefix cannot be derived from the DLL path.

Run a Python project

Inside a project, the environment selects the interpreter. An activated VIRTUAL_ENV wins; otherwise the nearest .venv of the current directory or one of its parents, where bun uv sync creates it, is used. Its bin/python (Scriptspython.exe on Windows) becomes sys.executable, and the matching shared library is found from the home and version (or version_info) keys of pyvenv.cfg: python3XY.dll in home on Windows, lib/libpython3.X.so (.dylib) beside home elsewhere. If it is not there, BUN_PYTHON_LIBPYTHON is used, and a library of another Python version is refused before CPython starts.

git clone https://github.com/nousresearch/hermes-agent && cd hermes-agent
bun uv sync                      # creates .venv with the project's Python and dependencies
bun python -m hermes_cli.main --version
bun run hermes --help            # the project's console script, in the Bun process

bun run <name> runs a console script of the selected environment (bin/<name>, or the Scripts<name>.exe launcher on Windows) in the hosted interpreter, after package.json scripts and files and before node_modules/.bin and PATH. A file without a suffix whose #! line names python, python3 or python3.X (directly or through env) also runs as Python.

Run Python files and the CLI

A path ending in .py or .pyw runs in CPython, with Unicode arguments, sys.argv, the script directory on sys.path, the selected interpreter and the exit status preserved. bun python (or bun python3) accepts CPython's command line.

bun ./script.py 茅t茅馃悕                 # sys.argv[1] == "茅t茅馃悕"
bun run script.py                     # same through bun run
bun python -c "import sys; print(sys.version)"
bun python -m http.server 8000
echo "print('from stdin')" | bun python -

Tracebacks are printed by CPython and an uncaught exception exits with a non-zero status. A missing or incompatible host fails before any Python source is evaluated.

Embed CPython with bun:python

bun:python loads the host through bun:ffi and runs code in the __main__ module of an interpreter inside the current process (os.getpid() equals process.pid). The modules buv:python and pyjs:python are aliases.

python.ts
import { Python, PythonError } from "bun:python";

using py = Python.open();
console.log(py.version()); // Py_GetVersion()

py.exec`import math; value = {"text": "caf茅馃悕", "n": 42}`;
console.log(py.evalJSON("value")); // { text: "caf茅馃悕", n: 42 }
console.log(py.evalBatch(["value['n']", "math.sqrt(2)"])); // ["42", "1.4142135623730951"]
console.log(py.eval("math.sqrt(2)")); // "1.4142135623730951" (str() of the result)
console.log(py.call("platform", "system")); // module.function(), returns str()
py.exec`shared = bytearray(b"abc")`;
const shared = new Uint8Array(py.buffer("shared"));
shared[0] = 122; // Python now sees bytearray(b"zbc") without a copy

try {
  py.eval("1 / 0");
} catch (error) {
  if (error instanceof PythonError) console.log(error.status, error.message); // -4, "... division by zero"
}
MethodDescription
Python.open(options?)Loads the host and initialises the interpreter, or attaches to a running one. started tells whether this handle owns it.
run(code), exec`code` Runs statements in __main__. The tagged template takes raw source and refuses interpolation.
eval(expression)Evaluates an expression and returns str(result).
evalJSON(expression)Evaluates an expression, serialises it with Python's json module and parses it in JavaScript.
evalBatch(expressions)Evaluates up to 1024 expressions in one GIL/FFI crossing and returns their string forms.
call(module, fn, arg?)Calls module.fn(arg) (without an argument when arg is omitted) and returns str(result).
buffer(expression)Pins a contiguous writable Python buffer and returns a zero-copy ArrayBuffer; its GC releases the native lease.
version()Py_GetVersion().
close() / usingReleases this client lease; the last client finalises an interpreter started by the host, then closes its library handle.

Python.open() options:

  • libraryPath: a host library to load instead of BUV_PYTHON_HOST_LIBRARY / BUN_PYTHON_HOST_LIBRARY or a host embedded in a compiled executable.
  • libpython: the CPython library, or null to use one already loaded. By default it comes from BUV_PYTHON_LIBPYTHON, BUN_PYTHON_LIBPYTHON or APHRODY_LIBPYTHON, then from the active buv artifact (BUV_RUNTIME, or $BUV_HOME/runtime/<target>/current, default ~/.buv).

buffer() accepts writable contiguous exporters such as bytearray and writable contiguous memoryview objects. Keep the Python handle open while any returned buffer is reachable. close() refuses to unload the host while a lease is active, and remains usable after that refusal.

The helpers pythonHostLibraryPath(), buvLibpython() and libpythonIn(directory) expose this resolution.

Errors

Every failure throws a PythonError with a status from PyStatus:

PyStatusValueMeaning
Ok0success
Arg-1invalid argument (for example a NUL character)
Load-2the host or libpython could not be loaded
State-3closed handle or interpreter state error
Python-4a Python exception; the message carries it
Panic-5a panic inside the host
Unsupported-6host ABI mismatch or unsupported operation

Asynchronous calls

Python.async() (or PythonAsync.open()) runs the interpreter in a Worker of the same process, so long Python calls do not block the event loop. Calls are queued in order (up to 256 pending) and every method returns a promise.

async.ts
import { Python } from "bun:python";

await using py = await Python.async();
await py.exec`import os; value = 40`;
const [a, b] = await Promise.all([py.eval("value + 2"), py.eval("value + 3")]);
console.log(a, b); // "42" "43"

Compile Python

bun compile <entry.py> (also buv compile and pyjs compile) compiles Python with an isolated native compiler resolved through the embedded UV: Cython 3.3.0 by default, or Nuitka 4.2.2.

bun compile app.py                                  # native executable (Cython)
bun compile app.py --backend nuitka --onefile       # one-file Nuitka executable
bun compile mod.py --format shared --module-name mod # CPython extension exporting PyInit_mod
bun compile app.py --zipapp                         # executable archive
bun compile --help
FlagDescription
--format exe|shared|wasmProduct kind. shared exports the PyInit_* extension ABI.
--outfile <path>Output path. An existing product is kept unless --force is given.
--backend cython|nuitkaNative compiler (default Cython).
--module-name <name>Import name of the extension and of its PyInit_* symbol.
--onefileNuitka standalone executable in a single file.
--target native|wasmTarget platform (default native).
--bytecode, --zipappBytecode or executable-archive products.
--db <bun_python.sqlite>Record each compilation in a SQLite database.
--offlineResolve compiler packages from the UV cache only.
--forceReplace an existing build product.

bun build app.py with the same flags takes the same path.

Cython executables need CPython and its standard library at run time. WASM output needs a real CPython WASI toolchain and fails without one; it never writes a partial artifact. Compilation never overwrites its source file.

PyJS files

.pyjs, .pyts and .pytsx modules use Bun's JavaScript, TypeScript and TSX loaders. Python code inside them still goes through bun:python explicitly.

entry.pyts
import { value } from "./value"; // value.pyjs, value.pyts or value.pytsx
console.log(value);