Metadata-Version: 2.5
Name: dwsandbox-client
Version: 0.2.0
Summary: Python client for the DonkeyWork Sandbox control plane (REST + SSE), with types generated from its OpenAPI spec.
Project-URL: Homepage, https://sandbox.donkeywork.dev
Project-URL: Source, https://github.com/andyjmorgan/DonkeyWork-Sandbox
Author: Andrew Morgan
License-Expression: MIT
Keywords: donkeywork,grpc,mcp,openapi,sandbox
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Description-Content-Type: text/markdown

# dwsandbox-client

A typed Python client for the **DonkeyWork Sandbox** control plane. The models are
generated from the service's OpenAPI spec; the clients wrap them with a base URL and
an API key.

```bash
pip install dwsandbox-client
```

## Usage

Give it your sandbox access key (`dws_...`) and it exposes two provisioning methods.
Both a synchronous and an asynchronous client ship in the box.

### `create` — provision and wait

Returns the ready sandbox in one call (blocks ~15s while it provisions), including the
absolute MCP URL:

```python
from dwsandbox_client import SandboxClient

with SandboxClient("dws_your_key") as sandbox:
    ready = sandbox.create()                      # or create(template_id=..., sandbox_id=...)
    print(ready.status, ready.mcp_url)
```

### `create_stream` — provision with live progress

Yields each provisioning event as it happens:

```python
from dwsandbox_client import SandboxClient, SandboxStatus

with SandboxClient("dws_your_key") as sandbox:
    for event in sandbox.create_stream():
        print(f"[{event.stage}] {event.message}")
        if event.is_terminal:
            assert event.status is SandboxStatus.ready
```

### Async

Same surface, `await` / `async for`:

```python
import asyncio
from dwsandbox_client import AsyncSandboxClient

async def main():
    async with AsyncSandboxClient("dws_your_key") as sandbox:
        ready = await sandbox.create()
        print(ready.mcp_url)
        async for event in sandbox.create_stream():
            print(event.stage, event.status)

asyncio.run(main())
```

The access key may also be sent by your own `httpx` client — pass `http_client=` to
either constructor to reuse a configured client (proxies, custom timeouts, etc.). The
base URL defaults to `https://sandbox.donkeywork.dev`; override with `base_url=`.

> **Note on transport.** `create` and `create_stream` both go over REST/SSE, which is
> the transport that works reliably through the public edge. (Native gRPC provisioning
> exists for in-cluster/LAN callers but the edge does not proxy its long-lived stream.)

## Types

`dwsandbox_client.models` contains the full set of shapes generated from the OpenAPI
spec. The commonly used ones are re-exported at the top level:
`CreateSandboxRequest`, `CreateSandboxResponse`, `ProvisioningEvent`, `SandboxStatus`.
Fields are snake_case with camelCase wire aliases (e.g. `ready.mcp_url` ↔ `mcpUrl`).

## Development

Regenerate the models after the API's OpenAPI spec changes:

```bash
./scripts/generate.sh        # regenerates src/dwsandbox_client/models.py from openapi.json
```

`scripts/generate.sh` documents how to refresh `openapi.json` from the running API.

### Releasing (PyPI, via GitHub Actions)

Publishing is automated through `.github/workflows/pyclient-publish.yml`, which uploads
to PyPI using **Trusted Publishing** (OIDC, no stored token). Two ways to cut a release:

- **Auto minor bump (normal path):** GitHub → **Actions → Publish dwsandbox-client →
  Run workflow**. It bumps the *minor* version in `pyproject.toml`, commits it, tags
  `pyclient-v<version>`, and publishes — no version to pick by hand.
- **Specific version:** push the tag yourself (used for the first release):

  ```bash
  git tag pyclient-v0.1.0 && git push origin pyclient-v0.1.0
  ```

Either way the workflow verifies the tag matches `pyproject.toml`, builds the sdist +
wheel, and publishes.

**One-time PyPI setup** (only the project owner can do this): on
[pypi.org](https://pypi.org) create the `dwsandbox-client` project (or a *pending*
publisher before the first release) and add a **Trusted Publisher** pointing at this
GitHub repo, workflow `pyclient-publish.yml`, and environment `pypi`. No API token or
GitHub secret is needed after that.
