clouditia 1.2.4__tar.gz → 1.3.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.
- {clouditia-1.2.4/clouditia.egg-info → clouditia-1.3.0}/PKG-INFO +190 -6
- {clouditia-1.2.4 → clouditia-1.3.0}/README.md +189 -5
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/__init__.py +1 -1
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/client.py +486 -1
- {clouditia-1.2.4 → clouditia-1.3.0/clouditia.egg-info}/PKG-INFO +190 -6
- {clouditia-1.2.4 → clouditia-1.3.0}/pyproject.toml +1 -1
- {clouditia-1.2.4 → clouditia-1.3.0}/CHANGELOG.md +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/LICENSE +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/MANIFEST.in +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/exceptions.py +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/jobs.py +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/magic.py +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/py.typed +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia/results.py +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia.egg-info/SOURCES.txt +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia.egg-info/dependency_links.txt +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia.egg-info/requires.txt +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/clouditia.egg-info/top_level.txt +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/setup.cfg +0 -0
- {clouditia-1.2.4 → clouditia-1.3.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clouditia
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Execute Python and Shell code on remote GPU sessions
|
|
5
5
|
Author-email: Aina KIKI-SAGBE <support@clouditia.com>
|
|
6
6
|
Maintainer-email: Aina KIKI-SAGBE <support@clouditia.com>
|
|
@@ -81,6 +81,7 @@ print(result.output)
|
|
|
81
81
|
- **Shell Commands**: Execute shell commands on the GPU pod
|
|
82
82
|
- **Persistent Sessions**: Keep variables between executions with `start()`/`stop()`
|
|
83
83
|
- **Variable Transfer**: Send and retrieve variables between local and remote
|
|
84
|
+
- **File Transfer**: Upload/download files and folders between local and remote
|
|
84
85
|
- **Async Jobs**: Submit long-running tasks with real-time log monitoring
|
|
85
86
|
- **Jupyter Magic**: Use `%%clouditia` magic in notebooks
|
|
86
87
|
- **Decorator Support**: Use `@session.remote` to run functions on GPU
|
|
@@ -95,11 +96,12 @@ print(result.output)
|
|
|
95
96
|
4. [Executing Python Code](#executing-python-code)
|
|
96
97
|
5. [Shell Commands](#shell-commands)
|
|
97
98
|
6. [Variable Transfer](#variable-transfer)
|
|
98
|
-
7. [
|
|
99
|
-
8. [
|
|
100
|
-
9. [
|
|
101
|
-
10. [
|
|
102
|
-
11. [
|
|
99
|
+
7. [File Transfer](#file-transfer)
|
|
100
|
+
8. [Remote Functions (Decorator)](#remote-functions-decorator)
|
|
101
|
+
9. [Async Jobs (Long-Running Tasks)](#async-jobs-long-running-tasks)
|
|
102
|
+
10. [Jupyter Magic](#jupyter-magic)
|
|
103
|
+
11. [Error Handling](#error-handling)
|
|
104
|
+
12. [API Reference](#api-reference)
|
|
103
105
|
|
|
104
106
|
---
|
|
105
107
|
|
|
@@ -377,6 +379,182 @@ print(f"Learning rate: {config['learning_rate']}")
|
|
|
377
379
|
|
|
378
380
|
---
|
|
379
381
|
|
|
382
|
+
## File Transfer
|
|
383
|
+
|
|
384
|
+
Transfer files and folders between your local machine and the remote GPU session.
|
|
385
|
+
|
|
386
|
+
### Uploading a Single File
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
# Upload a local file to the remote session
|
|
390
|
+
session.upload("./data.csv", "/home/coder/workspace/data.csv")
|
|
391
|
+
|
|
392
|
+
# Upload with custom path
|
|
393
|
+
session.upload("./model.pkl", "/home/coder/workspace/models/trained_model.pkl")
|
|
394
|
+
|
|
395
|
+
# Disable progress output
|
|
396
|
+
session.upload("./config.json", "/home/coder/workspace/config.json", show_progress=False)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Downloading a Single File
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# Download a file from the remote session
|
|
403
|
+
session.download("/home/coder/workspace/results.csv", "./results.csv")
|
|
404
|
+
|
|
405
|
+
# Download trained model
|
|
406
|
+
session.download("/home/coder/workspace/checkpoints/model.pt", "./local_model.pt")
|
|
407
|
+
|
|
408
|
+
# Download silently
|
|
409
|
+
session.download("/home/coder/workspace/logs.txt", "./logs.txt", show_progress=False)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Uploading a Folder
|
|
413
|
+
|
|
414
|
+
Upload an entire directory with all its contents:
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
# Upload a project folder
|
|
418
|
+
session.upload_folder("./my_project", "/home/coder/workspace/project")
|
|
419
|
+
|
|
420
|
+
# Upload with exclusions (default excludes: __pycache__, .git, *.pyc, .DS_Store, node_modules)
|
|
421
|
+
session.upload_folder(
|
|
422
|
+
"./my_project",
|
|
423
|
+
"/home/coder/workspace/project",
|
|
424
|
+
exclude=["*.log", ".env", "__pycache__", ".git"]
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Upload data folder
|
|
428
|
+
session.upload_folder("./datasets", "/home/coder/workspace/data")
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Downloading a Folder
|
|
432
|
+
|
|
433
|
+
Download an entire directory with all its contents:
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
# Download results folder
|
|
437
|
+
session.download_folder("/home/coder/workspace/results", "./local_results")
|
|
438
|
+
|
|
439
|
+
# Download checkpoints
|
|
440
|
+
session.download_folder(
|
|
441
|
+
"/home/coder/workspace/checkpoints",
|
|
442
|
+
"./checkpoints",
|
|
443
|
+
exclude=["*.tmp", "*.log"]
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Download trained models
|
|
447
|
+
session.download_folder("/home/coder/workspace/models", "./downloaded_models")
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Listing Remote Files
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
# List files in a directory
|
|
454
|
+
files = session.list_files("/home/coder/workspace")
|
|
455
|
+
for f in files:
|
|
456
|
+
icon = "📁" if f["is_dir"] else "📄"
|
|
457
|
+
print(f"{icon} {f['name']} - {f['size']} bytes")
|
|
458
|
+
|
|
459
|
+
# Filter by pattern
|
|
460
|
+
python_files = session.list_files("/home/coder/workspace", pattern="*.py")
|
|
461
|
+
for f in python_files:
|
|
462
|
+
print(f"📄 {f['name']}")
|
|
463
|
+
|
|
464
|
+
# List with full details
|
|
465
|
+
files = session.list_files("/home/coder/workspace")
|
|
466
|
+
for f in files:
|
|
467
|
+
print(f"Name: {f['name']}")
|
|
468
|
+
print(f" Path: {f['path']}")
|
|
469
|
+
print(f" Size: {f['size']} bytes")
|
|
470
|
+
print(f" Is Directory: {f['is_dir']}")
|
|
471
|
+
print(f" Modified: {f['modified']}")
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Checking if a File Exists
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
# Check before downloading
|
|
478
|
+
if session.file_exists("/home/coder/workspace/model.pt"):
|
|
479
|
+
session.download("/home/coder/workspace/model.pt", "./model.pt")
|
|
480
|
+
print("Model downloaded!")
|
|
481
|
+
else:
|
|
482
|
+
print("Model not found, training required...")
|
|
483
|
+
|
|
484
|
+
# Check multiple files
|
|
485
|
+
files_to_check = ["config.json", "data.csv", "model.pt"]
|
|
486
|
+
for filename in files_to_check:
|
|
487
|
+
path = f"/home/coder/workspace/{filename}"
|
|
488
|
+
exists = session.file_exists(path)
|
|
489
|
+
status = "✓" if exists else "✗"
|
|
490
|
+
print(f"{status} {filename}")
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Complete Workflow Example
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from clouditia import GPUSession
|
|
497
|
+
|
|
498
|
+
session = GPUSession("ck_your_api_key")
|
|
499
|
+
|
|
500
|
+
# 1. Upload training data and code
|
|
501
|
+
session.upload_folder("./training_code", "/home/coder/workspace/code")
|
|
502
|
+
session.upload("./data/train.csv", "/home/coder/workspace/data/train.csv")
|
|
503
|
+
session.upload("./data/test.csv", "/home/coder/workspace/data/test.csv")
|
|
504
|
+
|
|
505
|
+
# 2. Run training
|
|
506
|
+
result = session.run("""
|
|
507
|
+
import sys
|
|
508
|
+
sys.path.insert(0, '/home/coder/workspace/code')
|
|
509
|
+
from train import train_model
|
|
510
|
+
|
|
511
|
+
model = train_model('/home/coder/workspace/data/train.csv')
|
|
512
|
+
model.save('/home/coder/workspace/output/model.pt')
|
|
513
|
+
print("Training complete!")
|
|
514
|
+
""")
|
|
515
|
+
|
|
516
|
+
# 3. Check and download results
|
|
517
|
+
if session.file_exists("/home/coder/workspace/output/model.pt"):
|
|
518
|
+
session.download("/home/coder/workspace/output/model.pt", "./trained_model.pt")
|
|
519
|
+
print("Model saved locally!")
|
|
520
|
+
|
|
521
|
+
# 4. Download all outputs
|
|
522
|
+
session.download_folder("/home/coder/workspace/output", "./results")
|
|
523
|
+
print("All results downloaded!")
|
|
524
|
+
|
|
525
|
+
# 5. List what was created
|
|
526
|
+
files = session.list_files("/home/coder/workspace/output")
|
|
527
|
+
print(f"Created {len(files)} files during training")
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Working with Different File Types
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
# CSV files
|
|
534
|
+
session.upload("./data.csv", "/home/coder/workspace/data.csv")
|
|
535
|
+
|
|
536
|
+
# Pickle files (models, data)
|
|
537
|
+
session.upload("./model.pkl", "/home/coder/workspace/model.pkl")
|
|
538
|
+
|
|
539
|
+
# PyTorch models
|
|
540
|
+
session.download("/home/coder/workspace/checkpoint.pt", "./checkpoint.pt")
|
|
541
|
+
|
|
542
|
+
# JSON configuration
|
|
543
|
+
session.upload("./config.json", "/home/coder/workspace/config.json")
|
|
544
|
+
|
|
545
|
+
# Text files
|
|
546
|
+
session.upload("./requirements.txt", "/home/coder/workspace/requirements.txt")
|
|
547
|
+
|
|
548
|
+
# Binary files
|
|
549
|
+
session.upload("./image.png", "/home/coder/workspace/image.png")
|
|
550
|
+
|
|
551
|
+
# Any file type works!
|
|
552
|
+
session.upload("./data.parquet", "/home/coder/workspace/data.parquet")
|
|
553
|
+
session.upload("./weights.h5", "/home/coder/workspace/weights.h5")
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
380
558
|
## Remote Functions (Decorator)
|
|
381
559
|
|
|
382
560
|
Use the `@session.remote` decorator to run functions on the GPU:
|
|
@@ -707,6 +885,12 @@ GPUSession(
|
|
|
707
885
|
| `stop()` | Stop the persistent session |
|
|
708
886
|
| `set(name, value)` | Send variable to remote |
|
|
709
887
|
| `get(name)` | Retrieve variable from remote |
|
|
888
|
+
| `upload(local_path, remote_path, show_progress=True)` | Upload a file to remote session |
|
|
889
|
+
| `download(remote_path, local_path, show_progress=True)` | Download a file from remote session |
|
|
890
|
+
| `upload_folder(local_path, remote_path, exclude=None)` | Upload a folder to remote session |
|
|
891
|
+
| `download_folder(remote_path, local_path, exclude=None)` | Download a folder from remote session |
|
|
892
|
+
| `list_files(remote_path, pattern=None)` | List files in remote directory |
|
|
893
|
+
| `file_exists(remote_path)` | Check if a file exists on remote |
|
|
710
894
|
| `submit(code, name=None, job_type="python")` | Submit async job |
|
|
711
895
|
| `jobs(status=None, limit=10)` | List jobs |
|
|
712
896
|
| `gpu_info()` | Get GPU information |
|
|
@@ -38,6 +38,7 @@ print(result.output)
|
|
|
38
38
|
- **Shell Commands**: Execute shell commands on the GPU pod
|
|
39
39
|
- **Persistent Sessions**: Keep variables between executions with `start()`/`stop()`
|
|
40
40
|
- **Variable Transfer**: Send and retrieve variables between local and remote
|
|
41
|
+
- **File Transfer**: Upload/download files and folders between local and remote
|
|
41
42
|
- **Async Jobs**: Submit long-running tasks with real-time log monitoring
|
|
42
43
|
- **Jupyter Magic**: Use `%%clouditia` magic in notebooks
|
|
43
44
|
- **Decorator Support**: Use `@session.remote` to run functions on GPU
|
|
@@ -52,11 +53,12 @@ print(result.output)
|
|
|
52
53
|
4. [Executing Python Code](#executing-python-code)
|
|
53
54
|
5. [Shell Commands](#shell-commands)
|
|
54
55
|
6. [Variable Transfer](#variable-transfer)
|
|
55
|
-
7. [
|
|
56
|
-
8. [
|
|
57
|
-
9. [
|
|
58
|
-
10. [
|
|
59
|
-
11. [
|
|
56
|
+
7. [File Transfer](#file-transfer)
|
|
57
|
+
8. [Remote Functions (Decorator)](#remote-functions-decorator)
|
|
58
|
+
9. [Async Jobs (Long-Running Tasks)](#async-jobs-long-running-tasks)
|
|
59
|
+
10. [Jupyter Magic](#jupyter-magic)
|
|
60
|
+
11. [Error Handling](#error-handling)
|
|
61
|
+
12. [API Reference](#api-reference)
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
|
@@ -334,6 +336,182 @@ print(f"Learning rate: {config['learning_rate']}")
|
|
|
334
336
|
|
|
335
337
|
---
|
|
336
338
|
|
|
339
|
+
## File Transfer
|
|
340
|
+
|
|
341
|
+
Transfer files and folders between your local machine and the remote GPU session.
|
|
342
|
+
|
|
343
|
+
### Uploading a Single File
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
# Upload a local file to the remote session
|
|
347
|
+
session.upload("./data.csv", "/home/coder/workspace/data.csv")
|
|
348
|
+
|
|
349
|
+
# Upload with custom path
|
|
350
|
+
session.upload("./model.pkl", "/home/coder/workspace/models/trained_model.pkl")
|
|
351
|
+
|
|
352
|
+
# Disable progress output
|
|
353
|
+
session.upload("./config.json", "/home/coder/workspace/config.json", show_progress=False)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Downloading a Single File
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
# Download a file from the remote session
|
|
360
|
+
session.download("/home/coder/workspace/results.csv", "./results.csv")
|
|
361
|
+
|
|
362
|
+
# Download trained model
|
|
363
|
+
session.download("/home/coder/workspace/checkpoints/model.pt", "./local_model.pt")
|
|
364
|
+
|
|
365
|
+
# Download silently
|
|
366
|
+
session.download("/home/coder/workspace/logs.txt", "./logs.txt", show_progress=False)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Uploading a Folder
|
|
370
|
+
|
|
371
|
+
Upload an entire directory with all its contents:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
# Upload a project folder
|
|
375
|
+
session.upload_folder("./my_project", "/home/coder/workspace/project")
|
|
376
|
+
|
|
377
|
+
# Upload with exclusions (default excludes: __pycache__, .git, *.pyc, .DS_Store, node_modules)
|
|
378
|
+
session.upload_folder(
|
|
379
|
+
"./my_project",
|
|
380
|
+
"/home/coder/workspace/project",
|
|
381
|
+
exclude=["*.log", ".env", "__pycache__", ".git"]
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Upload data folder
|
|
385
|
+
session.upload_folder("./datasets", "/home/coder/workspace/data")
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Downloading a Folder
|
|
389
|
+
|
|
390
|
+
Download an entire directory with all its contents:
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
# Download results folder
|
|
394
|
+
session.download_folder("/home/coder/workspace/results", "./local_results")
|
|
395
|
+
|
|
396
|
+
# Download checkpoints
|
|
397
|
+
session.download_folder(
|
|
398
|
+
"/home/coder/workspace/checkpoints",
|
|
399
|
+
"./checkpoints",
|
|
400
|
+
exclude=["*.tmp", "*.log"]
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# Download trained models
|
|
404
|
+
session.download_folder("/home/coder/workspace/models", "./downloaded_models")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Listing Remote Files
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
# List files in a directory
|
|
411
|
+
files = session.list_files("/home/coder/workspace")
|
|
412
|
+
for f in files:
|
|
413
|
+
icon = "📁" if f["is_dir"] else "📄"
|
|
414
|
+
print(f"{icon} {f['name']} - {f['size']} bytes")
|
|
415
|
+
|
|
416
|
+
# Filter by pattern
|
|
417
|
+
python_files = session.list_files("/home/coder/workspace", pattern="*.py")
|
|
418
|
+
for f in python_files:
|
|
419
|
+
print(f"📄 {f['name']}")
|
|
420
|
+
|
|
421
|
+
# List with full details
|
|
422
|
+
files = session.list_files("/home/coder/workspace")
|
|
423
|
+
for f in files:
|
|
424
|
+
print(f"Name: {f['name']}")
|
|
425
|
+
print(f" Path: {f['path']}")
|
|
426
|
+
print(f" Size: {f['size']} bytes")
|
|
427
|
+
print(f" Is Directory: {f['is_dir']}")
|
|
428
|
+
print(f" Modified: {f['modified']}")
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Checking if a File Exists
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
# Check before downloading
|
|
435
|
+
if session.file_exists("/home/coder/workspace/model.pt"):
|
|
436
|
+
session.download("/home/coder/workspace/model.pt", "./model.pt")
|
|
437
|
+
print("Model downloaded!")
|
|
438
|
+
else:
|
|
439
|
+
print("Model not found, training required...")
|
|
440
|
+
|
|
441
|
+
# Check multiple files
|
|
442
|
+
files_to_check = ["config.json", "data.csv", "model.pt"]
|
|
443
|
+
for filename in files_to_check:
|
|
444
|
+
path = f"/home/coder/workspace/{filename}"
|
|
445
|
+
exists = session.file_exists(path)
|
|
446
|
+
status = "✓" if exists else "✗"
|
|
447
|
+
print(f"{status} {filename}")
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Complete Workflow Example
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
from clouditia import GPUSession
|
|
454
|
+
|
|
455
|
+
session = GPUSession("ck_your_api_key")
|
|
456
|
+
|
|
457
|
+
# 1. Upload training data and code
|
|
458
|
+
session.upload_folder("./training_code", "/home/coder/workspace/code")
|
|
459
|
+
session.upload("./data/train.csv", "/home/coder/workspace/data/train.csv")
|
|
460
|
+
session.upload("./data/test.csv", "/home/coder/workspace/data/test.csv")
|
|
461
|
+
|
|
462
|
+
# 2. Run training
|
|
463
|
+
result = session.run("""
|
|
464
|
+
import sys
|
|
465
|
+
sys.path.insert(0, '/home/coder/workspace/code')
|
|
466
|
+
from train import train_model
|
|
467
|
+
|
|
468
|
+
model = train_model('/home/coder/workspace/data/train.csv')
|
|
469
|
+
model.save('/home/coder/workspace/output/model.pt')
|
|
470
|
+
print("Training complete!")
|
|
471
|
+
""")
|
|
472
|
+
|
|
473
|
+
# 3. Check and download results
|
|
474
|
+
if session.file_exists("/home/coder/workspace/output/model.pt"):
|
|
475
|
+
session.download("/home/coder/workspace/output/model.pt", "./trained_model.pt")
|
|
476
|
+
print("Model saved locally!")
|
|
477
|
+
|
|
478
|
+
# 4. Download all outputs
|
|
479
|
+
session.download_folder("/home/coder/workspace/output", "./results")
|
|
480
|
+
print("All results downloaded!")
|
|
481
|
+
|
|
482
|
+
# 5. List what was created
|
|
483
|
+
files = session.list_files("/home/coder/workspace/output")
|
|
484
|
+
print(f"Created {len(files)} files during training")
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Working with Different File Types
|
|
488
|
+
|
|
489
|
+
```python
|
|
490
|
+
# CSV files
|
|
491
|
+
session.upload("./data.csv", "/home/coder/workspace/data.csv")
|
|
492
|
+
|
|
493
|
+
# Pickle files (models, data)
|
|
494
|
+
session.upload("./model.pkl", "/home/coder/workspace/model.pkl")
|
|
495
|
+
|
|
496
|
+
# PyTorch models
|
|
497
|
+
session.download("/home/coder/workspace/checkpoint.pt", "./checkpoint.pt")
|
|
498
|
+
|
|
499
|
+
# JSON configuration
|
|
500
|
+
session.upload("./config.json", "/home/coder/workspace/config.json")
|
|
501
|
+
|
|
502
|
+
# Text files
|
|
503
|
+
session.upload("./requirements.txt", "/home/coder/workspace/requirements.txt")
|
|
504
|
+
|
|
505
|
+
# Binary files
|
|
506
|
+
session.upload("./image.png", "/home/coder/workspace/image.png")
|
|
507
|
+
|
|
508
|
+
# Any file type works!
|
|
509
|
+
session.upload("./data.parquet", "/home/coder/workspace/data.parquet")
|
|
510
|
+
session.upload("./weights.h5", "/home/coder/workspace/weights.h5")
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
337
515
|
## Remote Functions (Decorator)
|
|
338
516
|
|
|
339
517
|
Use the `@session.remote` decorator to run functions on the GPU:
|
|
@@ -664,6 +842,12 @@ GPUSession(
|
|
|
664
842
|
| `stop()` | Stop the persistent session |
|
|
665
843
|
| `set(name, value)` | Send variable to remote |
|
|
666
844
|
| `get(name)` | Retrieve variable from remote |
|
|
845
|
+
| `upload(local_path, remote_path, show_progress=True)` | Upload a file to remote session |
|
|
846
|
+
| `download(remote_path, local_path, show_progress=True)` | Download a file from remote session |
|
|
847
|
+
| `upload_folder(local_path, remote_path, exclude=None)` | Upload a folder to remote session |
|
|
848
|
+
| `download_folder(remote_path, local_path, exclude=None)` | Download a folder from remote session |
|
|
849
|
+
| `list_files(remote_path, pattern=None)` | List files in remote directory |
|
|
850
|
+
| `file_exists(remote_path)` | Check if a file exists on remote |
|
|
667
851
|
| `submit(code, name=None, job_type="python")` | Submit async job |
|
|
668
852
|
| `jobs(status=None, limit=10)` | List jobs |
|
|
669
853
|
| `gpu_info()` | Get GPU information |
|
|
@@ -6,11 +6,15 @@ This module provides the GPUSession class for interacting with remote GPU sessio
|
|
|
6
6
|
|
|
7
7
|
import base64
|
|
8
8
|
import inspect
|
|
9
|
+
import io
|
|
9
10
|
import json
|
|
11
|
+
import os
|
|
10
12
|
import pickle
|
|
13
|
+
import tarfile
|
|
11
14
|
import textwrap
|
|
12
15
|
from functools import wraps
|
|
13
|
-
from
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
14
18
|
|
|
15
19
|
import requests
|
|
16
20
|
|
|
@@ -548,6 +552,487 @@ _tmp_value
|
|
|
548
552
|
except Exception as e:
|
|
549
553
|
raise ClouditiaError(f"Cannot deserialize value: {e}")
|
|
550
554
|
|
|
555
|
+
# ==================== FILE TRANSFER METHODS ====================
|
|
556
|
+
|
|
557
|
+
def upload(
|
|
558
|
+
self,
|
|
559
|
+
local_path: Union[str, Path],
|
|
560
|
+
remote_path: str,
|
|
561
|
+
show_progress: bool = True
|
|
562
|
+
) -> bool:
|
|
563
|
+
"""
|
|
564
|
+
Upload a file from local machine to the remote GPU session.
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
local_path: Path to the local file
|
|
568
|
+
remote_path: Destination path on the remote pod
|
|
569
|
+
show_progress: Show upload progress (default: True)
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
True if upload succeeded.
|
|
573
|
+
|
|
574
|
+
Raises:
|
|
575
|
+
FileNotFoundError: If local file doesn't exist
|
|
576
|
+
ClouditiaError: If upload fails
|
|
577
|
+
|
|
578
|
+
Example:
|
|
579
|
+
>>> session.upload("./data.csv", "/workspace/data.csv")
|
|
580
|
+
>>> session.upload("model.pkl", "/workspace/models/model.pkl")
|
|
581
|
+
"""
|
|
582
|
+
local_path = Path(local_path)
|
|
583
|
+
|
|
584
|
+
if not local_path.exists():
|
|
585
|
+
raise FileNotFoundError(f"Local file not found: {local_path}")
|
|
586
|
+
|
|
587
|
+
if not local_path.is_file():
|
|
588
|
+
raise ClouditiaError(f"Not a file: {local_path}. Use upload_folder() for directories.")
|
|
589
|
+
|
|
590
|
+
# Read and encode file
|
|
591
|
+
file_size = local_path.stat().st_size
|
|
592
|
+
if show_progress:
|
|
593
|
+
print(f"📤 Uploading {local_path.name} ({self._format_size(file_size)})...")
|
|
594
|
+
|
|
595
|
+
with open(local_path, 'rb') as f:
|
|
596
|
+
file_data = f.read()
|
|
597
|
+
|
|
598
|
+
file_b64 = base64.b64encode(file_data).decode()
|
|
599
|
+
|
|
600
|
+
# Create directory and write file on remote
|
|
601
|
+
remote_dir = os.path.dirname(remote_path)
|
|
602
|
+
code = f'''
|
|
603
|
+
import base64
|
|
604
|
+
import os
|
|
605
|
+
|
|
606
|
+
# Create directory if needed
|
|
607
|
+
os.makedirs("{remote_dir}", exist_ok=True)
|
|
608
|
+
|
|
609
|
+
# Decode and write file
|
|
610
|
+
file_data = base64.b64decode("{file_b64}")
|
|
611
|
+
with open("{remote_path}", "wb") as f:
|
|
612
|
+
f.write(file_data)
|
|
613
|
+
|
|
614
|
+
print(f"File written: {remote_path} ({{len(file_data)}} bytes)")
|
|
615
|
+
'''
|
|
616
|
+
result = self.run(code, stream=False)
|
|
617
|
+
|
|
618
|
+
if not result.success:
|
|
619
|
+
raise ClouditiaError(f"Upload failed: {result.error}")
|
|
620
|
+
|
|
621
|
+
if show_progress:
|
|
622
|
+
print(f"✅ Uploaded to {remote_path}")
|
|
623
|
+
|
|
624
|
+
return True
|
|
625
|
+
|
|
626
|
+
def download(
|
|
627
|
+
self,
|
|
628
|
+
remote_path: str,
|
|
629
|
+
local_path: Union[str, Path],
|
|
630
|
+
show_progress: bool = True
|
|
631
|
+
) -> bool:
|
|
632
|
+
"""
|
|
633
|
+
Download a file from the remote GPU session to local machine.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
remote_path: Path to the file on the remote pod
|
|
637
|
+
local_path: Destination path on local machine
|
|
638
|
+
show_progress: Show download progress (default: True)
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
True if download succeeded.
|
|
642
|
+
|
|
643
|
+
Raises:
|
|
644
|
+
ClouditiaError: If file doesn't exist or download fails
|
|
645
|
+
|
|
646
|
+
Example:
|
|
647
|
+
>>> session.download("/workspace/results.csv", "./results.csv")
|
|
648
|
+
>>> session.download("/workspace/model.pt", "./models/trained_model.pt")
|
|
649
|
+
"""
|
|
650
|
+
local_path = Path(local_path)
|
|
651
|
+
|
|
652
|
+
if show_progress:
|
|
653
|
+
print(f"📥 Downloading {os.path.basename(remote_path)}...")
|
|
654
|
+
|
|
655
|
+
# Read and encode file on remote - use print to output the base64
|
|
656
|
+
code = f'''
|
|
657
|
+
import base64
|
|
658
|
+
import os
|
|
659
|
+
|
|
660
|
+
if not os.path.exists("{remote_path}"):
|
|
661
|
+
raise FileNotFoundError("File not found: {remote_path}")
|
|
662
|
+
|
|
663
|
+
if os.path.isdir("{remote_path}"):
|
|
664
|
+
raise IsADirectoryError("Path is a directory. Use download_folder() instead.")
|
|
665
|
+
|
|
666
|
+
with open("{remote_path}", "rb") as f:
|
|
667
|
+
file_data = f.read()
|
|
668
|
+
|
|
669
|
+
file_b64 = base64.b64encode(file_data).decode()
|
|
670
|
+
print("__FILEDATA_START__")
|
|
671
|
+
print(file_b64)
|
|
672
|
+
print("__FILEDATA_END__")
|
|
673
|
+
'''
|
|
674
|
+
result = self.run(code, stream=False)
|
|
675
|
+
|
|
676
|
+
if not result.success:
|
|
677
|
+
raise ClouditiaError(f"Download failed: {result.error}")
|
|
678
|
+
|
|
679
|
+
# Extract base64 data from output
|
|
680
|
+
output = result.output or ""
|
|
681
|
+
if "__FILEDATA_START__" not in output or "__FILEDATA_END__" not in output:
|
|
682
|
+
raise ClouditiaError("No data received from remote")
|
|
683
|
+
|
|
684
|
+
# Parse the base64 data between markers
|
|
685
|
+
try:
|
|
686
|
+
start = output.index("__FILEDATA_START__") + len("__FILEDATA_START__")
|
|
687
|
+
end = output.index("__FILEDATA_END__")
|
|
688
|
+
file_b64 = output[start:end].strip()
|
|
689
|
+
file_data = base64.b64decode(file_b64)
|
|
690
|
+
except Exception as e:
|
|
691
|
+
raise ClouditiaError(f"Failed to decode file data: {e}")
|
|
692
|
+
|
|
693
|
+
# Create local directory if needed
|
|
694
|
+
local_path.parent.mkdir(parents=True, exist_ok=True)
|
|
695
|
+
|
|
696
|
+
with open(local_path, 'wb') as f:
|
|
697
|
+
f.write(file_data)
|
|
698
|
+
|
|
699
|
+
if show_progress:
|
|
700
|
+
print(f"✅ Downloaded to {local_path} ({self._format_size(len(file_data))})")
|
|
701
|
+
|
|
702
|
+
return True
|
|
703
|
+
|
|
704
|
+
def upload_folder(
|
|
705
|
+
self,
|
|
706
|
+
local_path: Union[str, Path],
|
|
707
|
+
remote_path: str,
|
|
708
|
+
exclude: Optional[List[str]] = None,
|
|
709
|
+
show_progress: bool = True
|
|
710
|
+
) -> bool:
|
|
711
|
+
"""
|
|
712
|
+
Upload an entire folder from local machine to the remote GPU session.
|
|
713
|
+
|
|
714
|
+
The folder is compressed locally, transferred, and extracted on the remote.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
local_path: Path to the local folder
|
|
718
|
+
remote_path: Destination path on the remote pod
|
|
719
|
+
exclude: List of patterns to exclude (e.g., ['__pycache__', '.git', '*.pyc'])
|
|
720
|
+
show_progress: Show upload progress (default: True)
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
True if upload succeeded.
|
|
724
|
+
|
|
725
|
+
Raises:
|
|
726
|
+
FileNotFoundError: If local folder doesn't exist
|
|
727
|
+
ClouditiaError: If upload fails
|
|
728
|
+
|
|
729
|
+
Example:
|
|
730
|
+
>>> session.upload_folder("./my_project", "/workspace/project")
|
|
731
|
+
>>> session.upload_folder("./data", "/workspace/data", exclude=['*.tmp', '.git'])
|
|
732
|
+
"""
|
|
733
|
+
local_path = Path(local_path)
|
|
734
|
+
|
|
735
|
+
if not local_path.exists():
|
|
736
|
+
raise FileNotFoundError(f"Local folder not found: {local_path}")
|
|
737
|
+
|
|
738
|
+
if not local_path.is_dir():
|
|
739
|
+
raise ClouditiaError(f"Not a directory: {local_path}. Use upload() for files.")
|
|
740
|
+
|
|
741
|
+
# Default exclusions
|
|
742
|
+
if exclude is None:
|
|
743
|
+
exclude = ['__pycache__', '.git', '*.pyc', '.DS_Store', 'node_modules']
|
|
744
|
+
|
|
745
|
+
if show_progress:
|
|
746
|
+
print(f"📦 Compressing {local_path.name}...")
|
|
747
|
+
|
|
748
|
+
# Create tar.gz in memory
|
|
749
|
+
tar_buffer = io.BytesIO()
|
|
750
|
+
|
|
751
|
+
def filter_func(tarinfo):
|
|
752
|
+
# Check exclusion patterns
|
|
753
|
+
for pattern in exclude:
|
|
754
|
+
if pattern.startswith('*'):
|
|
755
|
+
if tarinfo.name.endswith(pattern[1:]):
|
|
756
|
+
return None
|
|
757
|
+
elif pattern in tarinfo.name:
|
|
758
|
+
return None
|
|
759
|
+
return tarinfo
|
|
760
|
+
|
|
761
|
+
with tarfile.open(fileobj=tar_buffer, mode='w:gz') as tar:
|
|
762
|
+
tar.add(local_path, arcname=os.path.basename(local_path), filter=filter_func)
|
|
763
|
+
|
|
764
|
+
tar_data = tar_buffer.getvalue()
|
|
765
|
+
tar_b64 = base64.b64encode(tar_data).decode()
|
|
766
|
+
|
|
767
|
+
if show_progress:
|
|
768
|
+
print(f"📤 Uploading {self._format_size(len(tar_data))}...")
|
|
769
|
+
|
|
770
|
+
# Transfer and extract on remote
|
|
771
|
+
code = f'''
|
|
772
|
+
import base64
|
|
773
|
+
import tarfile
|
|
774
|
+
import io
|
|
775
|
+
import os
|
|
776
|
+
|
|
777
|
+
# Decode tar data
|
|
778
|
+
tar_data = base64.b64decode("{tar_b64}")
|
|
779
|
+
|
|
780
|
+
# Create destination directory
|
|
781
|
+
os.makedirs("{remote_path}", exist_ok=True)
|
|
782
|
+
|
|
783
|
+
# Extract
|
|
784
|
+
tar_buffer = io.BytesIO(tar_data)
|
|
785
|
+
with tarfile.open(fileobj=tar_buffer, mode='r:gz') as tar:
|
|
786
|
+
tar.extractall(path="{remote_path}")
|
|
787
|
+
|
|
788
|
+
# Count files
|
|
789
|
+
file_count = sum(1 for root, dirs, files in os.walk("{remote_path}") for f in files)
|
|
790
|
+
print(f"Extracted {{file_count}} files to {remote_path}")
|
|
791
|
+
'''
|
|
792
|
+
result = self.run(code, stream=False)
|
|
793
|
+
|
|
794
|
+
if not result.success:
|
|
795
|
+
raise ClouditiaError(f"Upload folder failed: {result.error}")
|
|
796
|
+
|
|
797
|
+
if show_progress:
|
|
798
|
+
print(f"✅ Folder uploaded to {remote_path}")
|
|
799
|
+
|
|
800
|
+
return True
|
|
801
|
+
|
|
802
|
+
def download_folder(
|
|
803
|
+
self,
|
|
804
|
+
remote_path: str,
|
|
805
|
+
local_path: Union[str, Path],
|
|
806
|
+
exclude: Optional[List[str]] = None,
|
|
807
|
+
show_progress: bool = True
|
|
808
|
+
) -> bool:
|
|
809
|
+
"""
|
|
810
|
+
Download an entire folder from the remote GPU session to local machine.
|
|
811
|
+
|
|
812
|
+
The folder is compressed on the remote, transferred, and extracted locally.
|
|
813
|
+
|
|
814
|
+
Args:
|
|
815
|
+
remote_path: Path to the folder on the remote pod
|
|
816
|
+
local_path: Destination path on local machine
|
|
817
|
+
exclude: List of patterns to exclude (e.g., ['__pycache__', '*.pyc'])
|
|
818
|
+
show_progress: Show download progress (default: True)
|
|
819
|
+
|
|
820
|
+
Returns:
|
|
821
|
+
True if download succeeded.
|
|
822
|
+
|
|
823
|
+
Raises:
|
|
824
|
+
ClouditiaError: If folder doesn't exist or download fails
|
|
825
|
+
|
|
826
|
+
Example:
|
|
827
|
+
>>> session.download_folder("/workspace/results", "./results")
|
|
828
|
+
>>> session.download_folder("/workspace/checkpoints", "./checkpoints", exclude=['*.tmp'])
|
|
829
|
+
"""
|
|
830
|
+
local_path = Path(local_path)
|
|
831
|
+
|
|
832
|
+
# Default exclusions
|
|
833
|
+
if exclude is None:
|
|
834
|
+
exclude = ['__pycache__', '*.pyc', '.DS_Store']
|
|
835
|
+
|
|
836
|
+
if show_progress:
|
|
837
|
+
print(f"📦 Compressing remote folder...")
|
|
838
|
+
|
|
839
|
+
# Build exclude filter for Python
|
|
840
|
+
exclude_patterns = repr(exclude)
|
|
841
|
+
|
|
842
|
+
# Compress on remote and get base64
|
|
843
|
+
code = f'''
|
|
844
|
+
import base64
|
|
845
|
+
import tarfile
|
|
846
|
+
import io
|
|
847
|
+
import os
|
|
848
|
+
import fnmatch
|
|
849
|
+
|
|
850
|
+
if not os.path.exists("{remote_path}"):
|
|
851
|
+
raise FileNotFoundError("Folder not found: {remote_path}")
|
|
852
|
+
|
|
853
|
+
if not os.path.isdir("{remote_path}"):
|
|
854
|
+
raise NotADirectoryError("Path is not a directory. Use download() instead.")
|
|
855
|
+
|
|
856
|
+
exclude_patterns = {exclude_patterns}
|
|
857
|
+
|
|
858
|
+
def should_exclude(name):
|
|
859
|
+
for pattern in exclude_patterns:
|
|
860
|
+
if fnmatch.fnmatch(name, pattern) or fnmatch.fnmatch(os.path.basename(name), pattern):
|
|
861
|
+
return True
|
|
862
|
+
return False
|
|
863
|
+
|
|
864
|
+
def filter_func(tarinfo):
|
|
865
|
+
if should_exclude(tarinfo.name):
|
|
866
|
+
return None
|
|
867
|
+
return tarinfo
|
|
868
|
+
|
|
869
|
+
# Create tar.gz in memory
|
|
870
|
+
tar_buffer = io.BytesIO()
|
|
871
|
+
with tarfile.open(fileobj=tar_buffer, mode='w:gz') as tar:
|
|
872
|
+
tar.add("{remote_path}", arcname=os.path.basename("{remote_path}"), filter=filter_func)
|
|
873
|
+
|
|
874
|
+
tar_data = tar_buffer.getvalue()
|
|
875
|
+
tar_b64 = base64.b64encode(tar_data).decode()
|
|
876
|
+
print(f"Compressed size: {{len(tar_data)}} bytes")
|
|
877
|
+
print("__FOLDERDATA_START__")
|
|
878
|
+
print(tar_b64)
|
|
879
|
+
print("__FOLDERDATA_END__")
|
|
880
|
+
'''
|
|
881
|
+
result = self.run(code, stream=False)
|
|
882
|
+
|
|
883
|
+
if not result.success:
|
|
884
|
+
raise ClouditiaError(f"Download folder failed: {result.error}")
|
|
885
|
+
|
|
886
|
+
# Extract base64 data from output
|
|
887
|
+
output = result.output or ""
|
|
888
|
+
if "__FOLDERDATA_START__" not in output or "__FOLDERDATA_END__" not in output:
|
|
889
|
+
raise ClouditiaError("No data received from remote")
|
|
890
|
+
|
|
891
|
+
if show_progress:
|
|
892
|
+
print(f"📥 Downloading...")
|
|
893
|
+
|
|
894
|
+
# Decode and extract locally
|
|
895
|
+
try:
|
|
896
|
+
start = output.index("__FOLDERDATA_START__") + len("__FOLDERDATA_START__")
|
|
897
|
+
end = output.index("__FOLDERDATA_END__")
|
|
898
|
+
tar_b64 = output[start:end].strip()
|
|
899
|
+
tar_data = base64.b64decode(tar_b64)
|
|
900
|
+
except Exception as e:
|
|
901
|
+
raise ClouditiaError(f"Failed to decode folder data: {e}")
|
|
902
|
+
|
|
903
|
+
# Create local directory
|
|
904
|
+
local_path.mkdir(parents=True, exist_ok=True)
|
|
905
|
+
|
|
906
|
+
# Extract
|
|
907
|
+
tar_buffer = io.BytesIO(tar_data)
|
|
908
|
+
with tarfile.open(fileobj=tar_buffer, mode='r:gz') as tar:
|
|
909
|
+
tar.extractall(path=local_path)
|
|
910
|
+
|
|
911
|
+
# Count files
|
|
912
|
+
file_count = sum(1 for root, dirs, files in os.walk(local_path) for f in files)
|
|
913
|
+
|
|
914
|
+
if show_progress:
|
|
915
|
+
print(f"✅ Downloaded to {local_path} ({file_count} files, {self._format_size(len(tar_data))})")
|
|
916
|
+
|
|
917
|
+
return True
|
|
918
|
+
|
|
919
|
+
def list_files(
|
|
920
|
+
self,
|
|
921
|
+
remote_path: str = "/workspace",
|
|
922
|
+
pattern: Optional[str] = None
|
|
923
|
+
) -> List[Dict]:
|
|
924
|
+
"""
|
|
925
|
+
List files in a remote directory.
|
|
926
|
+
|
|
927
|
+
Args:
|
|
928
|
+
remote_path: Directory path to list (default: /workspace)
|
|
929
|
+
pattern: Optional glob pattern to filter files (e.g., '*.py')
|
|
930
|
+
|
|
931
|
+
Returns:
|
|
932
|
+
List of dicts with file info (name, path, size, is_dir, modified).
|
|
933
|
+
|
|
934
|
+
Example:
|
|
935
|
+
>>> files = session.list_files("/workspace")
|
|
936
|
+
>>> for f in files:
|
|
937
|
+
... print(f"{f['name']} - {f['size']} bytes")
|
|
938
|
+
|
|
939
|
+
>>> py_files = session.list_files("/workspace", pattern="*.py")
|
|
940
|
+
"""
|
|
941
|
+
pattern_code = f'"{pattern}"' if pattern else 'None'
|
|
942
|
+
|
|
943
|
+
code = f'''
|
|
944
|
+
import os
|
|
945
|
+
import json
|
|
946
|
+
from datetime import datetime
|
|
947
|
+
import fnmatch
|
|
948
|
+
|
|
949
|
+
path = "{remote_path}"
|
|
950
|
+
pattern = {pattern_code}
|
|
951
|
+
|
|
952
|
+
if not os.path.exists(path):
|
|
953
|
+
raise FileNotFoundError(f"Path not found: {{path}}")
|
|
954
|
+
|
|
955
|
+
files = []
|
|
956
|
+
for item in os.listdir(path):
|
|
957
|
+
item_path = os.path.join(path, item)
|
|
958
|
+
stat = os.stat(item_path)
|
|
959
|
+
|
|
960
|
+
if pattern and not fnmatch.fnmatch(item, pattern):
|
|
961
|
+
continue
|
|
962
|
+
|
|
963
|
+
files.append({{
|
|
964
|
+
"name": item,
|
|
965
|
+
"path": item_path,
|
|
966
|
+
"size": stat.st_size,
|
|
967
|
+
"is_dir": os.path.isdir(item_path),
|
|
968
|
+
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
|
|
969
|
+
}})
|
|
970
|
+
|
|
971
|
+
# Sort: directories first, then by name
|
|
972
|
+
files.sort(key=lambda x: (not x["is_dir"], x["name"].lower()))
|
|
973
|
+
|
|
974
|
+
print("__LISTFILES_START__")
|
|
975
|
+
print(json.dumps(files))
|
|
976
|
+
print("__LISTFILES_END__")
|
|
977
|
+
'''
|
|
978
|
+
result = self.run(code, stream=False)
|
|
979
|
+
|
|
980
|
+
if not result.success:
|
|
981
|
+
raise ClouditiaError(f"Failed to list files: {result.error}")
|
|
982
|
+
|
|
983
|
+
output = result.output or ""
|
|
984
|
+
if "__LISTFILES_START__" in output and "__LISTFILES_END__" in output:
|
|
985
|
+
try:
|
|
986
|
+
start = output.index("__LISTFILES_START__") + len("__LISTFILES_START__")
|
|
987
|
+
end = output.index("__LISTFILES_END__")
|
|
988
|
+
files_json = output[start:end].strip()
|
|
989
|
+
return json.loads(files_json)
|
|
990
|
+
except (json.JSONDecodeError, ValueError):
|
|
991
|
+
return []
|
|
992
|
+
|
|
993
|
+
return []
|
|
994
|
+
|
|
995
|
+
def file_exists(self, remote_path: str) -> bool:
|
|
996
|
+
"""
|
|
997
|
+
Check if a file or directory exists on the remote pod.
|
|
998
|
+
|
|
999
|
+
Args:
|
|
1000
|
+
remote_path: Path to check
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
True if path exists, False otherwise.
|
|
1004
|
+
|
|
1005
|
+
Example:
|
|
1006
|
+
>>> if session.file_exists("/workspace/model.pt"):
|
|
1007
|
+
... session.download("/workspace/model.pt", "./model.pt")
|
|
1008
|
+
"""
|
|
1009
|
+
code = f'''
|
|
1010
|
+
import os
|
|
1011
|
+
exists = os.path.exists("{remote_path}")
|
|
1012
|
+
print("__EXISTS_START__")
|
|
1013
|
+
print("TRUE" if exists else "FALSE")
|
|
1014
|
+
print("__EXISTS_END__")
|
|
1015
|
+
'''
|
|
1016
|
+
result = self.run(code, stream=False)
|
|
1017
|
+
|
|
1018
|
+
output = result.output or ""
|
|
1019
|
+
if "__EXISTS_START__" in output and "__EXISTS_END__" in output:
|
|
1020
|
+
start = output.index("__EXISTS_START__") + len("__EXISTS_START__")
|
|
1021
|
+
end = output.index("__EXISTS_END__")
|
|
1022
|
+
value = output[start:end].strip()
|
|
1023
|
+
return value == "TRUE"
|
|
1024
|
+
return False
|
|
1025
|
+
|
|
1026
|
+
def _format_size(self, size: int) -> str:
|
|
1027
|
+
"""Format file size in human-readable format."""
|
|
1028
|
+
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
1029
|
+
if size < 1024:
|
|
1030
|
+
return f"{size:.1f} {unit}"
|
|
1031
|
+
size /= 1024
|
|
1032
|
+
return f"{size:.1f} TB"
|
|
1033
|
+
|
|
1034
|
+
# ==================== END FILE TRANSFER METHODS ====================
|
|
1035
|
+
|
|
551
1036
|
def submit(
|
|
552
1037
|
self,
|
|
553
1038
|
code: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clouditia
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Execute Python and Shell code on remote GPU sessions
|
|
5
5
|
Author-email: Aina KIKI-SAGBE <support@clouditia.com>
|
|
6
6
|
Maintainer-email: Aina KIKI-SAGBE <support@clouditia.com>
|
|
@@ -81,6 +81,7 @@ print(result.output)
|
|
|
81
81
|
- **Shell Commands**: Execute shell commands on the GPU pod
|
|
82
82
|
- **Persistent Sessions**: Keep variables between executions with `start()`/`stop()`
|
|
83
83
|
- **Variable Transfer**: Send and retrieve variables between local and remote
|
|
84
|
+
- **File Transfer**: Upload/download files and folders between local and remote
|
|
84
85
|
- **Async Jobs**: Submit long-running tasks with real-time log monitoring
|
|
85
86
|
- **Jupyter Magic**: Use `%%clouditia` magic in notebooks
|
|
86
87
|
- **Decorator Support**: Use `@session.remote` to run functions on GPU
|
|
@@ -95,11 +96,12 @@ print(result.output)
|
|
|
95
96
|
4. [Executing Python Code](#executing-python-code)
|
|
96
97
|
5. [Shell Commands](#shell-commands)
|
|
97
98
|
6. [Variable Transfer](#variable-transfer)
|
|
98
|
-
7. [
|
|
99
|
-
8. [
|
|
100
|
-
9. [
|
|
101
|
-
10. [
|
|
102
|
-
11. [
|
|
99
|
+
7. [File Transfer](#file-transfer)
|
|
100
|
+
8. [Remote Functions (Decorator)](#remote-functions-decorator)
|
|
101
|
+
9. [Async Jobs (Long-Running Tasks)](#async-jobs-long-running-tasks)
|
|
102
|
+
10. [Jupyter Magic](#jupyter-magic)
|
|
103
|
+
11. [Error Handling](#error-handling)
|
|
104
|
+
12. [API Reference](#api-reference)
|
|
103
105
|
|
|
104
106
|
---
|
|
105
107
|
|
|
@@ -377,6 +379,182 @@ print(f"Learning rate: {config['learning_rate']}")
|
|
|
377
379
|
|
|
378
380
|
---
|
|
379
381
|
|
|
382
|
+
## File Transfer
|
|
383
|
+
|
|
384
|
+
Transfer files and folders between your local machine and the remote GPU session.
|
|
385
|
+
|
|
386
|
+
### Uploading a Single File
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
# Upload a local file to the remote session
|
|
390
|
+
session.upload("./data.csv", "/home/coder/workspace/data.csv")
|
|
391
|
+
|
|
392
|
+
# Upload with custom path
|
|
393
|
+
session.upload("./model.pkl", "/home/coder/workspace/models/trained_model.pkl")
|
|
394
|
+
|
|
395
|
+
# Disable progress output
|
|
396
|
+
session.upload("./config.json", "/home/coder/workspace/config.json", show_progress=False)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Downloading a Single File
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# Download a file from the remote session
|
|
403
|
+
session.download("/home/coder/workspace/results.csv", "./results.csv")
|
|
404
|
+
|
|
405
|
+
# Download trained model
|
|
406
|
+
session.download("/home/coder/workspace/checkpoints/model.pt", "./local_model.pt")
|
|
407
|
+
|
|
408
|
+
# Download silently
|
|
409
|
+
session.download("/home/coder/workspace/logs.txt", "./logs.txt", show_progress=False)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Uploading a Folder
|
|
413
|
+
|
|
414
|
+
Upload an entire directory with all its contents:
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
# Upload a project folder
|
|
418
|
+
session.upload_folder("./my_project", "/home/coder/workspace/project")
|
|
419
|
+
|
|
420
|
+
# Upload with exclusions (default excludes: __pycache__, .git, *.pyc, .DS_Store, node_modules)
|
|
421
|
+
session.upload_folder(
|
|
422
|
+
"./my_project",
|
|
423
|
+
"/home/coder/workspace/project",
|
|
424
|
+
exclude=["*.log", ".env", "__pycache__", ".git"]
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# Upload data folder
|
|
428
|
+
session.upload_folder("./datasets", "/home/coder/workspace/data")
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Downloading a Folder
|
|
432
|
+
|
|
433
|
+
Download an entire directory with all its contents:
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
# Download results folder
|
|
437
|
+
session.download_folder("/home/coder/workspace/results", "./local_results")
|
|
438
|
+
|
|
439
|
+
# Download checkpoints
|
|
440
|
+
session.download_folder(
|
|
441
|
+
"/home/coder/workspace/checkpoints",
|
|
442
|
+
"./checkpoints",
|
|
443
|
+
exclude=["*.tmp", "*.log"]
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Download trained models
|
|
447
|
+
session.download_folder("/home/coder/workspace/models", "./downloaded_models")
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Listing Remote Files
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
# List files in a directory
|
|
454
|
+
files = session.list_files("/home/coder/workspace")
|
|
455
|
+
for f in files:
|
|
456
|
+
icon = "📁" if f["is_dir"] else "📄"
|
|
457
|
+
print(f"{icon} {f['name']} - {f['size']} bytes")
|
|
458
|
+
|
|
459
|
+
# Filter by pattern
|
|
460
|
+
python_files = session.list_files("/home/coder/workspace", pattern="*.py")
|
|
461
|
+
for f in python_files:
|
|
462
|
+
print(f"📄 {f['name']}")
|
|
463
|
+
|
|
464
|
+
# List with full details
|
|
465
|
+
files = session.list_files("/home/coder/workspace")
|
|
466
|
+
for f in files:
|
|
467
|
+
print(f"Name: {f['name']}")
|
|
468
|
+
print(f" Path: {f['path']}")
|
|
469
|
+
print(f" Size: {f['size']} bytes")
|
|
470
|
+
print(f" Is Directory: {f['is_dir']}")
|
|
471
|
+
print(f" Modified: {f['modified']}")
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Checking if a File Exists
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
# Check before downloading
|
|
478
|
+
if session.file_exists("/home/coder/workspace/model.pt"):
|
|
479
|
+
session.download("/home/coder/workspace/model.pt", "./model.pt")
|
|
480
|
+
print("Model downloaded!")
|
|
481
|
+
else:
|
|
482
|
+
print("Model not found, training required...")
|
|
483
|
+
|
|
484
|
+
# Check multiple files
|
|
485
|
+
files_to_check = ["config.json", "data.csv", "model.pt"]
|
|
486
|
+
for filename in files_to_check:
|
|
487
|
+
path = f"/home/coder/workspace/{filename}"
|
|
488
|
+
exists = session.file_exists(path)
|
|
489
|
+
status = "✓" if exists else "✗"
|
|
490
|
+
print(f"{status} {filename}")
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Complete Workflow Example
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from clouditia import GPUSession
|
|
497
|
+
|
|
498
|
+
session = GPUSession("ck_your_api_key")
|
|
499
|
+
|
|
500
|
+
# 1. Upload training data and code
|
|
501
|
+
session.upload_folder("./training_code", "/home/coder/workspace/code")
|
|
502
|
+
session.upload("./data/train.csv", "/home/coder/workspace/data/train.csv")
|
|
503
|
+
session.upload("./data/test.csv", "/home/coder/workspace/data/test.csv")
|
|
504
|
+
|
|
505
|
+
# 2. Run training
|
|
506
|
+
result = session.run("""
|
|
507
|
+
import sys
|
|
508
|
+
sys.path.insert(0, '/home/coder/workspace/code')
|
|
509
|
+
from train import train_model
|
|
510
|
+
|
|
511
|
+
model = train_model('/home/coder/workspace/data/train.csv')
|
|
512
|
+
model.save('/home/coder/workspace/output/model.pt')
|
|
513
|
+
print("Training complete!")
|
|
514
|
+
""")
|
|
515
|
+
|
|
516
|
+
# 3. Check and download results
|
|
517
|
+
if session.file_exists("/home/coder/workspace/output/model.pt"):
|
|
518
|
+
session.download("/home/coder/workspace/output/model.pt", "./trained_model.pt")
|
|
519
|
+
print("Model saved locally!")
|
|
520
|
+
|
|
521
|
+
# 4. Download all outputs
|
|
522
|
+
session.download_folder("/home/coder/workspace/output", "./results")
|
|
523
|
+
print("All results downloaded!")
|
|
524
|
+
|
|
525
|
+
# 5. List what was created
|
|
526
|
+
files = session.list_files("/home/coder/workspace/output")
|
|
527
|
+
print(f"Created {len(files)} files during training")
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Working with Different File Types
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
# CSV files
|
|
534
|
+
session.upload("./data.csv", "/home/coder/workspace/data.csv")
|
|
535
|
+
|
|
536
|
+
# Pickle files (models, data)
|
|
537
|
+
session.upload("./model.pkl", "/home/coder/workspace/model.pkl")
|
|
538
|
+
|
|
539
|
+
# PyTorch models
|
|
540
|
+
session.download("/home/coder/workspace/checkpoint.pt", "./checkpoint.pt")
|
|
541
|
+
|
|
542
|
+
# JSON configuration
|
|
543
|
+
session.upload("./config.json", "/home/coder/workspace/config.json")
|
|
544
|
+
|
|
545
|
+
# Text files
|
|
546
|
+
session.upload("./requirements.txt", "/home/coder/workspace/requirements.txt")
|
|
547
|
+
|
|
548
|
+
# Binary files
|
|
549
|
+
session.upload("./image.png", "/home/coder/workspace/image.png")
|
|
550
|
+
|
|
551
|
+
# Any file type works!
|
|
552
|
+
session.upload("./data.parquet", "/home/coder/workspace/data.parquet")
|
|
553
|
+
session.upload("./weights.h5", "/home/coder/workspace/weights.h5")
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
380
558
|
## Remote Functions (Decorator)
|
|
381
559
|
|
|
382
560
|
Use the `@session.remote` decorator to run functions on the GPU:
|
|
@@ -707,6 +885,12 @@ GPUSession(
|
|
|
707
885
|
| `stop()` | Stop the persistent session |
|
|
708
886
|
| `set(name, value)` | Send variable to remote |
|
|
709
887
|
| `get(name)` | Retrieve variable from remote |
|
|
888
|
+
| `upload(local_path, remote_path, show_progress=True)` | Upload a file to remote session |
|
|
889
|
+
| `download(remote_path, local_path, show_progress=True)` | Download a file from remote session |
|
|
890
|
+
| `upload_folder(local_path, remote_path, exclude=None)` | Upload a folder to remote session |
|
|
891
|
+
| `download_folder(remote_path, local_path, exclude=None)` | Download a folder from remote session |
|
|
892
|
+
| `list_files(remote_path, pattern=None)` | List files in remote directory |
|
|
893
|
+
| `file_exists(remote_path)` | Check if a file exists on remote |
|
|
710
894
|
| `submit(code, name=None, job_type="python")` | Submit async job |
|
|
711
895
|
| `jobs(status=None, limit=10)` | List jobs |
|
|
712
896
|
| `gpu_info()` | Get GPU information |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|