keynode 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.
- keynode-0.1.0/PKG-INFO +324 -0
- keynode-0.1.0/README.md +300 -0
- keynode-0.1.0/pyproject.toml +39 -0
- keynode-0.1.0/setup.cfg +4 -0
- keynode-0.1.0/src/keynode/__init__.py +3 -0
- keynode-0.1.0/src/keynode/client.py +132 -0
- keynode-0.1.0/src/keynode/config.py +15 -0
- keynode-0.1.0/src/keynode/exceptions.py +15 -0
- keynode-0.1.0/src/keynode/resources/__init__.py +11 -0
- keynode-0.1.0/src/keynode/resources/api_keys.py +29 -0
- keynode-0.1.0/src/keynode/resources/backups.py +23 -0
- keynode-0.1.0/src/keynode/resources/datasets.py +42 -0
- keynode-0.1.0/src/keynode/resources/gpu_catalog.py +49 -0
- keynode-0.1.0/src/keynode/resources/me.py +22 -0
- keynode-0.1.0/src/keynode/resources/runs.py +96 -0
- keynode-0.1.0/src/keynode/resources/secrets.py +28 -0
- keynode-0.1.0/src/keynode/resources/sources.py +35 -0
- keynode-0.1.0/src/keynode/types.py +0 -0
- keynode-0.1.0/src/keynode.egg-info/PKG-INFO +324 -0
- keynode-0.1.0/src/keynode.egg-info/SOURCES.txt +24 -0
- keynode-0.1.0/src/keynode.egg-info/dependency_links.txt +1 -0
- keynode-0.1.0/src/keynode.egg-info/entry_points.txt +2 -0
- keynode-0.1.0/src/keynode.egg-info/requires.txt +3 -0
- keynode-0.1.0/src/keynode.egg-info/top_level.txt +2 -0
- keynode-0.1.0/src/keynode_cli/__init__.py +0 -0
- keynode-0.1.0/src/keynode_cli/main.py +619 -0
keynode-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keynode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK and CLI for KeyNode — AI compute on distributed GPUs
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://keynode.es
|
|
7
|
+
Project-URL: Documentation, https://docs.keynode.es
|
|
8
|
+
Project-URL: Repository, https://github.com/keynode-ai/keynode-python
|
|
9
|
+
Keywords: keynode,gpu,ai,ml,training,cloud,sdk,cli
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.27.0
|
|
22
|
+
Requires-Dist: typer>=0.12.3
|
|
23
|
+
Requires-Dist: rich>=13.7.1
|
|
24
|
+
|
|
25
|
+
# KeyNode Python SDK & CLI
|
|
26
|
+
|
|
27
|
+
Official Python SDK and CLI for [KeyNode](https://keynode.es) — the AI compute platform that connects your workloads to underutilized GPUs from distributed providers. Run training jobs and inference cheaper and faster than traditional cloud.
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Python 3.10+
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install keynode
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install from source:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone <repo_url>
|
|
43
|
+
cd keynode-python
|
|
44
|
+
pip install .
|
|
45
|
+
|
|
46
|
+
# Development (editable) install
|
|
47
|
+
pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Authentication
|
|
51
|
+
|
|
52
|
+
### Option 1 — CLI login (recommended)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
keynode auth login
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This prompts for your API key and saves it to `~/.keynode/config.json`.
|
|
59
|
+
|
|
60
|
+
### Option 2 — Environment variables
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export KEYNODE_API_KEY="kn_xxx"
|
|
64
|
+
export KEYNODE_BASE_URL="https://api.keynode.es" # optional, this is the default
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Option 3 — Direct SDK initialization
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from keynode import KeyNodeClient
|
|
71
|
+
|
|
72
|
+
client = KeyNodeClient(api_key="kn_xxx", base_url="https://api.keynode.es")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## CLI Usage
|
|
78
|
+
|
|
79
|
+
### Auth
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
keynode auth login # Save API key to config
|
|
83
|
+
keynode auth whoami # Verify authentication
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### GPU Catalog
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
keynode gpu list # List all available GPUs
|
|
90
|
+
keynode gpu list --min-vram 24 # Filter by minimum VRAM
|
|
91
|
+
keynode gpu list --max-price 1.5 --region eu-west # Filter by price and region
|
|
92
|
+
keynode gpu list --model "RTX 4090" # Search by model name
|
|
93
|
+
keynode gpu stats # Platform-wide GPU stats
|
|
94
|
+
keynode gpu detail "NVIDIA A100" --vram 80 # Detailed info for a GPU SKU
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Runs
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
keynode run list # List all runs
|
|
101
|
+
keynode run get <run_id> # Get run details
|
|
102
|
+
keynode run create -f payload.json # Create run from JSON file
|
|
103
|
+
keynode run cancel <run_id> # Cancel a run
|
|
104
|
+
keynode run retry <run_id> # Retry a run immediately
|
|
105
|
+
keynode run clone-retry <run_id> # Clone and retry a run
|
|
106
|
+
keynode run resume <run_id> # Resume a paused run
|
|
107
|
+
keynode run wait <run_id> # Block until run finishes
|
|
108
|
+
keynode run wait <run_id> --timeout 3600 # Wait with timeout (seconds)
|
|
109
|
+
keynode run metrics <run_id> # GPU/CPU metrics time-series
|
|
110
|
+
keynode run manifest <run_id> # Show run manifest
|
|
111
|
+
keynode run download-manifest <run_id> -o out.json # Download manifest as JSON
|
|
112
|
+
keynode run artifacts <run_id> # List artifacts
|
|
113
|
+
keynode run download-artifacts <run_id> -o out.tar.gz # Download artifacts tarball
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Backups
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
keynode backup list # List all backups
|
|
120
|
+
keynode backup list --run-id 365 # Filter by run
|
|
121
|
+
keynode backup list --status completed # Filter by status
|
|
122
|
+
keynode backup delete <backup_id> # Delete a backup
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Datasets
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
keynode dataset list # List datasets
|
|
129
|
+
keynode dataset upload ./train.csv --name my-train # Upload a dataset
|
|
130
|
+
keynode dataset download <dataset_id> -o data.csv # Download a dataset
|
|
131
|
+
keynode dataset delete <dataset_id> # Delete a dataset
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Sources
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
keynode source list # List sources
|
|
138
|
+
keynode source upload ./my_code.tar.gz # Upload a source archive
|
|
139
|
+
keynode source delete <source_id> # Delete a source
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Secrets
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
keynode secret list # List secrets
|
|
146
|
+
keynode secret create --name HF_TOKEN # Create a secret (prompts for value)
|
|
147
|
+
keynode secret delete <secret_id> # Delete a secret
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### API Keys
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
keynode apikey list # List your API keys
|
|
154
|
+
keynode apikey create --name "ci-pipeline" # Create a new API key
|
|
155
|
+
keynode apikey create --name "tmp" --expires-in-days 30 # Key with expiry
|
|
156
|
+
keynode apikey revoke <api_key_id> # Revoke an API key
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Python SDK Usage
|
|
162
|
+
|
|
163
|
+
### Initialize the client
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from keynode import KeyNodeClient
|
|
167
|
+
|
|
168
|
+
# From environment variables or ~/.keynode/config.json
|
|
169
|
+
client = KeyNodeClient.from_env()
|
|
170
|
+
|
|
171
|
+
# Or explicitly
|
|
172
|
+
client = KeyNodeClient(api_key="kn_xxx", base_url="https://api.keynode.es")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Browse the GPU catalog
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
with KeyNodeClient.from_env() as client:
|
|
179
|
+
# List available GPUs
|
|
180
|
+
gpus = client.gpu.list(min_vram=24, max_price=2.0)
|
|
181
|
+
for g in gpus:
|
|
182
|
+
print(f"{g['model']} {g['vram_gb']}GB — €{g['min_price_per_hour']:.3f}/hr ({g['units_available']} available)")
|
|
183
|
+
|
|
184
|
+
# Platform stats
|
|
185
|
+
stats = client.gpu.stats()
|
|
186
|
+
print(f"{stats['gpus_available']} GPUs available across {stats['regions_active']} regions")
|
|
187
|
+
|
|
188
|
+
# Detailed SKU info
|
|
189
|
+
detail = client.gpu.detail("NVIDIA A100", vram_gb=80)
|
|
190
|
+
print(detail["region_breakdown"])
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Create a training run and wait for completion
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from keynode import KeyNodeClient
|
|
197
|
+
|
|
198
|
+
payload = {
|
|
199
|
+
"name": "my-training-job",
|
|
200
|
+
"image": "registry.keynode.es/keynode-jupyter-scipy-ssh:v1",
|
|
201
|
+
"cmd": "python train.py",
|
|
202
|
+
"cpus": 4,
|
|
203
|
+
"memory_gb": 16,
|
|
204
|
+
"gpu_count": 1,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
with KeyNodeClient.from_env() as client:
|
|
208
|
+
run = client.runs.create(payload)
|
|
209
|
+
run_id = run["run_id"]
|
|
210
|
+
|
|
211
|
+
# Block until finished (polls every 5s)
|
|
212
|
+
result = client.runs.wait(run_id, timeout=7200)
|
|
213
|
+
print(f"Status: {result['status']}")
|
|
214
|
+
|
|
215
|
+
# Download results
|
|
216
|
+
client.runs.download_artifacts(run_id, output_path="./results.tar.gz")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### List runs
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
with KeyNodeClient.from_env() as client:
|
|
223
|
+
runs = client.runs.list()
|
|
224
|
+
print(runs)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Get GPU/CPU metrics for a run
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
with KeyNodeClient.from_env() as client:
|
|
231
|
+
metrics = client.runs.metrics(run_id=365, step_s=10)
|
|
232
|
+
for point in metrics["items"]:
|
|
233
|
+
print(f"{point['ts']} GPU {point['gpu_util']}% VRAM {point['gpu_mem_mb']}MB")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Work with backups
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
with KeyNodeClient.from_env() as client:
|
|
240
|
+
# List all backups
|
|
241
|
+
backups = client.backups.list()
|
|
242
|
+
|
|
243
|
+
# Filter by run
|
|
244
|
+
run_backups = client.backups.list(run_id=365, status="completed")
|
|
245
|
+
|
|
246
|
+
# Delete a backup
|
|
247
|
+
client.backups.delete(backups[0]["id"])
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Work with datasets
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
with KeyNodeClient.from_env() as client:
|
|
254
|
+
# Upload
|
|
255
|
+
dataset = client.datasets.upload("./data/train.csv", dataset_name="train-v1")
|
|
256
|
+
|
|
257
|
+
# Download
|
|
258
|
+
client.datasets.download(dataset["id"], output_path="./train_downloaded.csv")
|
|
259
|
+
|
|
260
|
+
# Delete
|
|
261
|
+
client.datasets.delete(dataset["id"])
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Manage API keys
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
with KeyNodeClient.from_env() as client:
|
|
268
|
+
# Create a new key (raw key shown only once)
|
|
269
|
+
result = client.api_keys.create(name="ci-pipeline", expires_in_days=90)
|
|
270
|
+
print(result["api_key"]) # save this immediately
|
|
271
|
+
|
|
272
|
+
# List existing keys
|
|
273
|
+
keys = client.api_keys.list()
|
|
274
|
+
|
|
275
|
+
# Revoke a key
|
|
276
|
+
client.api_keys.revoke(keys[0]["api_key_id"])
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Manage secrets
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
with KeyNodeClient.from_env() as client:
|
|
283
|
+
client.secrets.create(name="HF_TOKEN", value="hf_xxx", description="HuggingFace token")
|
|
284
|
+
secrets = client.secrets.list()
|
|
285
|
+
client.secrets.delete(secrets[0]["id"])
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Download run artifacts
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
with KeyNodeClient.from_env() as client:
|
|
292
|
+
# List artifacts
|
|
293
|
+
artifacts = client.runs.artifacts(run_id=365)
|
|
294
|
+
|
|
295
|
+
# Download as tarball
|
|
296
|
+
client.runs.download_artifacts(run_id=365, output_path="./results.tar.gz")
|
|
297
|
+
|
|
298
|
+
# Download manifest
|
|
299
|
+
client.runs.download_manifest(run_id=365, output_path="./manifest.json")
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Error handling
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from keynode import KeyNodeClient
|
|
306
|
+
from keynode.exceptions import KeynodeAuthError, KeynodeApiError
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
with KeyNodeClient.from_env() as client:
|
|
310
|
+
run = client.runs.get(999)
|
|
311
|
+
except KeynodeAuthError:
|
|
312
|
+
print("Invalid or missing API key")
|
|
313
|
+
except KeynodeApiError as e:
|
|
314
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Package Structure
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
keynode → Python SDK (client, resources, config, exceptions)
|
|
323
|
+
keynode_cli → CLI application
|
|
324
|
+
```
|
keynode-0.1.0/README.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# KeyNode Python SDK & CLI
|
|
2
|
+
|
|
3
|
+
Official Python SDK and CLI for [KeyNode](https://keynode.es) — the AI compute platform that connects your workloads to underutilized GPUs from distributed providers. Run training jobs and inference cheaper and faster than traditional cloud.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Python 3.10+
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install keynode
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or install from source:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone <repo_url>
|
|
19
|
+
cd keynode-python
|
|
20
|
+
pip install .
|
|
21
|
+
|
|
22
|
+
# Development (editable) install
|
|
23
|
+
pip install -e .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Authentication
|
|
27
|
+
|
|
28
|
+
### Option 1 — CLI login (recommended)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
keynode auth login
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This prompts for your API key and saves it to `~/.keynode/config.json`.
|
|
35
|
+
|
|
36
|
+
### Option 2 — Environment variables
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export KEYNODE_API_KEY="kn_xxx"
|
|
40
|
+
export KEYNODE_BASE_URL="https://api.keynode.es" # optional, this is the default
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Option 3 — Direct SDK initialization
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from keynode import KeyNodeClient
|
|
47
|
+
|
|
48
|
+
client = KeyNodeClient(api_key="kn_xxx", base_url="https://api.keynode.es")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## CLI Usage
|
|
54
|
+
|
|
55
|
+
### Auth
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
keynode auth login # Save API key to config
|
|
59
|
+
keynode auth whoami # Verify authentication
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### GPU Catalog
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
keynode gpu list # List all available GPUs
|
|
66
|
+
keynode gpu list --min-vram 24 # Filter by minimum VRAM
|
|
67
|
+
keynode gpu list --max-price 1.5 --region eu-west # Filter by price and region
|
|
68
|
+
keynode gpu list --model "RTX 4090" # Search by model name
|
|
69
|
+
keynode gpu stats # Platform-wide GPU stats
|
|
70
|
+
keynode gpu detail "NVIDIA A100" --vram 80 # Detailed info for a GPU SKU
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Runs
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
keynode run list # List all runs
|
|
77
|
+
keynode run get <run_id> # Get run details
|
|
78
|
+
keynode run create -f payload.json # Create run from JSON file
|
|
79
|
+
keynode run cancel <run_id> # Cancel a run
|
|
80
|
+
keynode run retry <run_id> # Retry a run immediately
|
|
81
|
+
keynode run clone-retry <run_id> # Clone and retry a run
|
|
82
|
+
keynode run resume <run_id> # Resume a paused run
|
|
83
|
+
keynode run wait <run_id> # Block until run finishes
|
|
84
|
+
keynode run wait <run_id> --timeout 3600 # Wait with timeout (seconds)
|
|
85
|
+
keynode run metrics <run_id> # GPU/CPU metrics time-series
|
|
86
|
+
keynode run manifest <run_id> # Show run manifest
|
|
87
|
+
keynode run download-manifest <run_id> -o out.json # Download manifest as JSON
|
|
88
|
+
keynode run artifacts <run_id> # List artifacts
|
|
89
|
+
keynode run download-artifacts <run_id> -o out.tar.gz # Download artifacts tarball
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Backups
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
keynode backup list # List all backups
|
|
96
|
+
keynode backup list --run-id 365 # Filter by run
|
|
97
|
+
keynode backup list --status completed # Filter by status
|
|
98
|
+
keynode backup delete <backup_id> # Delete a backup
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Datasets
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
keynode dataset list # List datasets
|
|
105
|
+
keynode dataset upload ./train.csv --name my-train # Upload a dataset
|
|
106
|
+
keynode dataset download <dataset_id> -o data.csv # Download a dataset
|
|
107
|
+
keynode dataset delete <dataset_id> # Delete a dataset
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Sources
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
keynode source list # List sources
|
|
114
|
+
keynode source upload ./my_code.tar.gz # Upload a source archive
|
|
115
|
+
keynode source delete <source_id> # Delete a source
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Secrets
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
keynode secret list # List secrets
|
|
122
|
+
keynode secret create --name HF_TOKEN # Create a secret (prompts for value)
|
|
123
|
+
keynode secret delete <secret_id> # Delete a secret
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### API Keys
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
keynode apikey list # List your API keys
|
|
130
|
+
keynode apikey create --name "ci-pipeline" # Create a new API key
|
|
131
|
+
keynode apikey create --name "tmp" --expires-in-days 30 # Key with expiry
|
|
132
|
+
keynode apikey revoke <api_key_id> # Revoke an API key
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Python SDK Usage
|
|
138
|
+
|
|
139
|
+
### Initialize the client
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from keynode import KeyNodeClient
|
|
143
|
+
|
|
144
|
+
# From environment variables or ~/.keynode/config.json
|
|
145
|
+
client = KeyNodeClient.from_env()
|
|
146
|
+
|
|
147
|
+
# Or explicitly
|
|
148
|
+
client = KeyNodeClient(api_key="kn_xxx", base_url="https://api.keynode.es")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Browse the GPU catalog
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
with KeyNodeClient.from_env() as client:
|
|
155
|
+
# List available GPUs
|
|
156
|
+
gpus = client.gpu.list(min_vram=24, max_price=2.0)
|
|
157
|
+
for g in gpus:
|
|
158
|
+
print(f"{g['model']} {g['vram_gb']}GB — €{g['min_price_per_hour']:.3f}/hr ({g['units_available']} available)")
|
|
159
|
+
|
|
160
|
+
# Platform stats
|
|
161
|
+
stats = client.gpu.stats()
|
|
162
|
+
print(f"{stats['gpus_available']} GPUs available across {stats['regions_active']} regions")
|
|
163
|
+
|
|
164
|
+
# Detailed SKU info
|
|
165
|
+
detail = client.gpu.detail("NVIDIA A100", vram_gb=80)
|
|
166
|
+
print(detail["region_breakdown"])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Create a training run and wait for completion
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from keynode import KeyNodeClient
|
|
173
|
+
|
|
174
|
+
payload = {
|
|
175
|
+
"name": "my-training-job",
|
|
176
|
+
"image": "registry.keynode.es/keynode-jupyter-scipy-ssh:v1",
|
|
177
|
+
"cmd": "python train.py",
|
|
178
|
+
"cpus": 4,
|
|
179
|
+
"memory_gb": 16,
|
|
180
|
+
"gpu_count": 1,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
with KeyNodeClient.from_env() as client:
|
|
184
|
+
run = client.runs.create(payload)
|
|
185
|
+
run_id = run["run_id"]
|
|
186
|
+
|
|
187
|
+
# Block until finished (polls every 5s)
|
|
188
|
+
result = client.runs.wait(run_id, timeout=7200)
|
|
189
|
+
print(f"Status: {result['status']}")
|
|
190
|
+
|
|
191
|
+
# Download results
|
|
192
|
+
client.runs.download_artifacts(run_id, output_path="./results.tar.gz")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### List runs
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
with KeyNodeClient.from_env() as client:
|
|
199
|
+
runs = client.runs.list()
|
|
200
|
+
print(runs)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Get GPU/CPU metrics for a run
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
with KeyNodeClient.from_env() as client:
|
|
207
|
+
metrics = client.runs.metrics(run_id=365, step_s=10)
|
|
208
|
+
for point in metrics["items"]:
|
|
209
|
+
print(f"{point['ts']} GPU {point['gpu_util']}% VRAM {point['gpu_mem_mb']}MB")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Work with backups
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
with KeyNodeClient.from_env() as client:
|
|
216
|
+
# List all backups
|
|
217
|
+
backups = client.backups.list()
|
|
218
|
+
|
|
219
|
+
# Filter by run
|
|
220
|
+
run_backups = client.backups.list(run_id=365, status="completed")
|
|
221
|
+
|
|
222
|
+
# Delete a backup
|
|
223
|
+
client.backups.delete(backups[0]["id"])
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Work with datasets
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
with KeyNodeClient.from_env() as client:
|
|
230
|
+
# Upload
|
|
231
|
+
dataset = client.datasets.upload("./data/train.csv", dataset_name="train-v1")
|
|
232
|
+
|
|
233
|
+
# Download
|
|
234
|
+
client.datasets.download(dataset["id"], output_path="./train_downloaded.csv")
|
|
235
|
+
|
|
236
|
+
# Delete
|
|
237
|
+
client.datasets.delete(dataset["id"])
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Manage API keys
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
with KeyNodeClient.from_env() as client:
|
|
244
|
+
# Create a new key (raw key shown only once)
|
|
245
|
+
result = client.api_keys.create(name="ci-pipeline", expires_in_days=90)
|
|
246
|
+
print(result["api_key"]) # save this immediately
|
|
247
|
+
|
|
248
|
+
# List existing keys
|
|
249
|
+
keys = client.api_keys.list()
|
|
250
|
+
|
|
251
|
+
# Revoke a key
|
|
252
|
+
client.api_keys.revoke(keys[0]["api_key_id"])
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Manage secrets
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
with KeyNodeClient.from_env() as client:
|
|
259
|
+
client.secrets.create(name="HF_TOKEN", value="hf_xxx", description="HuggingFace token")
|
|
260
|
+
secrets = client.secrets.list()
|
|
261
|
+
client.secrets.delete(secrets[0]["id"])
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Download run artifacts
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
with KeyNodeClient.from_env() as client:
|
|
268
|
+
# List artifacts
|
|
269
|
+
artifacts = client.runs.artifacts(run_id=365)
|
|
270
|
+
|
|
271
|
+
# Download as tarball
|
|
272
|
+
client.runs.download_artifacts(run_id=365, output_path="./results.tar.gz")
|
|
273
|
+
|
|
274
|
+
# Download manifest
|
|
275
|
+
client.runs.download_manifest(run_id=365, output_path="./manifest.json")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Error handling
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from keynode import KeyNodeClient
|
|
282
|
+
from keynode.exceptions import KeynodeAuthError, KeynodeApiError
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
with KeyNodeClient.from_env() as client:
|
|
286
|
+
run = client.runs.get(999)
|
|
287
|
+
except KeynodeAuthError:
|
|
288
|
+
print("Invalid or missing API key")
|
|
289
|
+
except KeynodeApiError as e:
|
|
290
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Package Structure
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
keynode → Python SDK (client, resources, config, exceptions)
|
|
299
|
+
keynode_cli → CLI application
|
|
300
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "keynode"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK and CLI for KeyNode — AI compute on distributed GPUs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["keynode", "gpu", "ai", "ml", "training", "cloud", "sdk", "cli"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.27.0",
|
|
26
|
+
"typer>=0.12.3",
|
|
27
|
+
"rich>=13.7.1",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://keynode.es"
|
|
32
|
+
Documentation = "https://docs.keynode.es"
|
|
33
|
+
Repository = "https://github.com/keynode-ai/keynode-python"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
keynode = "keynode_cli.main:app"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
keynode-0.1.0/setup.cfg
ADDED