Metadata-Version: 2.4
Name: platinum-sdk
Version: 0.3.0
Summary: Platinum Python SDK — client for hardware-isolated sandbox microVMs.
License: MIT
Project-URL: Homepage, https://github.com/kortix-ai/platinum
Project-URL: Repository, https://github.com/kortix-ai/platinum
Keywords: sandbox,microvm,vm,code-execution,agents,platinum
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.25
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# platinum-sdk (Python)

Python client for [Platinum](https://github.com/kortix-ai/platinum) — hardware-isolated
sandbox microVMs (one Cloud Hypervisor VM per sandbox, sub-second boots via a warm pool).

```sh
pip install platinum-sdk     # import name: platinum
```

Python ≥ 3.9, typed (PEP 561 — ships py.typed; mypy --strict clean). Sync and async
clients. Server-side use only — never ship an API key into client-side code.

## Quickstart

```python
from platinum import Platinum

dn = Platinum(token="pt_live_...", api_url="https://api.platinum.dev")
# or: PT_TOKEN / PT_API_URL env vars

# Pick a template that exists on YOUR deployment first:
print(dn.templates.list())

sbx = dn.sandboxes.create(template="pt-base", wait_for_running=True)
r = sbx.exec(["uname", "-a"]).check()     # .check() raises on non-zero exit
print(r.stdout)
sbx.delete()
```

Async — a full 1:1 mirror of the sync surface:

```python
from platinum import AsyncPlatinum

async with AsyncPlatinum(token="pt_live_...", api_url="https://api.platinum.dev") as dn:
    sbx = await dn.sandboxes.create(template="pt-base", wait_for_running=True)
    r = await sbx.exec("uname -a")        # argv list or plain string
    print(r.stdout)
    await sbx.delete()
```

Expose a port at create time (URL comes back in the same response):

```python
sbx = dn.sandboxes.create(
    template="pt-base",
    expose=[{"port": 8080, "public": True}],
    wait_for_running=True,
)
print(sbx.exposed_url(8080))
```

Build a custom image inline (cache-hit on repeat, built on first use):

```python
from platinum import Platinum, Template

dn = Platinum()
image = (Template.from_python_image("3.12-slim")
                 .pip_install(["fastapi", "uvicorn"])
                 .workdir("/app"))
sbx = dn.sandboxes.create(image=image, wait_for_running=True, wait_timeout_ms=600_000)
```

## Configuration

| Arg / env | Default | Meaning |
|---|---|---|
| `token` / `PT_TOKEN` | — (required) | API key `pt_live_…`, org-scoped bearer token |
| `api_url` / `PT_API_URL` | `http://127.0.0.1:3000` | Control-plane URL |
| `timeout` | `60.0` | Per-request timeout in seconds (file transfer calls override per call) |
| `transport` | — | httpx transport override (e.g. `httpx.MockTransport` in tests) |

## Errors

Everything the SDK raises extends `PlatinumError` (`.status`, `.body`, `.code` — the
API's machine-readable error code, also folded into `str(err)`):

```python
from platinum import NotFoundError, ConflictError, RateLimitError

try:
    sbx.exec("true")
except NotFoundError:            # sandbox gone
    ...
except ConflictError as e:       # e.g. e.code == "sandbox_not_running"
    ...
except RateLimitError as e:      # back off e.retry_after_seconds — the SDK never retries
    ...
```

Subclasses: `ValidationError` (400) · `AuthenticationError` (401) · `ForbiddenError` (403)
· `NotFoundError` (404) · `ConflictError` (409) · `RateLimitError` (429) · `ServerError`
(5xx) · `PlatinumTimeoutError` / `PlatinumConnectionError` (client-side, `status == 0`).
**No automatic retries, ever** — a 429 or a failed create is surfaced, never silently retried.

## Surface (at parity with the TypeScript SDK)

The two SDKs expose the same operations — enforced in CI by `verify/sdk-parity.sh`
(name-level) and the offline unit suites (behaviour). The async client mirrors the sync
surface 1:1 (enforced by `tests/test_unit.py::test_async_mirrors_sync_surface`).

| Area | Methods |
|---|---|
| Sandboxes | `sandboxes.create(...)` · `get` · `connect` · `list` · `iter` (auto-pagination) · `rename` · `delete` |
| Lifecycle | `stop`/`start` (optional server-side wait) · `pause`/`resume` · `kill` · `resize` · `fork` · `clone` · `snapshot` · `list_snapshots` · `delete_snapshot` · `restore` · `restore_from_backup` · `archive` · `backup` · `wait_running` · `wait_state` · `refresh` |
| Run | `exec(argv \| str)` · `sh(script)` · `run_code(code, lang=None)` — defaults to the sandbox's create-time `language` |
| Files (vsock) | `files.read/write/delete/list/stat/mkdir/exists` · `find` (glob) · `grep` (content) · `replace` (bulk sed) · `watch` (generator of change events) |
| Networking | `expose(port, public=, ttl_seconds=)` · `unexpose` · `exposed_url` · `revoke_expose_token` · `set_egress_policy` · `add_ssh_keys` |
| Observability | `metrics()` (live cpu/mem/disk) · `usage()` (billed usage) |
| Platform | `templates.list/get/delete` · `Template` builder · `webhooks.*` · `volumes.*` · `regions.list()` · `me()` · `health.check()` |
| Results | `ExecResult(stdout, stderr, exit_code, duration_ms, lang)` with `.check()` |

Watch for file changes:

```python
for ev in sbx.files.watch("/workspace", max_seconds=60):
    if ev["event"] == "change":
        print(ev["data"]["type"], ev["data"]["path"])
```

Not wrapped yet: interactive terminal (WebSocket), cross-host `migrate` (admin-only),
API-key management, audit-log queries, billing.

## Limits worth knowing

- **`files.write` bodies must stay ≤ 16 MiB** — larger writes currently truncate and
  fail through the vsock path. Split big files into ≤ 16 MiB chunks, or fetch them
  directly inside the sandbox (`exec` with `wget`).
- **`exec`/`run_code` are buffered**, not streaming — output arrives after the command
  exits (default timeout 30 s via `timeout_ms`). `run_code` source is capped at 1 MiB.
- **Template names and minimums are per-deployment.** `pt-base` (busybox — no
  git/pip/node/`httpd` inside; `nc`/`wget` available) exists on default installs; hosted
  deployments may only offer other templates with higher cpu/ram minimums. Call
  `templates.list()` first; a below-minimum create fails with a clear 400.
- **Background processes are reaped when their `exec` call returns.** To leave a server
  running, daemonize it: `setsid sh -c '<server loop>' >/dev/null 2>&1 < /dev/null &`
  — see `examples/02_expose_service.py`.

## Testing

`pip install -e '.[test]' && pytest` runs the offline unit suite (httpx.MockTransport,
no network). The live e2e is opt-in: `PT_E2E=1 PT_API_URL=… PT_TOKEN=… pytest test_e2e.py`.

## Examples

Runnable scripts in [`examples/`](./examples):

```sh
pip install platinum-sdk
PT_API_URL=… PT_TOKEN=… python packages/sdk-py/examples/01_create_exec_delete.py
```

## License

MIT
