Python SDK for VoidRun AI Sandbox
Project description
VoidRun Python SDK
Python client for VoidRun AI sandboxes: run commands, manage files, use pseudo-terminals, stream output, and execute multi-language code in isolated environments.
The high-level API is aligned with the official TypeScript SDK (create_sandbox, list_sandboxes, remove_sandbox, Sandbox.run_code, CodeExecutionResult, and shared defaults).
Table of contents
- Features
- Requirements
- Installation
- Configuration
- Quick start
- Parity with the TypeScript SDK
- Core concepts
- Code execution and interpreter
- Background commands
- File system
- File watching (async)
- Pseudo-terminal (PTY)
- API reference (summary)
- Runnable examples
- Development and tests
- Building and publishing
- Error handling
- Troubleshooting
- Contributing, license, support
Features
- Sandbox lifecycle: Create, list, fetch, start/stop/pause/resume, and remove sandboxes (sync and async).
- Shell execution:
execwith optionalExecRequest; SSE streaming viaexec_stream. - Code interpreter:
run_code/interpreter.runreturn a structuredCodeExecutionResult(stdout, stderr, success, exit_code, results, logs). - Files: Create, upload, download, list, move, copy, compress, extract, permissions, search, disk usage.
- File watching: WebSocket-backed directory watches (async).
- PTY: Ephemeral and persistent terminal sessions, resize,
run_commandwith prompt detection. - Background commands: Run, list, attach, wait, kill long-running processes.
Requirements
- Python 3.9+
- Dependencies: see
pyproject.toml(Pydantic v2, httpx, urllib3, websockets, python-dotenv, etc.).
Installation
pip install voidrun
With Poetry:
poetry add voidrun
From this repository (editable install for development):
cd py-sdk
pip install -e .
Configuration
VoidRun and AsyncVoidRun require an API key. The API base URL defaults to the hosted endpoint (DEFAULT_API_BASE_URL, aligned with the TypeScript SDK and OpenAPI servers). Set VR_API_URL or API_URL, or pass base_url=, only for self-hosted deployments.
Constructor
from voidrun import VoidRun
vr = VoidRun(
api_key="your-api-key", # optional if VR_API_KEY / API_KEY is set
)
Environment variables
| Variable | Purpose |
|---|---|
VR_API_KEY or API_KEY |
API key (required unless passed to the constructor). |
VR_API_URL or API_URL |
(Self-hosted only.) Overrides the default API base URL. |
Defaults (aligned with TypeScript SDK)
Exported from voidrun:
| Constant | Value | Meaning |
|---|---|---|
DEFAULT_API_BASE_URL |
https://platform.void-run.com/api |
Default API host when VR_API_URL / API_URL are unset. |
DEFAULT_SANDBOX_IMAGE |
"code" |
Default image id when creating a sandbox without image=. |
DEFAULT_SANDBOX_CPU |
1 |
Default CPU count. |
DEFAULT_SANDBOX_MEM |
1024 |
Default memory in MB. |
For self-hosted VoidRun, set VR_API_URL (or API_URL) to your instance’s API root (including /api if that is how your server is mounted).
Quick start
Synchronous (recommended shape)
from voidrun import VoidRun
vr = VoidRun() # uses VR_API_KEY; default base URL unless VR_API_URL / API_URL is set
sandbox = vr.create_sandbox(mem=1024, cpu=1)
result = sandbox.exec('echo "Hello from VoidRun"')
# Exec returns VoidRunResponse whose .data is ExecResponseData
print(result.data.stdout)
sandbox.remove()
Async
import asyncio
from voidrun import AsyncVoidRun
async def main():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox(mem=1024, cpu=1)
result = sandbox.exec('echo "Hello"')
print(result.data.stdout)
await sandbox.remove_async()
asyncio.run(main())
Context managers (auto cleanup)
Sync: exiting the block calls remove(). Async: __aexit__ calls remove_async().
with vr.create_sandbox() as sandbox:
print(sandbox.id)
async with await vr.create_sandbox() as sandbox:
print(sandbox.id)
Parity with the TypeScript SDK
Use the same mental model as @voidrun/sdk (or the internal TS client):
| TypeScript (concept) | Python |
|---|---|
createSandbox |
VoidRun.create_sandbox / await AsyncVoidRun.create_sandbox |
getSandbox |
get_sandbox |
listSandboxes |
list_sandboxes → ListSandboxesResult |
removeSandbox |
remove_sandbox or sandbox.remove() |
sandbox.runCode |
sandbox.run_code |
CodeExecutionResult |
CodeExecutionResult (Pydantic model) |
CodeInterpreter |
CodeInterpreter (alias: Interpreter on sandbox.interpreter) |
Listing sandboxes returns a ListSandboxesResult with:
.sandboxes: list ofSandboxinstances.meta:ListSandboxesMeta(total,page,limit,total_pages)
Core concepts
VoidRun / AsyncVoidRun
Recommended methods
create_sandbox(...)→Sandboxget_sandbox(sandbox_id)→Sandboxlist_sandboxes(page=..., limit=...)→ListSandboxesResultremove_sandbox(sandbox_id)→None
AsyncVoidRun exposes the same names with await and provides aclose() (and async with support) to shut down the thread pool used for API calls.
Keyword aliases
Create options accept both snake_case and camelCase where noted in code (e.g. env_vars / envVars, org_id / orgId).
Sandbox
Notable attributes: id, name, cpu, mem, org_id, status, env_vars, image, region, ref_id, auto_sleep, disk_mb, created_at, created_by.
Sub-clients:
.fs: file operations.pty: pseudo-terminal.interpreter:CodeInterpreter.commands: background processes
Lifecycle:
start,stop,pause,resume(and*_asyncvariants where applicable)remove()/remove_async(): delete sandbox on the APIdelete()/delete_async(): deprecated aliases forremove
info() returns self (same idea as TS sandbox.info()).
Code execution and interpreter
sandbox.exec
Accepts a command string, or keyword command=, or a full ExecRequest:
from voidrun.api_client.models.exec_request import ExecRequest
r = sandbox.exec("uname -a")
r = sandbox.exec(command="pwd", cwd="/tmp", timeout=60)
r = sandbox.exec(ExecRequest(command="ls", cwd="/"))
Return type: VoidRunResponse[ExecResponseData]: the API’s outer ExecResponse envelope is unwrapped so r.data is ExecResponseData (stdout / stderr / exit_code):
print(r.data.stdout, r.data.stderr, r.data.exit_code)
Streaming (exec_stream)
Provide callbacks for SSE events (on_stdout, on_stderr, on_exit, on_error).
Interpreter and run_code
sandbox.interpreter is a CodeInterpreter. Both interpreter.run(...) and sandbox.run_code(...) return CodeExecutionResult directly (no .data nesting):
result = sandbox.run_code("print(2 + 2)", language="python")
print(result.stdout.strip()) # "4"
print(result.success)
result = sandbox.interpreter.run("console.log(1+1)", language="javascript")
Supported languages (typical): python, javascript, typescript, node, bash, sh (as supported by your VoidRun deployment).
Background commands
run_result = sandbox.commands.run(
"sleep 5 && echo done",
{"DEBUG": "true"},
"/tmp",
0,
)
print(run_result.data.pid)
sandbox.commands.list()
sandbox.commands.connect(pid, on_stdout=..., on_stderr=..., on_exit=...)
sandbox.commands.wait(pid)
sandbox.commands.kill(pid)
Response shapes follow the generated OpenAPI models; access fields via .data on VoidRunResponse where applicable.
File system
sandbox.fs.create_file("/tmp/hello.txt")
sandbox.fs.upload_file("/tmp/hello.txt", "Hello, World!")
sandbox.fs.upload_file_from_path("/tmp/remote.txt", "/local/file.txt")
data = sandbox.fs.download_file("/tmp/hello.txt")
sandbox.fs.delete_file("/tmp/hello.txt")
result = sandbox.fs.list_files("/tmp")
files = result.data
sandbox.fs.stat_file("/tmp/hello.txt")
sandbox.fs.create_directory("/tmp/mydir")
sandbox.fs.move_file("/tmp/a.txt", "/tmp/b.txt")
sandbox.fs.copy_file("/tmp/a.txt", "/tmp/copy.txt")
sandbox.fs.change_permissions("/tmp/a.txt", "755")
sandbox.fs.head_tail("/tmp/log.txt", head=True, lines=10)
sandbox.fs.search_files("/tmp", "*.txt")
sandbox.fs.disk_usage("/tmp")
archive = sandbox.fs.compress_file("/tmp", "tar.gz")
sandbox.fs.extract_archive("/tmp/archive.tar.gz", "/tmp/extracted")
File watching (async)
import asyncio
from voidrun import AsyncVoidRun
async def watch_tmp():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox()
watcher = await sandbox.fs.watch(
"/app",
recursive=True,
on_event=lambda evt: print(evt.get("path"), evt.get("type")),
on_error=lambda err: print("watch error:", err),
)
await asyncio.sleep(60)
watcher.close()
await sandbox.remove_async()
asyncio.run(watch_tmp())
Pseudo-terminal (PTY)
Ephemeral session
import asyncio
from voidrun import AsyncVoidRun
async def ephemeral():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox()
pty = await sandbox.pty.connect(
on_data=lambda data: print(data, end=""),
on_error=lambda err: print("PTY error:", err),
)
pty.send_input('echo "Hello"\n')
await asyncio.sleep(2)
await pty.close()
await sandbox.remove_async()
asyncio.run(ephemeral())
Persistent session
async def persistent():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox()
response = sandbox.pty.create_session()
session_id = response.data.data.session_id
pty = await sandbox.pty.connect(
session_id=session_id,
on_data=lambda data: print(data, end=""),
)
pty.send_input('echo "Hello"\n')
await pty.close()
reconnected = await sandbox.pty.connect(
session_id=session_id,
on_data=lambda data: print(data, end=""),
)
await reconnected.close()
sandbox.pty.delete_session(session_id)
await sandbox.remove_async()
Helpers
# After connect(...)
output = await pty.run_command("ls -la", timeout=5000, prompt="# ")
await pty.resize(80, 24)
sandbox.pty.list()
sandbox.pty.delete_session(session_id)
API reference (summary)
Exports (from voidrun import ...)
VoidRun, AsyncVoidRun, Sandbox, CodeInterpreter, Interpreter (alias), CodeExecutionResult, ListSandboxesResult, ListSandboxesMeta, DEFAULT_SANDBOX_*.
CodeExecutionResult
| Field | Type | Description |
|---|---|---|
success |
bool |
Derived from exit code. |
stdout / stderr |
str |
Combined streams. |
exit_code |
int | None |
Process exit code. |
results |
Any |
Parsed output when applicable. |
error |
str | None |
Error hint (often stderr). |
logs |
dict |
e.g. stdout / stderr line lists. |
OpenAPI client
The package includes a generated voidrun.api_client module. Regenerate it when the VoidRun OpenAPI specification changes (same spec as other official clients). After regen, confirm sandbox status values still match the server (e.g. running, stopped, paused, error, killed, deleted, …).
Runnable examples
The examples/ directory contains scripts for sync/async usage, exec, FS, lifecycle, PTY, background commands, and the code interpreter.
Run all examples (loads py-sdk/.env if present, sets PYTHONPATH):
chmod +x scripts/run_all_examples.sh # once
./scripts/run_all_examples.sh
The script exits with status 1 if any example fails (useful for CI).
Single example
From the py-sdk directory (with PYTHONPATH including the repo root, same as the batch script):
export VR_API_KEY="your-api-key"
export PYTHONPATH="$(pwd)"
python3 examples/sync_usage.py
python3 examples/async_usage.py
Use an API key from the same VoidRun deployment as the API host (hosted default, or set VR_API_URL for self-hosted).
Development and tests
cd py-sdk
pip install -e .
pip install pytest pytest-asyncio pytest-mock
pytest tests/
Hatch shortcut:
hatch run test
hatch run all-examples # runs scripts/run_all_examples.sh
Building and publishing
pip install build
python -m build
Or with Poetry: poetry build / poetry publish. See pyproject.toml for package metadata.
Error handling
Failures raise exceptions from the OpenAPI client (for example ApiException) with HTTP status and body. Parse the error body for error and details fields returned by the VoidRun API.
from voidrun import VoidRun
from voidrun.api_client.rest import ApiException
vr = VoidRun()
try:
vr.get_sandbox("nonexistent-id")
except ApiException as e:
print(e.status, e.body)
Troubleshooting
"API key is required …"
Set VR_API_KEY or pass api_key= to the constructor.
"Base URL is required …"
You cleared the base URL (for example empty VR_API_URL). Omit the variable to use the packaged default, or set VR_API_URL / API_URL (self-hosted) with the correct prefix (often /api).
Unauthorized / invalid API key
The key must match the VoidRun API you are calling (hosted default host or your self-hosted VR_API_URL). A local .env pointing at localhost with a cloud key (or vice versa) will return 401.
Sandbox creation failures
Respect minimum CPU and memory enforced by your org/plan. Defaults are 1 CPU and 1024 MB; increase if the API rejects smaller values.
PTY timeouts
Increase timeout in run_command, or allow more time before closing the session.
File not found
List parent directories with sandbox.fs.list_files and confirm paths inside the sandbox.
Contributing, license, support
Contributions are welcome; see the repository’s contributing guidelines if present.
- License: MIT: see the
LICENSEfile. - PyPI: voidrun
- Issues / discussions: GitHub
- Contact: support@voidrun.io (see
pyproject.tomlauthors)
Made with care by VoidRun.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file voidrun-0.0.7.tar.gz.
File metadata
- Download URL: voidrun-0.0.7.tar.gz
- Upload date:
- Size: 61.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
137de5d9275b881197f704a0563e8a463fee570b8131be61851535ac60096fbe
|
|
| MD5 |
fb0661c3c06ca103c6f6b4b349f5ff07
|
|
| BLAKE2b-256 |
43e69562c7f170572f21eeb9f72718aa806e659a0c1b1b13597c6392e3e3d6d6
|
File details
Details for the file voidrun-0.0.7-py3-none-any.whl.
File metadata
- Download URL: voidrun-0.0.7-py3-none-any.whl
- Upload date:
- Size: 149.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b3b613c072b5b7b41765b4fc4f1ae70453bcbfee5726fa22b5220c8ba4179e5
|
|
| MD5 |
8890d0182c134ebe394f04520bdc7a2c
|
|
| BLAKE2b-256 |
497ba2d87b2036c1050d4fe93cd494e1be835580403ab8b516e855f4d7ba1b3c
|