Metadata-Version: 2.4
Name: chassis-cloud
Version: 0.1.5
Summary: Official Python SDK for the Chassis GPU cloud API by OkeyMeta Ltd
Project-URL: Homepage, https://chassis.okeymeta.com.ng/docs
Project-URL: Documentation, https://chassis.okeymeta.com.ng/docs
Project-URL: Repository, https://github.com/okeymeta/chassis
Project-URL: Issues, https://github.com/okeymeta/chassis/issues
Author-email: Okechukwu Nwaozor <okechukwunwaozor9@gmail.com>
License: MIT
Keywords: africa,ai,chassis,cloud,gpu,okeymeta,sdk
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: requests
Requires-Dist: requests>=2.31.0; extra == 'requests'
Description-Content-Type: text/markdown

# chassis-cloud (Python)

Official Python client for the Chassis public API (`/api/v1`).

Default base URL: `https://chassis.okeymeta.com.ng/api/v1`

PyPI: [chassis-cloud](https://pypi.org/project/chassis-cloud/) · import as `chassis`

## Install

```bash
pip install chassis-cloud
```

Requires **Python 3.10+** and **httpx**.

```python
from chassis import Chassis
```

## Auth

Create an API key in the Chassis console under **API Keys**. Keys look like `chs_…` and are shown **once**.

```python
from chassis import Chassis

with Chassis(api_key="chs_...") as client:
    ...
```

## Quickstart

```python
from chassis import Chassis

with Chassis(api_key="chs_...") as client:
    gpus = client.list_gpus()
    for gpu in gpus:
        print(gpu["displayName"], gpu["pricePerHourUsd"], gpu.get("stockStatus"))

    instance = client.spin_up(
        gpuSkuId=gpus[0]["id"],
        name="gpu-host-01",
        imageName="ghcr.io/YOUR_ORG/your-gpu-app:latest",
        ports="8080/http,22/tcp",
    )
```

## Example: list available GPUs

```python
with Chassis(api_key="chs_...") as client:
    gpus = client.list_gpus()
    for gpu in gpus:
        print(
            gpu["id"],
            gpu.get("displayName"),
            gpu.get("memoryGb"),
            f"${gpu.get('pricePerHourUsd')}/hr",
            gpu.get("stockStatus"),
        )

    gpu = next(
        (g for g in gpus if "4090" in str(g.get("displayName", ""))),
        gpus[0],
    )
    # use gpu["id"] as gpuSkuId on spin_up / create_cluster / create_endpoint
```

## Example: host a GPU service

Any CUDA app — APIs, media tools, notebooks, batch workers — not only training.

```python
with Chassis(api_key="chs_...") as client:
    gpus = client.list_gpus()
    instance = client.spin_up(
        gpuSkuId=gpus[0]["id"],
        name="gpu-host-01",
        imageName="ghcr.io/YOUR_ORG/your-gpu-app:latest",
        containerDiskGb=50,
        ports="8080/http,22/tcp",
        env={"MODEL_ID": "your-model"},
    )
    detail = client.get_instance(instance["id"])
    print(detail.get("publicIp"), detail.get("connection"), detail.get("status"))
    # Point your clients at publicIp / published ports
    client.stop(instance["id"])
```

## Example: training job

Spin up a dedicated GPU, run your trainer on the instance, then stop billing.

```python
from chassis import Chassis

with Chassis(api_key="chs_...") as client:
    gpus = client.list_gpus()
    gpu = next(
        (g for g in gpus if "A100" in str(g.get("displayName", ""))),
        gpus[0],
    )

    instance = client.spin_up(
        gpuSkuId=gpu["id"],
        name="finetune-bert",
        gpuCount=1,
        imageName="pytorch/pytorch:2.1.0-cuda11.8-cudnn8-devel",
        containerDiskGb=80,
        ports="8888/http,22/tcp",
    )
    print("running", instance["id"], instance["status"])
    # SSH / Jupyter → run train.py on the instance
    client.stop(instance["id"])
```

## Example: multi-node cluster

```python
with Chassis(api_key="chs_...") as client:
    gpus = client.list_gpus()
    cluster = client.create_cluster(
        name="dist-train",
        gpuSkuId=gpus[0]["id"],
        nodeCount=4,
        gpusPerNode=1,
        imageName="pytorch/pytorch:2.1.0-cuda11.8-cudnn8-devel",
    )
    print(client.get_cluster(cluster["id"]))
    # Nodes receive CHASSIS_CLUSTER_ID, CHASSIS_NODE_RANK, CHASSIS_NODE_COUNT
    client.stop_cluster(cluster["id"])
    # client.terminate_cluster(cluster["id"])
```

## Example: serverless endpoint

```python
with Chassis(api_key="chs_...") as client:
    gpus = client.list_gpus()
    endpoint = client.create_endpoint(
        name="text-infer",
        gpuSkuId=gpus[0]["id"],
        workersMin=0,
        workersMax=3,
    )

    result = client.run_sync(
        endpoint["id"],
        body={
            "input": {
                "prompt": "Summarize Chassis in one sentence.",
                "max_tokens": 128,
            }
        },
    )
    print(result)

    job = client.run(endpoint["id"], body={"input": {"prompt": "hello"}})
    status = client.get_job(endpoint["id"], job["id"])
    print(status)
```

## Instance options (`create_instance` / `spin_up`)

| Field | Required | Notes |
|-------|----------|--------|
| `gpuSkuId` | yes | Chassis SKU UUID from `list_gpus()` |
| `name` | yes | Instance name |
| `gpuCount` | no | Default `1`, max `8` |
| `imageName` | no | Container image |
| `containerDiskGb` | no | Default `50` |
| `volumeGb` | no | Ephemeral volume GB |
| `networkVolumeId` | no | Chassis network volume UUID |
| `registryCredentialId` | no | Chassis registry credential UUID |
| `cloudType` | no | `"SECURE"` or `"COMMUNITY"` |
| `ports` | no | e.g. `"8080/http,22/tcp"` |
| `env` | no | String map passed into the container |
| `startAfterCreate` | no | Default `True` (`spin_up` forces `True`) |

## Methods

### GPUs & instances

| Method | HTTP |
|--------|------|
| `list_gpus()` | `GET /gpus` |
| `list_instances()` | `GET /instances` |
| `create_instance(**kwargs)` | `POST /instances` |
| `spin_up(**kwargs)` | `POST /instances` (`startAfterCreate: true`) |
| `get_instance(id)` | `GET /instances/:id` |
| `get_instance_logs(id, tail=None)` | `GET /instances/:id/logs` |
| `start(id)` | `POST /instances/:id/start` |
| `stop(id)` | `POST /instances/:id/stop` |
| `restart(id)` | `POST /instances/:id/restart` |
| `terminate(id)` | `DELETE /instances/:id` |

### Clusters (multi-node)

| Method | HTTP |
|--------|------|
| `list_clusters()` | `GET /clusters` |
| `create_cluster(**kwargs)` | `POST /clusters` |
| `get_cluster(id)` | `GET /clusters/:id` |
| `start_cluster(id)` | `POST /clusters/:id/start` |
| `stop_cluster(id)` | `POST /clusters/:id/stop` |
| `terminate_cluster(id)` | `DELETE /clusters/:id` |

Create with `name`, `gpuSkuId`, `nodeCount` (2–8), and optional `gpusPerNode`, `imageName`, `networkVolumeId`, `ports`. Nodes get `CHASSIS_CLUSTER_ID`, `CHASSIS_NODE_RANK`, and `CHASSIS_NODE_COUNT`.

### Templates, volumes, registries

| Method | HTTP |
|--------|------|
| `list_templates()` / `create_template(**kwargs)` | `/templates` |
| `list_volumes()` / `create_volume(**kwargs)` | `/volumes` |
| `list_registries()` / `create_registry(**kwargs)` | `/registries` |

### Serverless endpoints

| Method | HTTP |
|--------|------|
| `list_endpoints()` | `GET /endpoints` |
| `create_endpoint(**kwargs)` | `POST /endpoints` |
| `delete_endpoint(id)` | `DELETE /endpoints/:id` |
| `run(endpoint_id, body=None)` | `POST /endpoints/:id/run` |
| `run_sync(endpoint_id, body=None, wait_ms=None)` | `POST /endpoints/:id/runsync` |

Create options also accept `env`, `interruptible`, and `publicIp`.

Responses use `data` / `error`. Failures raise `ChassisError` with `.status`, `.body`, and a clear message.

## Docs

Human setup guide: [https://chassis.okeymeta.com.ng/docs](https://chassis.okeymeta.com.ng/docs)

## License

MIT · OkeyMeta Ltd
