Skip to main content

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.