Metadata-Version: 2.5
Name: solari-sandbox
Version: 0.5.0
Summary: Python SDK for Solari Sandboxes — fast, ephemeral code sandboxes (micro-VMs) with a code interpreter, filesystem, and git.
Project-URL: Homepage, https://getsolari.com
Author: Solari
License: MIT
Keywords: code-interpreter,microvm,sandbox,solari
Requires-Python: >=3.9
Requires-Dist: solari-core==0.6.0
Description-Content-Type: text/markdown

# solari-sandbox (Python)

Python SDK for **Solari Sandboxes** — fast, ephemeral code sandboxes (micro-VMs)
with a code interpreter, filesystem, ports, and git. Mirrors the TypeScript
`@solarisdk/sandbox` package.

## Install
```sh
pip install solari-sandbox
```

## Quickstart
```python
import asyncio, os
from solari_sandbox import SandboxClient

async def main():
    client = SandboxClient(api_key=os.environ["SOLARI_API_KEY"])
    sb = await client.create(template="base")
    try:
        await sb.connect()
        result = await sb.run_code("print(6 * 7)")
        print("".join(r.text or "" for r in result.results if r.type == "stdout"))   # -> 42
    finally:
        await sb.kill()
        await client.aclose()

asyncio.run(main())
```

`SandboxClient(...)` accepts `api_key`, `base_url` (default
`https://api.getsolari.com`; pass `https://api-sta.getsolari.com` for staging), and
`call_timeout_ms`. The same `slr_live_…` key authenticates against both the browser and
desktop/sandbox APIs. `run_code` returns a `RunCodeResult`: its `results` list holds items of
`type` `"stdout"`, `"stderr"` or `"result"` (plus `charts` and `error`).

## Synchronous use

Not running asyncio? `SyncSandboxClient` takes the same arguments and hands back **synchronous**
handles: every method runs to completion on the client's own event loop and returns its value
(`create`, `connect`, `start` and `create_desktop` all return such handles).

```python
import os
from solari_sandbox import SyncSandboxClient

with SyncSandboxClient(api_key=os.environ["SOLARI_API_KEY"]) as client:
    sb = client.create(template="base")
    try:
        sb.connect()
        result = sb.run_code("print(6 * 7)")
        print("".join(r.text or "" for r in result.results if r.type == "stdout"))   # -> 42
        print(sb.files.list("/")[0].name, sb.metrics().cpuPct)
    finally:
        sb.kill()
```

One consequence of sharing one loop: output callbacks such as `on_stdout` fire while a call is
running on the handle (for example inside `handle.wait()`), not between calls; `async with` on a sync handle is not supported.

## Restarting a stopped sandbox

When the guest powers itself off the sandbox goes to state `stopped`; the host keeps its disk.
`start()` boots it again (a cold boot: processes and memory are gone, files on disk are kept)
and returns a live handle on the fresh control URL:

```python
sb = await client.start(sandbox_id)       # sync client: sb = client.start(sandbox_id) (a sync handle)
await sb.connect()
```

Calling it on an already-running sandbox is fine. Errors carry `.status` and `.code`:
`409 NotStopped` (a paused sandbox needs `resume`), `409 StartInProgress` (poll `get` until it is
`running`), `409 StoppedDiskMissing` / `StoppedDiskUnsafe` / `GoldenMismatch` / `DiskPlatformMismatch` /
`DiskShaMismatch` / `DiskExceedsSpec` / `DiskOwnerMismatch` (the host cannot start this disk),
`409 HostCapabilityMissing` (the host pool is not armed for start; not retryable), `500 StartFailed`
(the sandbox stays stopped with its disk, so calling again is safe), `502 StartHostError` (host
unreachable, retryable), `NoCapacityError` (503 `NoCapacity` or `StartTimeout`, retryable) and
`ConcurrencyLimitError` (429). A stopped sandbox reports `state == "stopped"` from `get`; every other
route on it answers `409 SandboxStopped` (only `get` and `kill` still work).
`start()` is not retried automatically. The gateway route is `POST /sandboxes/:id/start`.

Docs: <https://getsolari.com>
