feat: created README

This commit is contained in:
Tasnim Kabir Sadik
2026-04-13 03:16:44 +06:00
parent bf5914c0a8
commit 0ac9bf79ee

371
README.md
View File

@ -1,3 +1,370 @@
# python-sdk
# Wrenn Python SDK
Python SDK for wrenn
Python client for the [Wrenn](https://wrenn.dev) microVM code execution platform. Create isolated capsules, execute commands, manage files, run interactive terminals, and execute persistent code — all from Python.
## Installation
```bash
pip install wrenn
```
Requires Python 3.13+.
## Quick Start
```python
from wrenn import WrennClient
client = WrennClient(api_key="wrn_your_api_key_here")
# Create a capsule and run a command
with client.capsules.create(template="minimal", timeout_sec=120) as cap:
cap.wait_ready(timeout=60)
result = cap.exec("echo", args=["hello world"])
print(result.stdout) # "hello world"
print(result.exit_code) # 0
```
## Authentication
The SDK supports two authentication methods:
```python
# API key
client = WrennClient(api_key="wrn_...")
# JWT token
client = WrennClient(token="eyJ...")
```
You can obtain an API key via the dashboard or create one programmatically:
```python
with WrennClient(token="jwt_token") as client:
key = client.api_keys.create(name="my-key")
print(key.key) # wrn_...
```
## Capsules
Capsules are isolated microVM environments. Create, manage, and interact with them:
```python
# Create
cap = client.capsules.create(
template="base-python",
vcpus=2,
memory_mb=1024,
timeout_sec=300,
)
# List
for c in client.capsules.list():
print(c.id, c.status)
# Get
cap = client.capsules.get("cl-abc123")
# Destroy
client.capsules.destroy("cl-abc123")
```
### Context Manager
Use capsules as context managers for automatic cleanup:
```python
with client.capsules.create(template="minimal", timeout_sec=120) as cap:
cap.wait_ready(timeout=60)
cap.exec("python -c 'print(42)'")
# cap.destroy() is called automatically
```
## Command Execution
### `exec()` — One-off Commands
Starts a fresh process for each call. No state persists between calls.
```python
result = cap.exec("python", args=["-c", "import os; print(os.getcwd())"])
print(result.stdout) # "/home/user\n"
print(result.stderr) # ""
print(result.exit_code) # 0
print(result.duration_ms) # 42
```
### `exec_stream()` — Streaming Output
Stream real-time output from long-running commands:
```python
for event in cap.exec_stream("python", args=["-u", "train.py"]):
match event.type:
case "stdout":
print(event.data, end="")
case "stderr":
print(event.data, end="", file=sys.stderr)
case "exit":
print(f"\nExited with code {event.exit_code}")
```
### `run_code()` — Stateful Code Execution
Execute Python code in a persistent Jupyter kernel. Variables, imports, and function definitions survive across calls:
```python
with client.capsules.create(template="python-interpreter-v0-beta") as cap:
cap.wait_ready(timeout=60)
cap.run_code("x = 42")
r = cap.run_code("x * 2")
print(r.text) # "84"
cap.run_code("def greet(name): return f'hello {name}'")
r = cap.run_code("greet('world')")
print(r.text) # "'hello world'"
r = cap.run_code("1/0")
print(r.error) # "ZeroDivisionError: division by zero\n..."
```
**`CodeResult` fields:**
| Field | Type | Description |
|-------|------|-------------|
| `text` | `str \| None` | Plain text representation |
| `data` | `dict \| None` | Rich MIME bundle (e.g. `{"image/png": "..."}`) |
| `stdout` | `str` | Accumulated stdout |
| `stderr` | `str` | Accumulated stderr |
| `error` | `str \| None` | Error traceback string |
## Filesystem
Upload, download, and manage files inside capsules:
```python
# Upload / Download
cap.upload("/app/main.py", b"print('hello')")
content = cap.download("/app/main.py")
# Streaming (for large files)
def chunks():
yield b"chunk1"
yield b"chunk2"
cap.stream_upload("/data/large.bin", chunks())
for chunk in cap.stream_download("/data/large.bin"):
process(chunk)
# Directory operations
entries = cap.list_dir("/home/user", depth=1)
for entry in entries:
print(entry.name, entry.type, entry.size)
cap.mkdir("/home/user/data")
cap.remove("/home/user/old_data")
```
## Interactive Terminal (PTY)
Open a full interactive terminal session over WebSocket:
```python
with cap.pty(cmd="/bin/bash", cols=120, rows=40, cwd="/home/user") as term:
term.write(b"ls -la\n")
for event in term:
if event.type == "output":
sys.stdout.buffer.write(event.data)
elif event.type == "exit":
break
```
**PtySession methods:**
| Method | Description |
|--------|-------------|
| `write(data: bytes)` | Send raw bytes to stdin |
| `resize(cols, rows)` | Resize the terminal |
| `kill()` | Send SIGKILL to the process |
| `tag` | Session tag (available after `started` event) |
| `pid` | Process PID (available after `started` event) |
Reconnect to an existing session using the tag:
```python
with cap.pty_connect(term.tag) as term:
term.write(b"echo reconnected\n")
```
## Lifecycle
Pause and resume capsules to save resources:
```python
cap = client.capsules.create(template="minimal")
cap.wait_ready(timeout=60)
# Pause (snapshots and releases resources)
cap.pause()
print(cap.status) # "paused"
# Resume (restores from snapshot)
cap.resume()
cap.wait_ready(timeout=60)
```
Keep a capsule alive with `ping()`:
```python
cap.ping() # Resets the inactivity timer
```
## Proxy URL
Access services running inside a capsule through the proxy:
```python
url = cap.get_url(8888)
# "wss://8888-cl-abc123.api.wrenn.dev"
# Pre-configured HTTP client targeting port 8888
resp = cap.http_client.get("/api/kernels")
```
## Snapshots
Create templates from running capsules:
```python
# Create a snapshot
template = client.snapshots.create(
capsule_id="cl-abc123",
name="my-template",
overwrite=True,
)
# List templates
for t in client.snapshots.list():
print(t.name, t.type)
# Delete
client.snapshots.delete("my-template")
```
## Hosts
Manage host machines:
```python
host = client.hosts.create(type="regular")
client.hosts.list()
client.hosts.get("h-1")
client.hosts.delete("h-1")
client.hosts.regenerate_token("h-1")
client.hosts.list_tags("h-1")
client.hosts.add_tag("h-1", "gpu")
client.hosts.remove_tag("h-1", "gpu")
```
## Async Support
All operations have async variants. Use `AsyncWrennClient` and prefix capsule methods with `async_`:
```python
from wrenn import AsyncWrennClient
async with AsyncWrennClient(api_key="wrn_...") as client:
cap = await client.capsules.create(template="minimal")
await cap.async_wait_ready(timeout=60)
result = await cap.async_exec("echo", args=["hello"])
await cap.async_upload("/app/file.txt", b"data")
entries = await cap.async_list_dir("/home/user")
r = await cap.async_run_code("42 * 2")
await cap.async_destroy()
```
**Async method mapping:**
| Sync | Async |
|------|-------|
| `exec()` | `async_exec()` |
| `upload()` | `async_upload()` |
| `download()` | `async_download()` |
| `stream_upload()` | `async_stream_upload()` |
| `stream_download()` | `async_stream_download()` |
| `list_dir()` | `async_list_dir()` |
| `mkdir()` | `async_mkdir()` |
| `remove()` | `async_remove()` |
| `wait_ready()` | `async_wait_ready()` |
| `pause()` | `async_pause()` |
| `resume()` | `async_resume()` |
| `destroy()` | `async_destroy()` |
| `ping()` | `async_ping()` |
| `run_code()` | `async_run_code()` |
## Error Handling
The SDK maps server error codes to typed exceptions:
```python
from wrenn import (
WrennError,
WrennValidationError, # 400
WrennAuthenticationError, # 401
WrennForbiddenError, # 403
WrennNotFoundError, # 404
WrennConflictError, # 409
WrennHostHasCapsulesError, # 409 — host has running capsules
WrennAgentError, # 502
WrennInternalError, # 500
WrennHostUnavailableError, # 503
)
try:
client.capsules.get("nonexistent")
except WrennNotFoundError as e:
print(e.code) # "not_found"
print(e.message) # "capsule not found"
print(e.status_code) # 404
```
All exceptions inherit from `WrennError` and expose `.code`, `.message`, and `.status_code`.
## Development
This project uses [uv](https://docs.astral.sh/uv/) for dependency management.
```bash
# Install dependencies
uv sync
# Run linting
make lint
# Run unit tests
make test
# Run all tests (including integration)
make test-integration
# Regenerate models from OpenAPI spec
make generate
```
### Running Integration Tests
Integration tests require a live Wrenn server. Set environment variables:
```bash
export WRENN_API_KEY="wrn_..."
export WRENN_BASE_URL="http://localhost:8080" # optional
make test-integration
```
## License
MIT