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 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
+ ```
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .client import KeyNodeClient
2
+
3
+ __all__ = ["KeyNodeClient"]