Metadata-Version: 2.5
Name: numen-sandbox-py
Version: 0.22.0
Summary: Python client for the Numen sandbox API
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.12
Requires-Dist: grpcio>=1.70.0
Requires-Dist: protobuf>=7.36.2
Description-Content-Type: text/markdown

# numen-sandbox-py

Python client for the Numen sandbox API. **0.x beta.** The API can change
between minor versions.

## Install

```bash
pip install numen-sandbox-py
```

## Credentials

The SDK reads `~/.config/numen/credentials.json`.

## Create and run

`image` is required. `numen/base` is the general platform image. The `with`
block destroys the sandbox when the block ends. `sb.destroy()` does the same.

```python
from numen_sandbox_py import Sandbox

with Sandbox.create(image="numen/base") as sb:
    print(sb.run(["python", "-c", "print(2+2)"]).stdout)
```

Pass `id=` to keep a stable name. The same `id` on a later `create` returns
that sandbox. `net="egress"` allows outbound network.

`timeout` is seconds on every call. Omit it to keep that call's default.
`0` means no limit: `run` does not kill the command, and a file call does
not stop on its own.

## Your own image

`build_image` sends a Dockerfile and the directory next to it. The name you
give is what you later pass to `create`.

```python
from numen_sandbox_py import Client, Sandbox

with Client() as c:
    c.build_image("my-agent", dockerfile="Dockerfile", context=".")
    with Sandbox.create(c, image="my-agent") as sb:
        print(sb.run(["python", "-c", "print(2+2)"]).stdout)
```

```dockerfile
FROM python:3.12-slim
```

`FROM` is always a public image. An image you have already built or
registered, including `numen/base`, cannot be the base of a new one.

## Files

Paths are under `/work`. `upload` writes one file, or a local directory as
one tree. A directory upload replaces that guest directory. Pass
`overwrite=False` to update only the paths you send.

```python
sb.upload("/work/hello.py", "print('hi')\n", mode=0o755)
sb.upload("/work/app", local="./app")
print(sb.run(["python", "/work/hello.py"]).stdout)
```

`write` is a batch of file bodies, `mkdir`, and `rm`, in the order you give.
`read` returns the bytes of one file.

## A process that stays up

`start` leaves the process running. `write_stdin` sends to that process.
`follow` yields stdout and stderr until it exits. `wait` blocks until it exits.

```python
proc = sb.start(["python", "-u", "-c", "print(input())"])
proc.write_stdin("hello\n", eof=True)
for entry in proc.follow():
    print(entry.stream, entry.line)
proc.wait()
```

## Async

`AsyncSandbox` and `AsyncClient` have the same methods. `await` them, and
use `async with`.
