Metadata-Version: 2.5
Name: gmi-sandbox-sdk
Version: 1.1.1
Summary: GMI Sandbox SDK for Python.
Project-URL: Homepage, https://www.gmicloud.ai/
Author: GMI
License: MIT
License-File: LICENSE
Keywords: gmi,sandbox,sdk
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.27
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

---
title: "GMI Sandbox SDK Usage"
description: "Install, configure, and use the GMI Sandbox Python SDK."
---

# GMI Sandbox SDK Usage

## Install

```bash
python -m pip install gmi-sandbox-sdk
```

## Configure

Use environment variables or pass values directly.

To get an API key, log in to `https://console.gmicloud.ai/`, click **API keys**, select **compute**, and click **Create API key**.

```bash
export GMI_SANDBOX_API_KEY="your-api-key"
```

`GMI_SANDBOX_IDC_NAME` is optional. Leave it unset to use your organization's default IDC. Set it only when you want requests to target a non-default IDC:

```bash
export GMI_SANDBOX_IDC_NAME="gmi-sandbox-us"
```

## Create a client

```python
from sandbox_sdk import SandboxClient

client = SandboxClient()
```

You can also pass credentials explicitly:

```python
client = SandboxClient(
    api_key="your-api-key",
)
```

### Async client

`AsyncSandboxClient` provides the same resources and operations with native
async HTTP I/O. Use it as an async context manager so pooled connections are
closed automatically:

```python
from sandbox_sdk import AsyncSandboxClient

async with AsyncSandboxClient() as client:
    sandbox = await client.sandboxes.create(template_id="template-id")
    await sandbox.connect()
    result = await sandbox.commands.run("echo hello", wait=True)
    print(result.stdout)
    await sandbox.files.write("/tmp/hello.txt", "hello")
    await sandbox.delete()
```

When not using an async context manager, call `await client.aclose()` after
the client is no longer needed. `aclose()` closes only the HTTP client the SDK
created for you; a `transport` you pass in stays open and remains yours to
close. Requests issued after `aclose()` raise `TransportError`, so create a new
client instead of reusing a closed one.

Both clients only send requests to `http` and `https` URLs, and they validate
every redirect hop against that rule. This is a scheme check: it does not
restrict which hosts a `base_url` you configure can reach.

## Create and use a sandbox

```python
sandbox = client.sandboxes.create(
    template_id="template-id",
)

sandbox.connect()
result = sandbox.commands.run("echo hello", wait=True)
print(result.stdout)

sandbox.files.write("/tmp/hello.txt", "hello")
print(sandbox.files.read("/tmp/hello.txt").decode())

sandbox.delete()
```

If you already have a sandbox ID:

```python
sandbox = client.sandboxes.get("sandbox-id")
sandbox.connect()
```

## Files

```python
sandbox.files.read("/tmp/a.txt")
sandbox.files.write("/tmp/a.txt", "hello")
sandbox.files.write("/tmp/a.bin", b"binary-data")
sandbox.files.upload("./local.txt", "/workspace/local.txt")
sandbox.files.download("/workspace/output.txt", "./output.txt")
```

`read()` and `download()` buffer the complete response in memory. `download()`
returns bytes and can also write to a destination path; use its `byte_range`
argument to retrieve large files in chunks.

## Commands

```python
execution = sandbox.commands.run(
    "python --version",
    cwd="/workspace",
    envs={"PYTHONUNBUFFERED": "1"},
    wait=True,
    wait_timeout_seconds=25,
)

print(execution.status)
print(execution.exit_code)
print(execution.stdout)
print(execution.stderr)
```

You can refresh or cancel a running execution:

```python
execution.refresh()
execution.cancel()
```

## Templates

```python
template = client.templates.get("template-id")
template.update(name="new-name")
template.delete()

builds = template.builds()
build = template.build("build-id")
logs = template.build_logs("build-id", offset=0, limit=100)
```

Creating a template requires an idempotency key:

```python
template = client.templates.create(
    name="demo",
    resources={"type": "preset", "product": "gmi.sandbox.small"},
    build={"source": {"type": "image", "image": "ubuntu:22.04"}},
    idempotency_key="template-1",
)

print(template.template_id)
template.update(name="new-name")
```

Create from a Dockerfile and local build context:

```python
from pathlib import Path

template = client.templates.create(
    name="dockerfile-demo",
    resources={"type": "preset", "product": "gmi.sandbox.small"},
    dockerfile_content=Path("Dockerfile").read_text(),
    build_context_dir=".",
    idempotency_key="template-dockerfile-1",
)
```

The live API accepts one uploaded context archive. The SDK translates
`COPY . <destination>` and `ADD . <destination>` to the API's Template-file
reference syntax. Other local source forms are rejected before a request is
sent. `AsyncSandboxClient.templates.create()` accepts the same arguments.

Rebuild an existing template without replacing it with
`client.templates.builds.create(template.template_id, dockerfile_content=..., build_context_dir=..., start_cmd=..., idempotency_key=...)`; the async client exposes the same call and returns the newly created build, while `template.wait_until_ready()` remains the readiness helper.

## Product specifications

```python
client.product_specifications.list()
client.product_specifications.list(idc_name="gmi-sandbox-us")
```

## Errors

The SDK raises typed exceptions for HTTP failures:

- `BadRequestError`
- `AuthenticationError`
- `PermissionDeniedError`
- `NotFoundError`
- `ConflictError`
- `RateLimitError`
- `ServerError`

Example:

```python
from sandbox_sdk import NotFoundError

try:
    client.sandboxes.get("missing")
except NotFoundError:
    print("sandbox not found")
```
