Python SDK
Python client SDK for Boxer — a sandboxed container execution service backed by gVisor.
Installation
pip install boxer-sdk
Requires Python 3.9+ and a running Boxer server.
Quick Start
from boxer import BoxerClient
with BoxerClient("http://localhost:8080") as client:
result = client.run(
image="python:3.12-slim",
cmd=["python3", "-c", "print('hello world')"],
)
print(result.stdout) # hello world
print(result.stderr) # (empty)
print(result.exit_code) # 0
print(result.wall_ms) # e.g. 312
Multi-language Examples
Python
result = client.run(
image="python:3.12-slim",
cmd=["python3", "-c", "print('hello world')"],
)
print(result.stdout) # hello world
Node.js
result = client.run(
image="node:20-slim",
cmd=["node", "-e", "console.log('hello world')"],
)
print(result.stdout) # hello world
Perl
result = client.run(
image="perl:5.38-slim",
cmd=["perl", "-e", "print 'hello world\n'"],
)
print(result.stdout) # hello world
Working with Files
Upload a Script and Run It
Upload a file to the Boxer file store, then reference it by path in run. The file is bind-mounted read-only at /<remote_path> inside the container.
with open("script.py", "rb") as f:
client.upload_file("script.py", f)
result = client.run(
image="python:3.12-slim",
cmd=["python3", "/script.py"],
files=["script.py"],
)
Download Output Files
Any file the container writes to /output/ is automatically captured at the end of the run. To retrieve it afterwards, you must set persist=True — by default all output files are deleted before the response is returned.
code = """
import os, json
os.makedirs('/output', exist_ok=True)
with open('/output/result.json', 'w') as f:
json.dump({'message': 'hello world', 'value': 42}, f)
"""
with open("compute.py", "rb") as f:
client.upload_file("compute.py", f)
result = client.run(
image="python:3.12-slim",
cmd=["python3", "/compute.py"],
files=["compute.py"],
persist=True,
)
data = client.download_file(f"output/{result.exec_id}/result.json")
print(data) # b'{"message": "hello world", "value": 42}'
The output path pattern is output/<exec_id>/<relative_path>, preserving any subdirectory structure written under /output/ (e.g. output/<exec_id>/subdir/file.json).
Resource Limits
from boxer import ResourceLimits
limits = ResourceLimits(
cpu_cores=0.5,
memory_mb=128,
wall_clock_secs=10,
)
result = client.run(
image="python:3.12-slim",
cmd=["python3", "-c", "print('done')"],
limits=limits,
)
Async Client
Every method on BoxerClient has an await-able equivalent on AsyncBoxerClient:
import asyncio
from boxer import AsyncBoxerClient
async def main():
async with AsyncBoxerClient("http://localhost:8080") as client:
result = await client.run(
image="python:3.12-slim",
cmd=["python3", "-c", "print('hello world')"],
)
print(result.stdout)
asyncio.run(main())
Error Handling
from boxer import BoxerAPIError, BoxerTimeoutError, BoxerOutputLimitError, ResourceLimits
try:
result = client.run(
image="python:3.12-slim",
cmd=["python3", "-c", "while True: pass"],
limits=ResourceLimits(wall_clock_secs=5),
)
except BoxerTimeoutError:
print("execution timed out")
except BoxerOutputLimitError:
print("output exceeded size limit")
except BoxerAPIError as e:
print(f"API error {e.status_code}: {e}")
Running Tests
Tests require a live Boxer server:
pip install -e ".[dev]"
BOXER_URL=http://localhost:8080 pytest tests/ -v
Without BOXER_URL all tests are skipped automatically.