Metadata-Version: 2.5
Name: sandbox-oss
Version: 0.2.0
Summary: Python SDK for Sandbox OSS: micro-VM sandboxes on demand
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Provides-Extra: cli
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# sandbox-oss

Python SDK for **Sandbox OSS**: micro-VM sandboxes on demand. Create one, run
commands in it, move files in and out, suspend it when idle, delete it when
done. Every sandbox is a real VM with its own kernel.

```bash
pip install sandbox-oss
export SANDBOX_OSS_TOKEN=...            # your tenant token
# SANDBOX_OSS_URL defaults to https://sandbox.zillion.network
```

```python
from sandbox_oss import Sandbox

with Sandbox.create(image="default", flavor="medium") as sbx:   # deleted on exit
    print(sbx.run("python3 -c 'print(21*2)'").stdout)          # 42
```

## Size

A named preset, or an exact size: never both, and one is required (a size is
a price).

```python
Sandbox.create(flavor="medium")            # small, medium, large, xlarge-memory, xlarge
Sandbox.create(cpu=2, memory=4)            # whole numbers; 1-4 GiB per vCPU, at most 48 GiB
Flavor.list()                              # every preset's size, and the exact-size limits
```

Memory is reserved for you and a sandbox that exceeds it is ended, so size to
peak memory. CPU only throttles.

## Commands

```python
r = sbx.run("make test", cwd="/work", timeout=600, check=True)  # raises on nonzero exit
print(r.exit_code, r.stdout, r.stderr)

proc = sbx.commands.start("python3 train.py")                   # returns at once
for chunk in proc.stream():
    print(chunk.text, end="")
print(proc.wait().exit_code)
```

## Files

One file per call, **up to 100 MiB each way**. Anything readable works and is
streamed, not loaded into memory:

```python
sbx.files.write("/work/main.py", "print(42)\n")
with open("model.bin", "rb") as f:
    sbx.files.write("/work/model.bin", f)
sbx.files.write("/work/data.csv", Path("data.csv"))

text = sbx.files.read("/work/out.json")
with open("out.bin", "wb") as f:
    sbx.files.read_into("/work/out.bin", f)
```

Bigger than 100 MiB: have the sandbox download it from a presigned URL (the
catalog images have `python3` but not `curl`), compress it, or keep it on a volume:

```python
sbx.run(f"python3 -c \"import urllib.request as u; u.urlretrieve('{url}', '/work/big.tar')\"", check=True)
```

## Suspend

```python
sbx.suspend()     # costs disk only from here
sbx.resume()      # back in about a second, memory and processes intact
```

## Finding what you are paying for

```python
for info in Sandbox.list(status="running"):
    print(info.id, info.flavor, info.created_at)
sbx = Sandbox.connect("sb-81c7c52fe1f4")
```

## Your account

```python
from sandbox_oss import Image, Node, Secret, TenantUsage, Volume

Image.upload("myenv.tar", name="myenv", tag="v3")          # then Sandbox.create(image="myenv:v3", ...)
vol = Volume.create(20)                                     # disk that outlives sandboxes
Sandbox.create(flavor="small", volumes=[vol.mount("/data")])
Secret.put("gh_token", token)                               # write-only; injected by the egress proxy
Node.list()                                                 # machines you can pin to with node=
TenantUsage.get(from_="2026-10-01T00:00:00Z")               # the whole account, per flavor and machine
```

## Async

Every class has an async twin with the same methods: `AsyncSandbox`,
`AsyncImage` and so on.

```python
import asyncio
from sandbox_oss import AsyncSandbox

async def one(i):
    async with await AsyncSandbox.create(flavor="small") as sbx:
        return (await sbx.run(f"echo {i}")).stdout

async def main():
    return await asyncio.gather(*(one(i) for i in range(10)))   # ten sandboxes at once

print(asyncio.run(main()))
```

## CLI

`sbx` is installed with the package:

```bash
SB=$(sbx create --flavor small)
sbx exec $SB "python3 -V"
sbx rm $SB
```

`sbx --help` lists everything; [recipes](../docs/recipes.md) show each task.

## Errors

Every error is a `SandboxOSSError` with `.code`, `.retryable` and
`.request_id`. Retryable failures are retried for you, and creates, commands
and extends carry an idempotency key, so a retry never runs them twice.

## Credentials

The SDK sends your tenant token for create and list, and the sandbox's own
access token (returned at create) for everything on that sandbox, so code
running inside a sandbox can never reach the rest of your account.
`Sandbox.connect(id, access_token=...)` hands one sandbox to another process
without your tenant token.

Docs: [overview](../docs/index.md) · [recipes](../docs/recipes.md) · [reference](../docs/reference.md)

Status: pre-release (0.1.0.dev0).
