|
""" |
|
OpenSandbox Python SDK - Comprehensive Feature Example |
|
======================================================= |
|
|
|
Covers every major feature of the opensandbox SDK: |
|
- Sandbox lifecycle (async + sync APIs) |
|
- Connection configuration |
|
- Command execution (foreground + background) |
|
- File operations (read, write, create directories, read bytes) |
|
- Endpoint access (port mapping) |
|
- Volume mounts (Host, PVC/Docker named volume, OSSFS) |
|
- Network policies (egress allow/deny) |
|
- Metadata and environment variables |
|
- Entrypoint overrides |
|
- Resource limits (CPU, memory, disk) |
|
- Platform specification |
|
- Pool references (extensions) |
|
- Health checks |
|
- Error handling (SandboxException) |
|
- Code Interpreter integration (multi-language) |
|
- Sandbox manager (list/connect to existing sandboxes) |
|
|
|
Prerequisites: |
|
pip install opensandbox opensandbox-code-interpreter |
|
|
|
RunServer: |
|
$env:OPENSANDBOX_INSECURE_SERVER = "YES"; Start-Process -FilePath "C:\ProgramData\miniforge3\python.exe" -ArgumentList "-m opensandbox_server.main" |
|
|
|
Environment variables: |
|
SANDBOX_DOMAIN - OpenSandbox server address (default: localhost:8080) |
|
SANDBOX_API_KEY - API key for authentication (optional) |
|
SANDBOX_IMAGE - Container image (default: ubuntu) |
|
""" |
|
|
|
import asyncio |
|
import os |
|
from datetime import timedelta |
|
from pathlib import Path |
|
|
|
from opensandbox import Sandbox, SandboxSync |
|
from opensandbox.config import ConnectionConfig, ConnectionConfigSync |
|
from opensandbox.exceptions import SandboxException |
|
from opensandbox.models.execd import RunCommandOpts |
|
|
|
# ── Volume models (optional, require SDK with volume support) ──────────────── |
|
try: |
|
from opensandbox.models.sandboxes import Host, OSSFS, PVC, Volume |
|
HAS_VOLUMES = True |
|
except ImportError: |
|
HAS_VOLUMES = False |
|
|
|
# ── Network policy models ──────────────────────────────────────────────────── |
|
try: |
|
from opensandbox.models.sandboxes import NetworkPolicy, NetworkRule |
|
HAS_NETWORK = True |
|
except ImportError: |
|
HAS_NETWORK = False |
|
|
|
# ── Platform spec ──────────────────────────────────────────────────────────── |
|
try: |
|
from opensandbox.models.sandboxes import PlatformSpec |
|
HAS_PLATFORM = True |
|
except ImportError: |
|
HAS_PLATFORM = False |
|
|
|
# ── Code Interpreter (optional) ────────────────────────────────────────────── |
|
try: |
|
from code_interpreter import CodeInterpreter, SupportedLanguage |
|
HAS_CODE_INTERPRETER = True |
|
except ImportError: |
|
HAS_CODE_INTERPRETER = False |
|
|
|
|
|
# ============================================================================= |
|
# Helpers |
|
# ============================================================================= |
|
|
|
def _env(name: str, default: str = "") -> str: |
|
return os.getenv(name, default) |
|
|
|
|
|
DEFAULT_RESOURCE = {"cpu": "1", "memory": "1G"} |
|
|
|
|
|
def _print_logs(label: str, execution) -> None: |
|
"""Pretty-print stdout/stderr/error from a command execution.""" |
|
for msg in execution.logs.stdout: |
|
print(f" [{label}] {msg.text}") |
|
for msg in execution.logs.stderr: |
|
print(f" [{label}:err] {msg.text}") |
|
if execution.error: |
|
print(f" [{label}:error] {execution.error.name}: {execution.error.value}") |
|
|
|
|
|
async def _run(sandbox: Sandbox, command: str, label: str = "cmd") -> str: |
|
"""Run a command and return combined stdout text.""" |
|
result = await sandbox.commands.run(command) |
|
_print_logs(label, result) |
|
return "\n".join(msg.text for msg in result.logs.stdout) |
|
|
|
|
|
# ============================================================================= |
|
# 1. BASIC SANDBOX LIFECYCLE (async) |
|
# ============================================================================= |
|
|
|
async def demo_basic_lifecycle() -> None: |
|
"""Create a sandbox, run a command, and tear it down.""" |
|
print("\n" + "=" * 60) |
|
print("1. Basic Sandbox Lifecycle (async)") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
request_timeout=timedelta(seconds=60), |
|
) |
|
|
|
# Create with image, timeout, and metadata |
|
sandbox = await Sandbox.create( |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=5), |
|
resource=DEFAULT_RESOURCE, |
|
metadata={"example": "basic-lifecycle", "team": "demo"}, |
|
) |
|
|
|
async with sandbox: |
|
print(f" Sandbox ID: {sandbox.id}") |
|
|
|
# Run a simple command |
|
result = await sandbox.commands.run("echo 'Hello from OpenSandbox!'") |
|
for msg in result.logs.stdout: |
|
print(f" Output: {msg.text}") |
|
|
|
# Get the execd daemon endpoint |
|
endpoint = await sandbox.get_endpoint(44772) |
|
print(f" execd endpoint: {endpoint.endpoint}") |
|
|
|
# sandbox is automatically killed after exiting `async with` |
|
print(" Sandbox cleaned up.") |
|
|
|
|
|
# ============================================================================= |
|
# 2. SYNC API |
|
# ============================================================================= |
|
|
|
def demo_sync_api() -> None: |
|
"""Use the synchronous SandboxSync API.""" |
|
print("\n" + "=" * 60) |
|
print("2. Sync Sandbox API") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfigSync( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
request_timeout=timedelta(seconds=60), |
|
) |
|
|
|
sandbox = SandboxSync.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=5), |
|
) |
|
|
|
try: |
|
print(f" Sandbox ID: {sandbox.id}") |
|
|
|
result = sandbox.commands.run("uname -a") |
|
for msg in result.logs.stdout: |
|
print(f" {msg.text}") |
|
finally: |
|
sandbox.kill() |
|
sandbox.close() |
|
|
|
print(" Sync sandbox cleaned up.") |
|
|
|
|
|
# ============================================================================= |
|
# 3. COMMAND EXECUTION (foreground + background) |
|
# ============================================================================= |
|
|
|
async def demo_command_execution() -> None: |
|
"""Run foreground and background commands.""" |
|
print("\n" + "=" * 60) |
|
print("3. Command Execution (foreground + background)") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=3), |
|
) |
|
|
|
async with sandbox: |
|
# Foreground command |
|
print("\n [foreground] Running 'echo hello':") |
|
result = await sandbox.commands.run("echo hello") |
|
for msg in result.logs.stdout: |
|
print(f" {msg.text}") |
|
|
|
# Background command (fire-and-forget) |
|
print("\n [background] Starting 'sleep 60' in background:") |
|
bg = await sandbox.commands.run( |
|
"sleep 60", |
|
opts=RunCommandOpts(background=True), |
|
) |
|
print(f" Background PID tracked: {bg.logs is not None}") |
|
|
|
# Command with stderr |
|
print("\n [stderr] Running command that writes to stderr:") |
|
result = await sandbox.commands.run("echo 'error msg' >&2") |
|
for msg in result.logs.stderr: |
|
print(f" stderr: {msg.text}") |
|
|
|
# Command that fails |
|
print("\n [error] Running command that exits non-zero:") |
|
result = await sandbox.commands.run("exit 42") |
|
if result.error: |
|
print(f" Error: {result.error.name}: {result.error.value}") |
|
|
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 4. FILE OPERATIONS |
|
# ============================================================================= |
|
|
|
async def demo_file_operations() -> None: |
|
"""Read, write, and list files in the sandbox.""" |
|
print("\n" + "=" * 60) |
|
print("4. File Operations") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=3), |
|
) |
|
|
|
async with sandbox: |
|
# Write a file |
|
print("\n [write] Writing /tmp/hello.txt:") |
|
await sandbox.files.write_file("/tmp/hello.txt", "Hello, World!\nLine 2\n") |
|
|
|
# Create directories |
|
print(" [mkdir] Creating /tmp/subdir/nested:") |
|
try: |
|
from opensandbox.models.filesystem import WriteEntry |
|
await sandbox.files.create_directories([ |
|
WriteEntry(path="/tmp/subdir/nested", mode=755), |
|
]) |
|
except Exception: |
|
# Fallback: use command |
|
await sandbox.commands.run("mkdir -p /tmp/subdir/nested") |
|
|
|
# Read file back |
|
print(" [read] Reading /tmp/hello.txt:") |
|
content = await sandbox.files.read_file("/tmp/hello.txt") |
|
print(f" Content: {content.strip()}") |
|
|
|
# Read as bytes (useful for binary files) |
|
print(" [read_bytes] Reading /tmp/hello.txt as bytes:") |
|
data = await sandbox.files.read_bytes("/tmp/hello.txt") |
|
print(f" Bytes: {len(data)} bytes, starts with: {data[:20]}") |
|
|
|
# Write with mode |
|
print(" [write+mode] Writing /tmp/script.sh with mode 755:") |
|
await sandbox.files.write_file( |
|
"/tmp/script.sh", |
|
"#!/bin/sh\necho 'executable script'\n", |
|
mode=755, |
|
) |
|
|
|
# List files |
|
print(" [ls] Listing /tmp via command:") |
|
await _run(sandbox, "ls -la /tmp/", "ls") |
|
|
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 5. VOLUME MOUNTS |
|
# ============================================================================= |
|
|
|
async def demo_volume_mounts() -> None: |
|
"""Mount Docker named volumes into sandboxes.""" |
|
if not HAS_VOLUMES: |
|
print("\n[SKIP] Volume models not available. Install SDK from source.") |
|
return |
|
|
|
print("\n" + "=" * 60) |
|
print("5. Volume Mounts (Docker Named Volumes)") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
# ── 5a. PVC (Docker named volume) read-write ───────────────────────── |
|
volume_name = "osb-demo-volume" |
|
print(f"\n [5a] PVC volume (r/w): {volume_name} -> /mnt/data") |
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
volumes=[ |
|
Volume( |
|
name="demo-data", |
|
pvc=PVC(claimName=volume_name), |
|
mountPath="/mnt/data", |
|
readOnly=False, |
|
), |
|
], |
|
) |
|
async with sandbox: |
|
await _run(sandbox, "echo 'hello from sandbox' > /mnt/data/test.txt", "pvc-write") |
|
await _run(sandbox, "cat /mnt/data/test.txt", "pvc-read") |
|
await sandbox.kill() |
|
|
|
# ── 5b. PVC read-only ──────────────────────────────────────────────── |
|
print(f"\n [5b] PVC volume (r/o): {volume_name} -> /mnt/readonly") |
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
volumes=[ |
|
Volume( |
|
name="demo-ro", |
|
pvc=PVC(claimName=volume_name), |
|
mountPath="/mnt/readonly", |
|
readOnly=True, |
|
), |
|
], |
|
) |
|
async with sandbox: |
|
await _run(sandbox, "cat /mnt/readonly/test.txt", "ro-read") |
|
await _run( |
|
sandbox, |
|
"touch /mnt/readonly/should-fail.txt 2>&1 || echo 'Write denied (expected)'", |
|
"ro-write-attempt", |
|
) |
|
await sandbox.kill() |
|
|
|
# ── 5c. SubPath mount ──────────────────────────────────────────────── |
|
print(f"\n [5c] SubPath: datasets/train -> /mnt/training") |
|
# First create the subpath structure |
|
setup = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
volumes=[ |
|
Volume( |
|
name="setup-vol", |
|
pvc=PVC(claimName=volume_name), |
|
mountPath="/mnt/vol", |
|
readOnly=False, |
|
), |
|
], |
|
) |
|
async with setup: |
|
await _run(setup, "mkdir -p /mnt/vol/datasets/train && echo 'id,value' > /mnt/vol/datasets/train/data.csv", "setup") |
|
await setup.kill() |
|
|
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
volumes=[ |
|
Volume( |
|
name="train-data", |
|
pvc=PVC(claimName=volume_name), |
|
mountPath="/mnt/training", |
|
subPath="datasets/train", |
|
readOnly=True, |
|
), |
|
], |
|
) |
|
async with sandbox: |
|
await _run(sandbox, "cat /mnt/training/data.csv", "subpath") |
|
await sandbox.kill() |
|
|
|
print("\n Volume mount demos completed.") |
|
|
|
|
|
# ============================================================================= |
|
# 6. NETWORK POLICIES |
|
# ============================================================================= |
|
|
|
async def demo_network_policies() -> None: |
|
"""Restrict sandbox egress with network policies.""" |
|
if not HAS_NETWORK: |
|
print("\n[SKIP] NetworkPolicy models not available.") |
|
return |
|
|
|
print("\n" + "=" * 60) |
|
print("6. Network Policies (egress control)") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
# Deny all egress except specific domains |
|
policy = NetworkPolicy( |
|
defaultAction="deny", |
|
egress=[ |
|
NetworkRule(action="allow", target="pypi.org"), |
|
NetworkRule(action="allow", target="github.com"), |
|
], |
|
) |
|
|
|
print(" Policy: deny-all except pypi.org and github.com") |
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
network_policy=policy, |
|
) |
|
async with sandbox: |
|
# Try allowed domain (may fail if sandbox has no internet at all) |
|
print("\n [allowed] curl pypi.org:") |
|
await _run(sandbox, "curl -sI --connect-timeout 5 https://pypi.org 2>&1 | head -3 || echo '(network unavailable in sandbox)'", "pypi") |
|
|
|
# Try denied domain (should be blocked) |
|
print("\n [blocked] curl google.com (should fail):") |
|
await _run(sandbox, "curl -sI --connect-timeout 5 https://google.com 2>&1 | head -3 || echo '(blocked as expected)'", "google") |
|
|
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 7. ENVIRONMENT VARIABLES & METADATA |
|
# ============================================================================= |
|
|
|
async def demo_env_and_metadata() -> None: |
|
"""Pass environment variables and metadata to the sandbox.""" |
|
print("\n" + "=" * 60) |
|
print("7. Environment Variables & Metadata") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
env={ |
|
"MY_VAR": "hello", |
|
"ANOTHER_VAR": "42", |
|
}, |
|
metadata={ |
|
"project": "demo", |
|
"environment": "staging", |
|
}, |
|
) |
|
async with sandbox: |
|
print(" [env] Environment variables:") |
|
await _run(sandbox, "echo MY_VAR=$MY_VAR ANOTHER=$ANOTHER_VAR", "env") |
|
|
|
# Metadata is set on the server side (visible via API/CLI) |
|
print(" [metadata] Set: project=demo, environment=staging") |
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 8. ENTRYPOINT OVERRIDE |
|
# ============================================================================= |
|
|
|
async def demo_entrypoint() -> None: |
|
"""Override the container entrypoint.""" |
|
print("\n" + "=" * 60) |
|
print("8. Entrypoint Override") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
# Custom entrypoint: a simple long-running process |
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
entrypoint=["sleep", "3600"], |
|
) |
|
async with sandbox: |
|
print(" [entrypoint] Custom entrypoint: sleep 3600") |
|
result = await sandbox.commands.run("ps aux | grep 'sleep 3600'") |
|
for msg in result.logs.stdout: |
|
if "sleep" in msg.text: |
|
print(f" Process found: {msg.text.strip()[:80]}") |
|
break |
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 9. RESOURCE LIMITS |
|
# ============================================================================= |
|
|
|
async def demo_resource_limits() -> None: |
|
"""Set CPU, memory, and disk limits on the sandbox.""" |
|
print("\n" + "=" * 60) |
|
print("9. Resource Limits") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
sandbox = await Sandbox.create( |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
resource={ |
|
"cpu": "2", |
|
"memory": "4G", |
|
"disk": "20G", |
|
}, |
|
) |
|
async with sandbox: |
|
print(" [resources] Set: cpu=2, memory=4G, disk=20G") |
|
await _run(sandbox, "free -h | head -2", "mem") |
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 10. PLATFORM SPECIFICATION |
|
# ============================================================================= |
|
|
|
async def demo_platform_spec() -> None: |
|
"""Specify OS and architecture for the sandbox.""" |
|
if not HAS_PLATFORM: |
|
print("\n[SKIP] PlatformSpec model not available.") |
|
return |
|
|
|
print("\n" + "=" * 60) |
|
print("10. Platform Specification") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
platform=PlatformSpec(os="linux", arch="amd64"), |
|
) |
|
async with sandbox: |
|
print(" [platform] os=linux, arch=amd64") |
|
await _run(sandbox, "uname -m", "arch") |
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 11. HEALTH CHECKS |
|
# ============================================================================= |
|
|
|
async def demo_health_check() -> None: |
|
"""Custom health check function for sandbox readiness.""" |
|
import time |
|
|
|
print("\n" + "=" * 60) |
|
print("11. Health Check") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
def my_health_check(sbx: SandboxSync) -> bool: |
|
"""Poll /ping on execd until the sandbox is ready.""" |
|
try: |
|
endpoint = sbx.get_endpoint(44772) |
|
start = time.perf_counter() |
|
import requests |
|
for _ in range(50): |
|
try: |
|
resp = requests.get( |
|
f"http://{endpoint.endpoint}/ping", |
|
timeout=1, |
|
) |
|
if resp.status_code == 200: |
|
elapsed = time.perf_counter() - start |
|
print(f" Ready after {elapsed:.1f}s") |
|
return True |
|
except Exception: |
|
pass |
|
time.sleep(0.2) |
|
except Exception as exc: |
|
print(f" Health check failed: {exc}") |
|
return False |
|
|
|
sandbox = SandboxSync.create( |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=ConnectionConfigSync( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
), |
|
timeout=timedelta(minutes=2), |
|
resource=DEFAULT_RESOURCE, |
|
health_check=my_health_check, |
|
) |
|
try: |
|
print(f" Sandbox ID: {sandbox.id}") |
|
print(" Health check passed.") |
|
finally: |
|
sandbox.kill() |
|
sandbox.close() |
|
|
|
|
|
# ============================================================================= |
|
# 12. ERROR HANDLING |
|
# ============================================================================= |
|
|
|
async def demo_error_handling() -> None: |
|
"""Handle SandboxException and other errors.""" |
|
print("\n" + "=" * 60) |
|
print("12. Error Handling") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
try: |
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image="this-image-does-not-exist:latest", # intentionally bad |
|
connection_config=config, |
|
timeout=timedelta(seconds=30), |
|
) |
|
async with sandbox: |
|
pass |
|
except SandboxException as e: |
|
print(f" Caught SandboxException:") |
|
print(f" Code: {e.error.code}") |
|
print(f" Message: {e.error.message}") |
|
except Exception as e: |
|
print(f" Caught generic exception: {type(e).__name__}: {e}") |
|
|
|
# Command-level error |
|
print("\n [cmd-error] Running a command that fails:") |
|
sandbox = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
) |
|
async with sandbox: |
|
result = await sandbox.commands.run("false") |
|
if result.error: |
|
print(f" Error name: {result.error.name}") |
|
print(f" Error value: {result.error.value}") |
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 13. SANDBOX MANAGER (list & connect to existing sandboxes) |
|
# ============================================================================= |
|
|
|
async def demo_sandbox_manager() -> None: |
|
"""Use SandboxManager to list and connect to sandboxes.""" |
|
print("\n" + "=" * 60) |
|
print("13. Sandbox Manager (list & connect)") |
|
print("=" * 60) |
|
|
|
from opensandbox.manager import SandboxManager |
|
from opensandbox.models.sandboxes import SandboxFilter |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
manager = await SandboxManager.create(config) |
|
try: |
|
# List existing sandboxes |
|
result = await manager.list_sandbox_infos(SandboxFilter(page_size=10)) |
|
print(f" Found {len(result.sandboxes)} sandbox(es).") |
|
for sbx in result.sandboxes[:5]: |
|
print(f" - {sbx.id} (state={sbx.state})") |
|
except Exception as e: |
|
print(f" List failed (expected if no sandboxes): {e}") |
|
finally: |
|
manager.close() |
|
|
|
|
|
# ============================================================================= |
|
# 14. CODE INTERPRETER (multi-language) |
|
# ============================================================================= |
|
|
|
async def demo_code_interpreter() -> None: |
|
"""Run code in multiple languages via CodeInterpreter.""" |
|
if not HAS_CODE_INTERPRETER: |
|
print("\n[SKIP] code_interpreter not installed. pip install opensandbox-code-interpreter") |
|
return |
|
|
|
print("\n" + "=" * 60) |
|
print("14. Code Interpreter (Python, Java, Go, TypeScript)") |
|
print("=" * 60) |
|
|
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
image = _env( |
|
"SANDBOX_IMAGE", |
|
"sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/code-interpreter:v1.0.2", |
|
) |
|
|
|
sandbox = await Sandbox.create( |
|
image, |
|
connection_config=config, |
|
resource=DEFAULT_RESOURCE, |
|
entrypoint=["/opt/opensandbox/code-interpreter.sh"], |
|
) |
|
|
|
async with sandbox: |
|
interpreter = await CodeInterpreter.create(sandbox=sandbox) |
|
|
|
# Python |
|
print("\n [Python]") |
|
py = await interpreter.codes.run( |
|
"import platform\n" |
|
"print(f'Python {platform.python_version()}')\n" |
|
"result = sum(range(10))\n" |
|
"result", |
|
language=SupportedLanguage.PYTHON, |
|
) |
|
for msg in py.logs.stdout: |
|
print(f" {msg.text}") |
|
if py.result: |
|
for r in py.result: |
|
print(f" => {r.text}") |
|
|
|
# Java |
|
print("\n [Java]") |
|
java = await interpreter.codes.run( |
|
'System.out.println("Hello from Java!");\n' |
|
"int result = 2 * 21;\n" |
|
"System.out.println(\"2 * 21 = \" + result);\n" |
|
"result", |
|
language=SupportedLanguage.JAVA, |
|
) |
|
for msg in java.logs.stdout: |
|
print(f" {msg.text}") |
|
if java.result: |
|
for r in java.result: |
|
print(f" => {r.text}") |
|
|
|
# Go |
|
print("\n [Go]") |
|
go = await interpreter.codes.run( |
|
'package main\n' |
|
'import "fmt"\n' |
|
"func main() {\n" |
|
' fmt.Println("Hello from Go!")\n' |
|
" fmt.Println(\"3 + 4 =\", 3+4)\n" |
|
"}", |
|
language=SupportedLanguage.GO, |
|
) |
|
for msg in go.logs.stdout: |
|
print(f" {msg.text}") |
|
|
|
# TypeScript |
|
print("\n [TypeScript]") |
|
ts = await interpreter.codes.run( |
|
"const nums: number[] = [1, 2, 3, 4, 5];\n" |
|
"console.log('Sum:', nums.reduce((a, b) => a + b, 0));", |
|
language=SupportedLanguage.TYPESCRIPT, |
|
) |
|
for msg in ts.logs.stdout: |
|
print(f" {msg.text}") |
|
|
|
await sandbox.kill() |
|
|
|
|
|
# ============================================================================= |
|
# 15. CROSS-SANDBOX DATA SHARING (via named volume) |
|
# ============================================================================= |
|
|
|
async def demo_cross_sandbox_sharing() -> None: |
|
"""Two sandboxes share data through the same volume.""" |
|
if not HAS_VOLUMES: |
|
print("\n[SKIP] Volume models not available.") |
|
return |
|
|
|
print("\n" + "=" * 60) |
|
print("15. Cross-Sandbox Data Sharing (via PVC)") |
|
print("=" * 60) |
|
|
|
volume_name = "opensandbox-share-demo" |
|
config = ConnectionConfig( |
|
domain=_env("SANDBOX_DOMAIN", "localhost:8080"), |
|
api_key=_env("SANDBOX_API_KEY") or None, |
|
) |
|
|
|
volume_spec = Volume( |
|
name="shared-vol", |
|
pvc=PVC(claimName=volume_name), |
|
mountPath="/mnt/shared", |
|
readOnly=False, |
|
) |
|
|
|
# Sandbox A writes |
|
print("\n [Sandbox A] Writing data...") |
|
sandbox_a = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
volumes=[volume_spec], |
|
) |
|
async with sandbox_a: |
|
await _run(sandbox_a, "echo 'hello from A' > /mnt/shared/msg.txt", "A") |
|
await sandbox_a.kill() |
|
|
|
# Sandbox B reads |
|
print(" [Sandbox B] Reading data...") |
|
sandbox_b = await Sandbox.create( |
|
resource=DEFAULT_RESOURCE, |
|
image=_env("SANDBOX_IMAGE", "ubuntu"), |
|
connection_config=config, |
|
timeout=timedelta(minutes=2), |
|
volumes=[volume_spec], |
|
) |
|
async with sandbox_b: |
|
await _run(sandbox_b, "cat /mnt/shared/msg.txt", "B") |
|
await sandbox_b.kill() |
|
|
|
print(" Cross-sandbox sharing verified.") |
|
|
|
|
|
# ============================================================================= |
|
# MAIN |
|
# ============================================================================= |
|
|
|
async def main() -> None: |
|
"""Run all feature demos.""" |
|
print("OpenSandbox Python SDK - Comprehensive Feature Demo") |
|
print("=" * 60) |
|
|
|
demos = [ |
|
("1. Basic Lifecycle", demo_basic_lifecycle), |
|
("2. Sync API", demo_sync_api), |
|
("3. Command Execution", demo_command_execution), |
|
("4. File Operations", demo_file_operations), |
|
("5. Volume Mounts", demo_volume_mounts), |
|
("6. Network Policies", demo_network_policies), |
|
("7. Env & Metadata", demo_env_and_metadata), |
|
("8. Entrypoint Override", demo_entrypoint), |
|
("9. Resource Limits", demo_resource_limits), |
|
("10. Platform Specification", demo_platform_spec), |
|
("11. Health Checks", demo_health_check), |
|
("12. Error Handling", demo_error_handling), |
|
("13. Sandbox Manager", demo_sandbox_manager), |
|
("14. Code Interpreter", demo_code_interpreter), |
|
("15. Cross-Sandbox Sharing", demo_cross_sandbox_sharing), |
|
] |
|
|
|
for label, demo_fn in demos: |
|
try: |
|
if asyncio.iscoroutinefunction(demo_fn): |
|
await demo_fn() |
|
else: |
|
demo_fn() |
|
except Exception as e: |
|
print(f"\n [SKIP/ERROR] {label}: {type(e).__name__}: {e}") |
|
|
|
print("\n" + "=" * 60) |
|
print("All demos completed!") |
|
print("=" * 60) |
|
|
|
|
|
if __name__ == "__main__": |
|
asyncio.run(main()) |