Metadata-Version: 2.5
Name: anycloud-sdk
Version: 0.1.57
Summary: Python SDK for anycloud — submit jobs, run workloads on any cloud
License-Expression: MIT
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# anycloud Python SDK

Submit jobs, run workloads on any cloud.

## Install

```bash
pip install anycloud-sdk
```

## Quick start

```python
import anycloud

ac = anycloud.Client()

job = ac.submit("my-training:latest", gpu="h100:8", env={"LR": "0.01"})
job.wait()
print(job.logs())
```

## Chaining jobs

```python
prep = ac.submit("prep:latest")
prep.wait()

train = ac.submit("train:latest", gpu="h100:8", env={"LR": "0.01"})
train.wait()

eval_job = ac.submit("eval:latest")
eval_job.wait()
```

## Fan-out / fan-in

```python
from anycloud import Submission

split = ac.submit("split:latest")
split.wait()

shards = ac.submit_many([
    Submission(image="worker:latest", env={"LR": lr})
    for lr in ["0.1", "0.01", "0.001"]
])
shards.wait()

merge = ac.submit("merge:latest")
merge.wait()
```

## Decorator (run a Python function remotely)

Skip the image-build loop — decorate a function, and AnyCloud clones your repo on the remote VM:

```python
@anycloud.function(image="ghcr.io/acme/trainer:latest", gpu="h100:8")
def train(lr: float):
    ...

jobs = train.map([0.1, 0.01, 0.001])  # fan out across args
jobs.wait()
```

Decorator deployments clone your committed, pushed GitHub repo on the remote VM. Private repos work after `anycloud login`, or with `GITHUB_TOKEN` set to a token with repo read access. The image must include `git`. See [reference](https://anycloud.sh/docs/reference/python-sdk#function-decorator).

## Serve decorator (run a long-lived daemon)

Use `@anycloud.serve` for one long-running server process with a stable URL:

```python
import os
import anycloud

@anycloud.serve(image="ghcr.io/acme/inference:latest", gpu="L40S:1")
def node():
    import uvicorn
    from myapp import app

    uvicorn.run(app, host="0.0.0.0", port=int(os.environ["PORT"]))

server = node.start(id="model-a-001")
server.wait_running(timeout=600)
print(server.url)
```

The entrypoint must block. AnyCloud sets `PORT=8088` by default; override it with `env={"PORT": "<port>"}`. The public URL is `https://<deployment-id>.anycloud.sh`. `@anycloud.serve` uses the same private-repo git-clone authentication as `@anycloud.function`.

The live SDK e2e suite includes serve-decorator coverage:

```bash
bash test/integration/e2e.sh sdk-python-serve
```

When a remote function fails, `DeploymentFailedError.logs` includes the labeled bootstrap error:

```python
from anycloud import DeploymentFailedError

try:
    train.submit(0.01).wait()
except DeploymentFailedError as e:
    print(e.state, e.logs)
```

## Buckets

Discover storage, inspect objects, and chain data between jobs using bucket
handles. Select a credential saved with `anycloud credentials new`:

```python
ac = anycloud.Client(credentials="aws-prod")

for bucket in ac.list_buckets():
    print(bucket.name, bucket.region, bucket.created_at)

data  = ac.bucket("training-data")
model = ac.bucket("model-output")

data.upload("~/datasets/imagenet.tar", remote_path="datasets/imagenet.tar")
data.upload_directory("~/datasets/shards", prefix="datasets/shards/")

for entry in data.list_objects(prefix="datasets/"):
    print(entry.kind, entry.key, entry.size_bytes)

all_objects = data.list_objects(recursive=True, limit=None)

prep  = ac.submit("prep:latest", input=data, output=model)
prep.wait()

train = ac.submit("train:latest", input=model, output=model, gpu="h100:8")
train.wait()

model.download_directory("~/checkpoints", prefix="checkpoints/")
```

Listings default to 1,000 sorted results; `limit=None` exhausts every API page.
Prefixes are literal, so include a trailing `/` to browse a folder. Bucket I/O
and listing stream through the local `anycloud api` server—no provider SDK
extras are needed. Input buckets must already exist; output buckets used by a
Job can be created by the server.

Downloads validate the received byte count and atomically replace the target
only after a complete response. Incomplete responses are retried twice, then
raise `DownloadIntegrityError` while preserving any existing target.

Directory upload, download, and prefix deletion use four workers by default;
pass `concurrency=1` through `32` to tune the bound. They return a
`BucketOperationResult`. Partial item failures raise `BucketOperationError`
with the result summary and ordered `(object_key, exception)` failures.
