runcrate-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runcrate_sdk-0.1.0/.github/workflows/notify-monorepo.yml +17 -0
- runcrate_sdk-0.1.0/.github/workflows/publish.yml +20 -0
- runcrate_sdk-0.1.0/PKG-INFO +276 -0
- runcrate_sdk-0.1.0/README.md +256 -0
- runcrate_sdk-0.1.0/pyproject.toml +45 -0
- runcrate_sdk-0.1.0/src/runcrate/__init__.py +44 -0
- runcrate_sdk-0.1.0/src/runcrate/_client.py +323 -0
- runcrate_sdk-0.1.0/src/runcrate/_config.py +51 -0
- runcrate_sdk-0.1.0/src/runcrate/_exceptions.py +147 -0
- runcrate_sdk-0.1.0/src/runcrate/_pagination.py +40 -0
- runcrate_sdk-0.1.0/src/runcrate/_transport.py +211 -0
- runcrate_sdk-0.1.0/src/runcrate/models/__init__.py +41 -0
- runcrate_sdk-0.1.0/src/runcrate/models/billing.py +36 -0
- runcrate_sdk-0.1.0/src/runcrate/models/environments.py +27 -0
- runcrate_sdk-0.1.0/src/runcrate/models/instances.py +68 -0
- runcrate_sdk-0.1.0/src/runcrate/models/models.py +172 -0
- runcrate_sdk-0.1.0/src/runcrate/models/shared.py +15 -0
- runcrate_sdk-0.1.0/src/runcrate/models/ssh_keys.py +26 -0
- runcrate_sdk-0.1.0/src/runcrate/models/storage.py +44 -0
- runcrate_sdk-0.1.0/src/runcrate/models/templates.py +18 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/__init__.py +26 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/billing.py +103 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/environments.py +95 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/instances.py +189 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/models.py +502 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/ssh_keys.py +69 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/storage.py +121 -0
- runcrate_sdk-0.1.0/src/runcrate/resources/templates.py +77 -0
- runcrate_sdk-0.1.0/tests/__init__.py +0 -0
- runcrate_sdk-0.1.0/tests/conftest.py +32 -0
- runcrate_sdk-0.1.0/tests/test_billing.py +84 -0
- runcrate_sdk-0.1.0/tests/test_client.py +74 -0
- runcrate_sdk-0.1.0/tests/test_instances.py +162 -0
- runcrate_sdk-0.1.0/tests/test_models.py +236 -0
- runcrate_sdk-0.1.0/tests/test_pagination.py +42 -0
- runcrate_sdk-0.1.0/tests/test_retry.py +64 -0
- runcrate_sdk-0.1.0/tests/test_ssh_keys.py +65 -0
- runcrate_sdk-0.1.0/tests/test_storage.py +146 -0
- runcrate_sdk-0.1.0/tests/test_templates.py +69 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Notify Monorepo
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
notify:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Trigger monorepo sync
|
|
12
|
+
run: |
|
|
13
|
+
curl -X POST \
|
|
14
|
+
-H "Authorization: token ${{ secrets.MONOREPO_TOKEN }}" \
|
|
15
|
+
-H "Accept: application/vnd.github.v3+json" \
|
|
16
|
+
https://api.github.com/repos/aeonmindai/runcrate_app/dispatches \
|
|
17
|
+
-d '{"event_type":"sdk-updated"}'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: '3.11'
|
|
15
|
+
- run: pip install build twine
|
|
16
|
+
- run: python -m build
|
|
17
|
+
- run: twine upload dist/*
|
|
18
|
+
env:
|
|
19
|
+
TWINE_USERNAME: __token__
|
|
20
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runcrate-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Runcrate API
|
|
5
|
+
Project-URL: Homepage, https://runcrate.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/aeonmindai/runcrate-py
|
|
7
|
+
Project-URL: Documentation, https://runcrate.ai/docs
|
|
8
|
+
Author-email: Runcrate <support@runcrate.ai>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: httpx<1.0,>=0.25.0
|
|
12
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
18
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Runcrate Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python SDK for the [Runcrate](https://runcrate.ai) API.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install runcrate
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from runcrate import Runcrate
|
|
35
|
+
|
|
36
|
+
client = Runcrate(api_key="rc_live_...")
|
|
37
|
+
|
|
38
|
+
# Chat completion
|
|
39
|
+
response = client.models.chat_completion(
|
|
40
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
41
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
42
|
+
)
|
|
43
|
+
print(response.choices[0].message.content)
|
|
44
|
+
|
|
45
|
+
# List GPU instances
|
|
46
|
+
instances = client.instances.list()
|
|
47
|
+
|
|
48
|
+
# Create an instance
|
|
49
|
+
instance = client.instances.create(
|
|
50
|
+
name="training-run",
|
|
51
|
+
ssh_key_id="key_abc123",
|
|
52
|
+
gpu_type="A100",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Check status
|
|
56
|
+
status = client.instances.get_status(instance.id)
|
|
57
|
+
print(status.status, status.ip)
|
|
58
|
+
|
|
59
|
+
# Check billing
|
|
60
|
+
balance = client.billing.get_balance()
|
|
61
|
+
print(f"Credits: ${balance.credits_balance}")
|
|
62
|
+
|
|
63
|
+
client.close()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Async Usage
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
from runcrate import AsyncRuncrate
|
|
71
|
+
|
|
72
|
+
async def main():
|
|
73
|
+
async with AsyncRuncrate(api_key="rc_live_...") as client:
|
|
74
|
+
response = await client.models.chat_completion(
|
|
75
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
76
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
77
|
+
)
|
|
78
|
+
print(response.choices[0].message.content)
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
client = Runcrate(
|
|
87
|
+
api_key="rc_live_...", # or set RUNCRATE_API_KEY env var
|
|
88
|
+
base_url="https://runcrate.ai", # infra API (default)
|
|
89
|
+
inference_url="https://api.runcrate.ai", # model inference API (default)
|
|
90
|
+
timeout=30.0, # request timeout in seconds
|
|
91
|
+
max_retries=3, # retry on 429/5xx with exponential backoff
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Model Inference
|
|
96
|
+
|
|
97
|
+
The `client.models` resource connects to `api.runcrate.ai` for AI model inference. Same API key works for both infrastructure and inference.
|
|
98
|
+
|
|
99
|
+
### Chat Completions
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# Standard request
|
|
103
|
+
response = client.models.chat_completion(
|
|
104
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
105
|
+
messages=[
|
|
106
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
107
|
+
{"role": "user", "content": "Explain quantum computing in 3 sentences."},
|
|
108
|
+
],
|
|
109
|
+
max_tokens=256,
|
|
110
|
+
temperature=0.7,
|
|
111
|
+
)
|
|
112
|
+
print(response.choices[0].message.content)
|
|
113
|
+
print(f"Tokens used: {response.usage.total_tokens}")
|
|
114
|
+
|
|
115
|
+
# Streaming
|
|
116
|
+
for chunk in client.models.chat_completion(
|
|
117
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
118
|
+
messages=[{"role": "user", "content": "Write a poem about GPUs"}],
|
|
119
|
+
stream=True,
|
|
120
|
+
):
|
|
121
|
+
delta = chunk.get("choices", [{}])[0].get("delta", {})
|
|
122
|
+
print(delta.get("content", ""), end="", flush=True)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Image Generation
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
result = client.models.generate_image(
|
|
129
|
+
model="black-forest-labs/FLUX.1-schnell",
|
|
130
|
+
prompt="A futuristic city skyline at sunset",
|
|
131
|
+
width=1024,
|
|
132
|
+
height=1024,
|
|
133
|
+
)
|
|
134
|
+
# result.data[0].b64_json contains the base64-encoded image
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Video Generation
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# Submit video job (async processing)
|
|
141
|
+
job = client.models.generate_video(
|
|
142
|
+
model="google/veo-3.0",
|
|
143
|
+
prompt="A drone flyover of a mountain landscape",
|
|
144
|
+
duration=8,
|
|
145
|
+
)
|
|
146
|
+
print(f"Job ID: {job.id}, Status: {job.status}")
|
|
147
|
+
|
|
148
|
+
# Poll until complete
|
|
149
|
+
import time
|
|
150
|
+
while True:
|
|
151
|
+
job = client.models.get_video_status(job.id)
|
|
152
|
+
print(f"Status: {job.status}")
|
|
153
|
+
if job.status in ("completed", "failed"):
|
|
154
|
+
break
|
|
155
|
+
time.sleep(5)
|
|
156
|
+
|
|
157
|
+
# Download the video
|
|
158
|
+
if job.status == "completed":
|
|
159
|
+
video_bytes = client.models.download_video(job.id)
|
|
160
|
+
with open("output.mp4", "wb") as f:
|
|
161
|
+
f.write(video_bytes)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Text-to-Speech
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
audio_bytes = client.models.text_to_speech(
|
|
168
|
+
model="openai/tts-1",
|
|
169
|
+
input="Hello, welcome to Runcrate!",
|
|
170
|
+
voice="alloy",
|
|
171
|
+
response_format="mp3",
|
|
172
|
+
)
|
|
173
|
+
with open("speech.mp3", "wb") as f:
|
|
174
|
+
f.write(audio_bytes)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Transcription (Speech-to-Text)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
with open("audio.wav", "rb") as f:
|
|
181
|
+
result = client.models.transcribe(
|
|
182
|
+
model="openai/whisper-1",
|
|
183
|
+
file=f,
|
|
184
|
+
filename="audio.wav",
|
|
185
|
+
)
|
|
186
|
+
print(result.text)
|
|
187
|
+
print(f"Duration: {result.duration}s")
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Infrastructure Resources
|
|
191
|
+
|
|
192
|
+
### Instances
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
client.instances.list(search="my-gpu")
|
|
196
|
+
client.instances.create(name="run", ssh_key_id="key", gpu_type="A100")
|
|
197
|
+
client.instances.get("instance-id")
|
|
198
|
+
client.instances.terminate("instance-id")
|
|
199
|
+
client.instances.get_status("instance-id")
|
|
200
|
+
client.instances.list_types(gpu_type="A100", region="us-east")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Environments
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
client.environments.list()
|
|
207
|
+
client.environments.create(name="staging")
|
|
208
|
+
client.environments.get("env-id")
|
|
209
|
+
client.environments.update("env-id", name="production")
|
|
210
|
+
client.environments.delete("env-id")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### SSH Keys
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
client.ssh_keys.list()
|
|
217
|
+
client.ssh_keys.create(name="my-key", public_key="ssh-ed25519 AAAA...")
|
|
218
|
+
client.ssh_keys.delete("key-id")
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Storage
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
client.storage.list()
|
|
225
|
+
client.storage.get("volume-id")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Billing
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
client.billing.get_balance()
|
|
232
|
+
client.billing.list_transactions(limit=10, offset=0)
|
|
233
|
+
client.billing.usage(from_date="2025-01-01", to_date="2025-01-31")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Templates
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
client.templates.list(search="cuda", category="ml", page_size=10)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Error Handling
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from runcrate import Runcrate, NotFoundError, InsufficientCreditsError, RateLimitError
|
|
246
|
+
|
|
247
|
+
client = Runcrate(api_key="rc_live_...")
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
instance = client.instances.get("nonexistent")
|
|
251
|
+
except NotFoundError as e:
|
|
252
|
+
print(f"Not found: {e.message}")
|
|
253
|
+
except InsufficientCreditsError as e:
|
|
254
|
+
print(f"Need more credits: {e.message}")
|
|
255
|
+
except RateLimitError as e:
|
|
256
|
+
print(f"Rate limited: {e.message}")
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Pagination
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# Transactions support offset-based pagination
|
|
263
|
+
page = client.billing.list_transactions(limit=10, offset=0)
|
|
264
|
+
print(page.data) # list of Transaction objects
|
|
265
|
+
print(page.has_more) # True if more pages exist
|
|
266
|
+
print(page.total) # total count
|
|
267
|
+
|
|
268
|
+
# Templates support page-based pagination
|
|
269
|
+
templates = client.templates.list(page=1, page_size=25)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Requirements
|
|
273
|
+
|
|
274
|
+
- Python >= 3.9
|
|
275
|
+
- httpx >= 0.25.0
|
|
276
|
+
- pydantic >= 2.0
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Runcrate Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Runcrate](https://runcrate.ai) API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install runcrate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from runcrate import Runcrate
|
|
15
|
+
|
|
16
|
+
client = Runcrate(api_key="rc_live_...")
|
|
17
|
+
|
|
18
|
+
# Chat completion
|
|
19
|
+
response = client.models.chat_completion(
|
|
20
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
21
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
22
|
+
)
|
|
23
|
+
print(response.choices[0].message.content)
|
|
24
|
+
|
|
25
|
+
# List GPU instances
|
|
26
|
+
instances = client.instances.list()
|
|
27
|
+
|
|
28
|
+
# Create an instance
|
|
29
|
+
instance = client.instances.create(
|
|
30
|
+
name="training-run",
|
|
31
|
+
ssh_key_id="key_abc123",
|
|
32
|
+
gpu_type="A100",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Check status
|
|
36
|
+
status = client.instances.get_status(instance.id)
|
|
37
|
+
print(status.status, status.ip)
|
|
38
|
+
|
|
39
|
+
# Check billing
|
|
40
|
+
balance = client.billing.get_balance()
|
|
41
|
+
print(f"Credits: ${balance.credits_balance}")
|
|
42
|
+
|
|
43
|
+
client.close()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Async Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import asyncio
|
|
50
|
+
from runcrate import AsyncRuncrate
|
|
51
|
+
|
|
52
|
+
async def main():
|
|
53
|
+
async with AsyncRuncrate(api_key="rc_live_...") as client:
|
|
54
|
+
response = await client.models.chat_completion(
|
|
55
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
56
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
57
|
+
)
|
|
58
|
+
print(response.choices[0].message.content)
|
|
59
|
+
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client = Runcrate(
|
|
67
|
+
api_key="rc_live_...", # or set RUNCRATE_API_KEY env var
|
|
68
|
+
base_url="https://runcrate.ai", # infra API (default)
|
|
69
|
+
inference_url="https://api.runcrate.ai", # model inference API (default)
|
|
70
|
+
timeout=30.0, # request timeout in seconds
|
|
71
|
+
max_retries=3, # retry on 429/5xx with exponential backoff
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Model Inference
|
|
76
|
+
|
|
77
|
+
The `client.models` resource connects to `api.runcrate.ai` for AI model inference. Same API key works for both infrastructure and inference.
|
|
78
|
+
|
|
79
|
+
### Chat Completions
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Standard request
|
|
83
|
+
response = client.models.chat_completion(
|
|
84
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
85
|
+
messages=[
|
|
86
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
87
|
+
{"role": "user", "content": "Explain quantum computing in 3 sentences."},
|
|
88
|
+
],
|
|
89
|
+
max_tokens=256,
|
|
90
|
+
temperature=0.7,
|
|
91
|
+
)
|
|
92
|
+
print(response.choices[0].message.content)
|
|
93
|
+
print(f"Tokens used: {response.usage.total_tokens}")
|
|
94
|
+
|
|
95
|
+
# Streaming
|
|
96
|
+
for chunk in client.models.chat_completion(
|
|
97
|
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
98
|
+
messages=[{"role": "user", "content": "Write a poem about GPUs"}],
|
|
99
|
+
stream=True,
|
|
100
|
+
):
|
|
101
|
+
delta = chunk.get("choices", [{}])[0].get("delta", {})
|
|
102
|
+
print(delta.get("content", ""), end="", flush=True)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Image Generation
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
result = client.models.generate_image(
|
|
109
|
+
model="black-forest-labs/FLUX.1-schnell",
|
|
110
|
+
prompt="A futuristic city skyline at sunset",
|
|
111
|
+
width=1024,
|
|
112
|
+
height=1024,
|
|
113
|
+
)
|
|
114
|
+
# result.data[0].b64_json contains the base64-encoded image
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Video Generation
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Submit video job (async processing)
|
|
121
|
+
job = client.models.generate_video(
|
|
122
|
+
model="google/veo-3.0",
|
|
123
|
+
prompt="A drone flyover of a mountain landscape",
|
|
124
|
+
duration=8,
|
|
125
|
+
)
|
|
126
|
+
print(f"Job ID: {job.id}, Status: {job.status}")
|
|
127
|
+
|
|
128
|
+
# Poll until complete
|
|
129
|
+
import time
|
|
130
|
+
while True:
|
|
131
|
+
job = client.models.get_video_status(job.id)
|
|
132
|
+
print(f"Status: {job.status}")
|
|
133
|
+
if job.status in ("completed", "failed"):
|
|
134
|
+
break
|
|
135
|
+
time.sleep(5)
|
|
136
|
+
|
|
137
|
+
# Download the video
|
|
138
|
+
if job.status == "completed":
|
|
139
|
+
video_bytes = client.models.download_video(job.id)
|
|
140
|
+
with open("output.mp4", "wb") as f:
|
|
141
|
+
f.write(video_bytes)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Text-to-Speech
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
audio_bytes = client.models.text_to_speech(
|
|
148
|
+
model="openai/tts-1",
|
|
149
|
+
input="Hello, welcome to Runcrate!",
|
|
150
|
+
voice="alloy",
|
|
151
|
+
response_format="mp3",
|
|
152
|
+
)
|
|
153
|
+
with open("speech.mp3", "wb") as f:
|
|
154
|
+
f.write(audio_bytes)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Transcription (Speech-to-Text)
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
with open("audio.wav", "rb") as f:
|
|
161
|
+
result = client.models.transcribe(
|
|
162
|
+
model="openai/whisper-1",
|
|
163
|
+
file=f,
|
|
164
|
+
filename="audio.wav",
|
|
165
|
+
)
|
|
166
|
+
print(result.text)
|
|
167
|
+
print(f"Duration: {result.duration}s")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Infrastructure Resources
|
|
171
|
+
|
|
172
|
+
### Instances
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
client.instances.list(search="my-gpu")
|
|
176
|
+
client.instances.create(name="run", ssh_key_id="key", gpu_type="A100")
|
|
177
|
+
client.instances.get("instance-id")
|
|
178
|
+
client.instances.terminate("instance-id")
|
|
179
|
+
client.instances.get_status("instance-id")
|
|
180
|
+
client.instances.list_types(gpu_type="A100", region="us-east")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Environments
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
client.environments.list()
|
|
187
|
+
client.environments.create(name="staging")
|
|
188
|
+
client.environments.get("env-id")
|
|
189
|
+
client.environments.update("env-id", name="production")
|
|
190
|
+
client.environments.delete("env-id")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### SSH Keys
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
client.ssh_keys.list()
|
|
197
|
+
client.ssh_keys.create(name="my-key", public_key="ssh-ed25519 AAAA...")
|
|
198
|
+
client.ssh_keys.delete("key-id")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Storage
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
client.storage.list()
|
|
205
|
+
client.storage.get("volume-id")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Billing
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
client.billing.get_balance()
|
|
212
|
+
client.billing.list_transactions(limit=10, offset=0)
|
|
213
|
+
client.billing.usage(from_date="2025-01-01", to_date="2025-01-31")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Templates
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
client.templates.list(search="cuda", category="ml", page_size=10)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Error Handling
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from runcrate import Runcrate, NotFoundError, InsufficientCreditsError, RateLimitError
|
|
226
|
+
|
|
227
|
+
client = Runcrate(api_key="rc_live_...")
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
instance = client.instances.get("nonexistent")
|
|
231
|
+
except NotFoundError as e:
|
|
232
|
+
print(f"Not found: {e.message}")
|
|
233
|
+
except InsufficientCreditsError as e:
|
|
234
|
+
print(f"Need more credits: {e.message}")
|
|
235
|
+
except RateLimitError as e:
|
|
236
|
+
print(f"Rate limited: {e.message}")
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Pagination
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# Transactions support offset-based pagination
|
|
243
|
+
page = client.billing.list_transactions(limit=10, offset=0)
|
|
244
|
+
print(page.data) # list of Transaction objects
|
|
245
|
+
print(page.has_more) # True if more pages exist
|
|
246
|
+
print(page.total) # total count
|
|
247
|
+
|
|
248
|
+
# Templates support page-based pagination
|
|
249
|
+
templates = client.templates.list(page=1, page_size=25)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Requirements
|
|
253
|
+
|
|
254
|
+
- Python >= 3.9
|
|
255
|
+
- httpx >= 0.25.0
|
|
256
|
+
- pydantic >= 2.0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runcrate-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Runcrate API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Runcrate", email = "support@runcrate.ai" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx>=0.25.0,<1.0",
|
|
17
|
+
"pydantic>=2.0,<3.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://runcrate.ai"
|
|
22
|
+
Repository = "https://github.com/aeonmindai/runcrate-py"
|
|
23
|
+
Documentation = "https://runcrate.ai/docs"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=7.0",
|
|
28
|
+
"pytest-asyncio>=0.21",
|
|
29
|
+
"respx>=0.21",
|
|
30
|
+
"ruff>=0.4",
|
|
31
|
+
"mypy>=1.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["src/runcrate"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
target-version = "py39"
|
|
39
|
+
line-length = 100
|
|
40
|
+
|
|
41
|
+
[tool.mypy]
|
|
42
|
+
strict = true
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Runcrate Python SDK — Official client for the Runcrate API."""
|
|
2
|
+
|
|
3
|
+
from runcrate._client import AsyncRuncrate, Runcrate
|
|
4
|
+
from runcrate._exceptions import (
|
|
5
|
+
ApiError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
BadRequestError,
|
|
8
|
+
ConflictError,
|
|
9
|
+
UnprocessableEntityError,
|
|
10
|
+
ConnectionError,
|
|
11
|
+
InsufficientCreditsError,
|
|
12
|
+
InternalServerError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
PermissionDeniedError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
RuncrateError,
|
|
17
|
+
TimeoutError,
|
|
18
|
+
)
|
|
19
|
+
from runcrate._pagination import PaginatedResponse
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AsyncRuncrate",
|
|
25
|
+
"Runcrate",
|
|
26
|
+
# Exceptions
|
|
27
|
+
"ApiError",
|
|
28
|
+
"AuthenticationError",
|
|
29
|
+
"BadRequestError",
|
|
30
|
+
"ConflictError",
|
|
31
|
+
"UnprocessableEntityError",
|
|
32
|
+
"ConnectionError",
|
|
33
|
+
"InsufficientCreditsError",
|
|
34
|
+
"InternalServerError",
|
|
35
|
+
"NotFoundError",
|
|
36
|
+
"PermissionDeniedError",
|
|
37
|
+
"RateLimitError",
|
|
38
|
+
"RuncrateError",
|
|
39
|
+
"TimeoutError",
|
|
40
|
+
# Pagination
|
|
41
|
+
"PaginatedResponse",
|
|
42
|
+
# Version
|
|
43
|
+
"__version__",
|
|
44
|
+
]
|