Metadata-Version: 2.4
Name: brimble-sandbox
Version: 0.1.4
Summary: Python SDK for the Brimble Sandbox API
Author: Brimble Engineering
License-Expression: MIT
Project-URL: Homepage, https://brimble.io
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.31.0
Provides-Extra: dev
Requires-Dist: pytest>=8.3.0; extra == "dev"

# brimble-sandbox

Python SDK for the Brimble Sandbox API.

## Install

```bash
pip install brimble-sandbox
```

## Quickstart

```python
from brimble_sandbox import Sandbox

client = Sandbox()  # reads BRIMBLE_SANDBOX_KEY from env

sandbox = client.sandboxes.create_ready(
    {
        "template": "node-22",
        "persistent": True,
        "persistentDiskGB": 20,
        "mountPath": "/workspace",
    }
)

result = sandbox.exec({"cmd": "node -v", "env": {"NODE_ENV": "production"}})
print(result["stdout"])

sandbox.put_files(
    [
        {"path": "/tmp/hello.txt", "body": "hello from batch"},
        {"path": "/tmp/config.json", "body": '{"mode":"dev"}'},
    ]
)

existing = client.sandboxes.get(sandbox.id)
existing.destroy()
```

## Ergonomic helpers

```python
# Create + wait in one call
created = client.sandboxes.create_ready({"template": "node-22"})

# Get + wait in one call
loaded = client.sandboxes.get_ready(created.id)

# Create volume + attach at sandbox creation time
with_volume = client.sandboxes.with_volume(
    {
        "sandbox": {"template": "node-22", "mountPath": "/var/www/html"},
        "volume": {"name": "workspace-disk", "sizeGB": 20},
    }
)

# Auto-wait on runtime calls
with_volume.exec({"cmd": "npm -v"}, wait_until_ready=True)

# Streaming command output
output = with_volume.exec_stream({"cmd": "for i in 1 2 3; do echo $i; done"})
for log in output:
    if log["stream"] == "stdout":
        print(log["data"], end="")
result = output.result()

# Or stream with callbacks and still get the final result
buffered = with_volume.exec(
    {"cmd": "npm install"},
    on_stdout=lambda chunk: print(chunk, end=""),
)

# File downloads
file_stream = with_volume.get_file("tmp/notes.txt")
for chunk in file_stream:
    print(chunk.decode("utf-8"), end="")
file_stream.close()

# Templates + regions
templates = client.sandboxes.list_templates()
node_template = client.sandboxes.get_template("node-22")
regions = client.sandboxes.list_regions()

# Iterate through all sandboxes
for sb in client.sandboxes.iterate({"teamId": "<team>"}):
    print(sb.id, sb.status)
```

Volume attachment is create-time only.
Use `create(..., volumeId=...)` or `with_volume(...)`.

## Network egress

```python
from brimble_sandbox import Sandbox
from brimble_sandbox.enums import SandboxEgressMode

client = Sandbox()

sandbox = client.sandboxes.create_ready({
    "template": "node-22",
    "egress": {
        "mode": SandboxEgressMode.RESTRICTED,
        "allow": ["1.1.1.1", "api.example.com"],
    },
})

updated = sandbox.update_egress({"mode": SandboxEgressMode.DENY_ALL})
print(updated.get("network_updated"))

# Legacy shorthand (maps to deny_all)
client.sandboxes.create({"template": "node-22", "blockOutbound": True})
```

Modes: `open`, `restricted` (allowlist required), `deny_all`.

## Auth

Requests are authenticated with the `x-brimble-key` header.

- Pass `api_key` to `Sandbox(...)`
- Or set `BRIMBLE_SANDBOX_KEY` in your environment

## Retry, timeout, idempotency

```python
from brimble_sandbox import RetryOptions, Sandbox

client = Sandbox(
    retry=RetryOptions(max_attempts=3, base_delay_ms=250, max_delay_ms=2000),
)

sandbox = client.sandboxes.create(
    {"template": "node-22"},
    idempotency_key="create-sandbox-123",
)
```

If `region` is omitted or `auto`, it is omitted from the create request and the API assigns an enabled sandbox region. Pass a slug (e.g. `eu-west`) or region id to pin placement.
