alphai 0.1.2__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- alphai/__init__.py +40 -2
- alphai/auth.py +31 -11
- alphai/cleanup.py +351 -0
- alphai/cli.py +45 -910
- alphai/client.py +115 -70
- alphai/commands/__init__.py +24 -0
- alphai/commands/config.py +67 -0
- alphai/commands/docker.py +615 -0
- alphai/commands/jupyter.py +350 -0
- alphai/commands/notebooks.py +1173 -0
- alphai/commands/orgs.py +27 -0
- alphai/commands/projects.py +35 -0
- alphai/config.py +15 -5
- alphai/docker.py +80 -45
- alphai/exceptions.py +122 -0
- alphai/jupyter_manager.py +577 -0
- alphai/notebook_renderer.py +473 -0
- alphai/utils.py +67 -0
- {alphai-0.1.2.dist-info → alphai-0.2.1.dist-info}/METADATA +8 -9
- alphai-0.2.1.dist-info/RECORD +23 -0
- alphai-0.1.2.dist-info/RECORD +0 -12
- {alphai-0.1.2.dist-info → alphai-0.2.1.dist-info}/WHEEL +0 -0
- {alphai-0.1.2.dist-info → alphai-0.2.1.dist-info}/entry_points.txt +0 -0
- {alphai-0.1.2.dist-info → alphai-0.2.1.dist-info}/top_level.txt +0 -0
alphai/commands/orgs.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Organization commands for alphai CLI."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
6
|
+
|
|
7
|
+
from ..client import AlphAIClient
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
@click.pass_context
|
|
14
|
+
def orgs(ctx: click.Context) -> None:
|
|
15
|
+
"""List your organizations."""
|
|
16
|
+
client: AlphAIClient = ctx.obj['client']
|
|
17
|
+
|
|
18
|
+
with Progress(
|
|
19
|
+
SpinnerColumn(),
|
|
20
|
+
TextColumn("[progress.description]{task.description}"),
|
|
21
|
+
console=console
|
|
22
|
+
) as progress:
|
|
23
|
+
task = progress.add_task("Fetching organizations...", total=None)
|
|
24
|
+
orgs_data = client.get_organizations()
|
|
25
|
+
progress.update(task, completed=1)
|
|
26
|
+
|
|
27
|
+
client.display_organizations(orgs_data)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Project commands for alphai CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
8
|
+
|
|
9
|
+
from ..client import AlphAIClient
|
|
10
|
+
from ..config import Config
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@click.option('--org', help='Organization slug to filter by')
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def projects(ctx: click.Context, org: Optional[str]) -> None:
|
|
19
|
+
"""List your projects."""
|
|
20
|
+
client: AlphAIClient = ctx.obj['client']
|
|
21
|
+
config: Config = ctx.obj['config']
|
|
22
|
+
|
|
23
|
+
# Use provided org or current org
|
|
24
|
+
org_id = org or config.current_org
|
|
25
|
+
|
|
26
|
+
with Progress(
|
|
27
|
+
SpinnerColumn(),
|
|
28
|
+
TextColumn("[progress.description]{task.description}"),
|
|
29
|
+
console=console
|
|
30
|
+
) as progress:
|
|
31
|
+
task = progress.add_task("Fetching projects...", total=None)
|
|
32
|
+
projects_data = client.get_projects(org_id)
|
|
33
|
+
progress.update(task, completed=1)
|
|
34
|
+
|
|
35
|
+
client.display_projects(projects_data)
|
alphai/config.py
CHANGED
|
@@ -11,10 +11,9 @@ from pydantic import BaseModel, Field
|
|
|
11
11
|
class Config(BaseModel):
|
|
12
12
|
"""Configuration model for alphai CLI."""
|
|
13
13
|
|
|
14
|
-
api_url: str = Field(default="https://www.runalph.ai", description="API base URL")
|
|
14
|
+
api_url: str = Field(default="https://www.runalph.ai/api", description="API base URL")
|
|
15
15
|
bearer_token: Optional[str] = Field(default=None, description="Bearer token for API authentication")
|
|
16
|
-
current_org: Optional[str] = Field(default=None, description="Current organization
|
|
17
|
-
current_project: Optional[str] = Field(default=None, description="Current project ID")
|
|
16
|
+
current_org: Optional[str] = Field(default=None, description="Current organization slug")
|
|
18
17
|
debug: bool = Field(default=False, description="Enable debug mode")
|
|
19
18
|
|
|
20
19
|
@classmethod
|
|
@@ -80,9 +79,20 @@ class Config(BaseModel):
|
|
|
80
79
|
self.bearer_token = None
|
|
81
80
|
self.save()
|
|
82
81
|
|
|
82
|
+
@property
|
|
83
|
+
def base_url(self) -> str:
|
|
84
|
+
"""Get the base URL (without /api suffix) for auth and frontend URLs."""
|
|
85
|
+
if self.api_url.endswith("/api"):
|
|
86
|
+
return self.api_url[:-4]
|
|
87
|
+
return self.api_url.rstrip("/")
|
|
88
|
+
|
|
83
89
|
def to_sdk_config(self) -> Dict[str, Any]:
|
|
84
|
-
"""Convert to SDK configuration format.
|
|
90
|
+
"""Convert to SDK configuration format.
|
|
91
|
+
|
|
92
|
+
Note: The SDK expects the base URL without /api suffix,
|
|
93
|
+
as it constructs paths like /api/orgs internally.
|
|
94
|
+
"""
|
|
85
95
|
return {
|
|
86
96
|
"bearer_auth": self.bearer_token,
|
|
87
|
-
"server_url": self.
|
|
97
|
+
"server_url": self.base_url,
|
|
88
98
|
}
|
alphai/docker.py
CHANGED
|
@@ -8,6 +8,11 @@ from rich.console import Console
|
|
|
8
8
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
9
|
from rich.panel import Panel
|
|
10
10
|
|
|
11
|
+
from .utils import get_logger
|
|
12
|
+
from . import exceptions
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
class DockerManager:
|
|
13
18
|
"""Manage Docker operations for the alphai CLI."""
|
|
@@ -22,8 +27,11 @@ class DockerManager:
|
|
|
22
27
|
if self._docker_available is not None:
|
|
23
28
|
return self._docker_available
|
|
24
29
|
|
|
30
|
+
logger.debug("Checking Docker availability")
|
|
31
|
+
|
|
25
32
|
# Check if docker command exists
|
|
26
33
|
if not shutil.which("docker"):
|
|
34
|
+
logger.warning("Docker command not found in PATH")
|
|
27
35
|
self._docker_available = False
|
|
28
36
|
return False
|
|
29
37
|
|
|
@@ -36,8 +44,17 @@ class DockerManager:
|
|
|
36
44
|
timeout=10
|
|
37
45
|
)
|
|
38
46
|
self._docker_available = result.returncode == 0
|
|
47
|
+
if self._docker_available:
|
|
48
|
+
logger.info("Docker is available and running")
|
|
49
|
+
else:
|
|
50
|
+
logger.warning("Docker daemon is not running")
|
|
39
51
|
return self._docker_available
|
|
40
|
-
except
|
|
52
|
+
except subprocess.TimeoutExpired:
|
|
53
|
+
logger.error("Docker info command timed out")
|
|
54
|
+
self._docker_available = False
|
|
55
|
+
return False
|
|
56
|
+
except subprocess.SubprocessError as e:
|
|
57
|
+
logger.error(f"Error checking Docker availability: {e}")
|
|
41
58
|
self._docker_available = False
|
|
42
59
|
return False
|
|
43
60
|
|
|
@@ -89,10 +106,12 @@ class DockerManager:
|
|
|
89
106
|
command: Optional[str] = None
|
|
90
107
|
) -> Optional[Any]:
|
|
91
108
|
"""Run a Docker container with the specified configuration."""
|
|
109
|
+
logger.info(f"Starting container from image: {image}")
|
|
92
110
|
if not self.is_docker_available():
|
|
111
|
+
logger.error("Cannot run container: Docker is not available")
|
|
93
112
|
self.console.print("[red]Error: Docker is not available or not running[/red]")
|
|
94
113
|
self.console.print("[yellow]Please install Docker and ensure it's running[/yellow]")
|
|
95
|
-
|
|
114
|
+
raise exceptions.DockerNotAvailableError()
|
|
96
115
|
|
|
97
116
|
# Build docker run command
|
|
98
117
|
cmd = ["docker", "run"]
|
|
@@ -142,9 +161,11 @@ class DockerManager:
|
|
|
142
161
|
)
|
|
143
162
|
|
|
144
163
|
if not check_result.stdout.strip():
|
|
164
|
+
logger.info(f"Image {image} not found locally, pulling...")
|
|
145
165
|
self.console.print(f"[yellow]Image {image} not found locally, pulling...[/yellow]")
|
|
146
166
|
if not self.pull_image(image):
|
|
147
|
-
|
|
167
|
+
logger.error(f"Failed to pull image: {image}")
|
|
168
|
+
raise exceptions.DockerError(f"Failed to pull image: {image}")
|
|
148
169
|
|
|
149
170
|
# Run the container
|
|
150
171
|
if detach:
|
|
@@ -166,10 +187,12 @@ class DockerManager:
|
|
|
166
187
|
|
|
167
188
|
if result.returncode == 0:
|
|
168
189
|
container_id = result.stdout.strip()
|
|
169
|
-
|
|
190
|
+
logger.info(f"Container started successfully: {container_id[:12]}")
|
|
191
|
+
return ContainerHandle(container_id)
|
|
170
192
|
else:
|
|
193
|
+
logger.error(f"Failed to start container: {result.stderr}")
|
|
171
194
|
self.console.print(f"[red]Error starting container: {result.stderr}[/red]")
|
|
172
|
-
|
|
195
|
+
raise exceptions.ContainerError(f"Failed to start container: {result.stderr}")
|
|
173
196
|
else:
|
|
174
197
|
# Interactive mode
|
|
175
198
|
self.console.print(f"[green]Starting interactive container from {image}...[/green]")
|
|
@@ -178,17 +201,21 @@ class DockerManager:
|
|
|
178
201
|
try:
|
|
179
202
|
# Run interactively without capturing output
|
|
180
203
|
result = subprocess.run(cmd)
|
|
181
|
-
return
|
|
204
|
+
return ContainerHandle("interactive")
|
|
182
205
|
except KeyboardInterrupt:
|
|
183
206
|
self.console.print("\n[yellow]Container stopped by user[/yellow]")
|
|
184
|
-
return
|
|
207
|
+
return ContainerHandle("interactive")
|
|
185
208
|
|
|
186
209
|
except subprocess.TimeoutExpired:
|
|
210
|
+
logger.error("Timeout starting container")
|
|
187
211
|
self.console.print("[red]Timeout starting container[/red]")
|
|
188
|
-
|
|
212
|
+
raise exceptions.TimeoutError("start container", 30)
|
|
213
|
+
except exceptions.DockerError:
|
|
214
|
+
raise
|
|
189
215
|
except Exception as e:
|
|
216
|
+
logger.error(f"Unexpected error running container: {e}", exc_info=True)
|
|
190
217
|
self.console.print(f"[red]Error running container: {e}[/red]")
|
|
191
|
-
|
|
218
|
+
raise exceptions.ContainerError(f"Error running container: {e}")
|
|
192
219
|
|
|
193
220
|
def list_containers(self, all_containers: bool = False) -> list:
|
|
194
221
|
"""List Docker containers."""
|
|
@@ -262,8 +289,9 @@ class DockerManager:
|
|
|
262
289
|
|
|
263
290
|
def install_cloudflared_in_container(self, container_id: str) -> bool:
|
|
264
291
|
"""Install cloudflared in a running container."""
|
|
292
|
+
logger.info(f"Installing cloudflared in container {container_id[:12]}")
|
|
265
293
|
if not self.is_docker_available():
|
|
266
|
-
|
|
294
|
+
raise exceptions.DockerNotAvailableError()
|
|
267
295
|
|
|
268
296
|
try:
|
|
269
297
|
# Detect the container's package manager and architecture
|
|
@@ -271,8 +299,9 @@ class DockerManager:
|
|
|
271
299
|
architecture = self._detect_architecture(container_id)
|
|
272
300
|
|
|
273
301
|
if not package_manager:
|
|
302
|
+
logger.error(f"No compatible package manager found in container {container_id[:12]}")
|
|
274
303
|
self.console.print("[red]Unsupported container: No compatible package manager found[/red]")
|
|
275
|
-
|
|
304
|
+
raise exceptions.CloudflaredError("No compatible package manager found")
|
|
276
305
|
|
|
277
306
|
# Commands to install cloudflared based on package manager
|
|
278
307
|
install_commands = self._get_install_commands(package_manager, architecture)
|
|
@@ -298,15 +327,20 @@ class DockerManager:
|
|
|
298
327
|
|
|
299
328
|
progress.update(task, advance=1)
|
|
300
329
|
|
|
301
|
-
|
|
330
|
+
logger.info(f"Connector installed successfully in container {container_id[:12]}")
|
|
331
|
+
self.console.print("[green]✓ Connector installed[/green]")
|
|
302
332
|
return True
|
|
303
333
|
|
|
304
334
|
except subprocess.TimeoutExpired:
|
|
305
|
-
|
|
306
|
-
|
|
335
|
+
logger.error(f"Timeout installing connector in container {container_id[:12]}")
|
|
336
|
+
self.console.print("[red]Timeout installing connector[/red]")
|
|
337
|
+
raise exceptions.TimeoutError("install connector", 60)
|
|
338
|
+
except exceptions.CloudflaredError:
|
|
339
|
+
raise
|
|
307
340
|
except Exception as e:
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
logger.error(f"Error installing connector: {e}", exc_info=True)
|
|
342
|
+
self.console.print(f"[red]Error installing connector: {e}[/red]")
|
|
343
|
+
raise exceptions.CloudflaredError(f"Failed to install connector: {e}")
|
|
310
344
|
|
|
311
345
|
def _detect_package_manager(self, container_id: str) -> Optional[str]:
|
|
312
346
|
"""Detect the package manager available in the container."""
|
|
@@ -329,7 +363,7 @@ class DockerManager:
|
|
|
329
363
|
)
|
|
330
364
|
if result.returncode == 0:
|
|
331
365
|
return pm_name
|
|
332
|
-
except:
|
|
366
|
+
except Exception:
|
|
333
367
|
continue
|
|
334
368
|
|
|
335
369
|
return None
|
|
@@ -353,7 +387,7 @@ class DockerManager:
|
|
|
353
387
|
'i386': '386'
|
|
354
388
|
}
|
|
355
389
|
return arch_map.get(arch, 'amd64')
|
|
356
|
-
except:
|
|
390
|
+
except Exception:
|
|
357
391
|
pass
|
|
358
392
|
|
|
359
393
|
return 'amd64' # Default fallback
|
|
@@ -403,7 +437,7 @@ class DockerManager:
|
|
|
403
437
|
]
|
|
404
438
|
|
|
405
439
|
def setup_tunnel_in_container(self, container_id: str, tunnel_token: str) -> bool:
|
|
406
|
-
"""
|
|
440
|
+
"""Start cloudflared connector in container as a background process."""
|
|
407
441
|
if not self.is_docker_available():
|
|
408
442
|
return False
|
|
409
443
|
|
|
@@ -413,30 +447,30 @@ class DockerManager:
|
|
|
413
447
|
TextColumn("[progress.description]{task.description}"),
|
|
414
448
|
console=self.console
|
|
415
449
|
) as progress:
|
|
416
|
-
task = progress.add_task("
|
|
450
|
+
task = progress.add_task("Establishing connection...", total=None)
|
|
417
451
|
|
|
418
|
-
#
|
|
452
|
+
# Run cloudflared in background (no service installation needed)
|
|
419
453
|
result = subprocess.run(
|
|
420
|
-
["docker", "exec", "
|
|
454
|
+
["docker", "exec", "-d", container_id, "cloudflared", "tunnel", "run", "--token", tunnel_token],
|
|
421
455
|
capture_output=True,
|
|
422
456
|
text=True,
|
|
423
|
-
timeout=
|
|
457
|
+
timeout=10
|
|
424
458
|
)
|
|
425
459
|
|
|
426
460
|
progress.update(task, completed=1)
|
|
427
461
|
|
|
428
462
|
if result.returncode == 0:
|
|
429
|
-
self.console.print("[green]✓
|
|
463
|
+
self.console.print("[green]✓ Connected[/green]")
|
|
430
464
|
return True
|
|
431
465
|
else:
|
|
432
|
-
self.console.print(f"[red]Error
|
|
466
|
+
self.console.print(f"[red]Error connecting: {result.stderr}[/red]")
|
|
433
467
|
return False
|
|
434
468
|
|
|
435
469
|
except subprocess.TimeoutExpired:
|
|
436
|
-
self.console.print("[red]
|
|
470
|
+
self.console.print("[red]Connection timed out[/red]")
|
|
437
471
|
return False
|
|
438
472
|
except Exception as e:
|
|
439
|
-
self.console.print(f"[red]Error
|
|
473
|
+
self.console.print(f"[red]Error connecting: {e}[/red]")
|
|
440
474
|
return False
|
|
441
475
|
|
|
442
476
|
def exec_command(self, container_id: str, command: str) -> Optional[str]:
|
|
@@ -643,8 +677,8 @@ class DockerManager:
|
|
|
643
677
|
import secrets
|
|
644
678
|
return secrets.token_hex(32) # 64-character hex token
|
|
645
679
|
|
|
646
|
-
def
|
|
647
|
-
"""
|
|
680
|
+
def stop_cloudflared_in_container(self, container_id: str) -> bool:
|
|
681
|
+
"""Stop cloudflared processes in a running container."""
|
|
648
682
|
if not self.is_docker_available():
|
|
649
683
|
return False
|
|
650
684
|
|
|
@@ -654,31 +688,32 @@ class DockerManager:
|
|
|
654
688
|
TextColumn("[progress.description]{task.description}"),
|
|
655
689
|
console=self.console
|
|
656
690
|
) as progress:
|
|
657
|
-
task = progress.add_task("
|
|
691
|
+
task = progress.add_task("Stopping cloudflared...", total=None)
|
|
658
692
|
|
|
659
|
-
#
|
|
693
|
+
# Kill all cloudflared processes in the container
|
|
660
694
|
result = subprocess.run(
|
|
661
|
-
["docker", "exec",
|
|
695
|
+
["docker", "exec", container_id, "pkill", "-f", "cloudflared"],
|
|
662
696
|
capture_output=True,
|
|
663
697
|
text=True,
|
|
664
|
-
timeout=
|
|
698
|
+
timeout=10
|
|
665
699
|
)
|
|
666
700
|
|
|
667
701
|
progress.update(task, completed=1)
|
|
668
702
|
|
|
669
|
-
if
|
|
670
|
-
|
|
703
|
+
# pkill returns 0 if processes were found and killed, 1 if none found
|
|
704
|
+
if result.returncode in [0, 1]:
|
|
705
|
+
self.console.print("[green]✓ Connection closed[/green]")
|
|
671
706
|
return True
|
|
672
707
|
else:
|
|
673
|
-
# Don't treat this as a hard error
|
|
674
|
-
self.console.print(f"[yellow]Warning: Could not
|
|
708
|
+
# Don't treat this as a hard error
|
|
709
|
+
self.console.print(f"[yellow]Warning: Could not close connection: {result.stderr}[/yellow]")
|
|
675
710
|
return True
|
|
676
711
|
|
|
677
712
|
except subprocess.TimeoutExpired:
|
|
678
|
-
self.console.print("[yellow]Warning: Timeout
|
|
713
|
+
self.console.print("[yellow]Warning: Timeout closing connection[/yellow]")
|
|
679
714
|
return True
|
|
680
715
|
except Exception as e:
|
|
681
|
-
self.console.print(f"[yellow]Warning: Error
|
|
716
|
+
self.console.print(f"[yellow]Warning: Error closing connection: {e}[/yellow]")
|
|
682
717
|
return True
|
|
683
718
|
|
|
684
719
|
def stop_and_remove_container(self, container_id: str, force: bool = False) -> bool:
|
|
@@ -729,9 +764,9 @@ class DockerManager:
|
|
|
729
764
|
try:
|
|
730
765
|
# Check if container is running
|
|
731
766
|
if self.is_container_running(container_id):
|
|
732
|
-
# Step 1:
|
|
733
|
-
self.console.print("[yellow]
|
|
734
|
-
if not self.
|
|
767
|
+
# Step 1: Stop connection service if container is running
|
|
768
|
+
self.console.print("[yellow]Closing connection...[/yellow]")
|
|
769
|
+
if not self.stop_cloudflared_in_container(container_id):
|
|
735
770
|
success = False
|
|
736
771
|
|
|
737
772
|
# Give it a moment for the service to stop
|
|
@@ -755,10 +790,10 @@ class DockerManager:
|
|
|
755
790
|
return False
|
|
756
791
|
|
|
757
792
|
|
|
758
|
-
class
|
|
759
|
-
"""
|
|
793
|
+
class ContainerHandle:
|
|
794
|
+
"""Lightweight container reference returned from Docker operations."""
|
|
760
795
|
|
|
761
796
|
def __init__(self, container_id: str):
|
|
762
|
-
"""Initialize
|
|
797
|
+
"""Initialize container handle."""
|
|
763
798
|
self.id = container_id
|
|
764
799
|
self.short_id = container_id[:12] if len(container_id) > 12 else container_id
|
alphai/exceptions.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Custom exceptions for alphai CLI."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AlphAIException(Exception):
|
|
7
|
+
"""Base exception for all alphai errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, details: Optional[str] = None):
|
|
10
|
+
"""Initialize exception with message and optional details."""
|
|
11
|
+
self.message = message
|
|
12
|
+
self.details = details
|
|
13
|
+
super().__init__(self.message)
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
"""String representation of the exception."""
|
|
17
|
+
if self.details:
|
|
18
|
+
return f"{self.message}\nDetails: {self.details}"
|
|
19
|
+
return self.message
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthenticationError(AlphAIException):
|
|
23
|
+
"""Raised when authentication fails."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AuthorizationError(AlphAIException):
|
|
28
|
+
"""Raised when user lacks permissions for an operation."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class APIError(AlphAIException):
|
|
33
|
+
"""Raised when API request fails."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message: str, status_code: Optional[int] = None, details: Optional[str] = None):
|
|
36
|
+
"""Initialize API error with status code."""
|
|
37
|
+
self.status_code = status_code
|
|
38
|
+
super().__init__(message, details)
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
"""String representation including status code."""
|
|
42
|
+
base = super().__str__()
|
|
43
|
+
if self.status_code:
|
|
44
|
+
return f"{base} (Status: {self.status_code})"
|
|
45
|
+
return base
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DockerError(AlphAIException):
|
|
49
|
+
"""Raised when Docker operations fail."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DockerNotAvailableError(DockerError):
|
|
54
|
+
"""Raised when Docker is not installed or not running."""
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
super().__init__(
|
|
58
|
+
"Docker is not available or not running",
|
|
59
|
+
"Please install Docker and ensure it's running. Visit https://docs.docker.com/get-docker/"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ContainerError(DockerError):
|
|
64
|
+
"""Raised when container operations fail."""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TunnelError(AlphAIException):
|
|
69
|
+
"""Raised when tunnel operations fail."""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CloudflaredError(TunnelError):
|
|
74
|
+
"""Raised when cloudflared operations fail."""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ConfigurationError(AlphAIException):
|
|
79
|
+
"""Raised when configuration is invalid."""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ValidationError(AlphAIException):
|
|
84
|
+
"""Raised when input validation fails."""
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class NetworkError(AlphAIException):
|
|
89
|
+
"""Raised when network operations fail."""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TimeoutError(AlphAIException):
|
|
94
|
+
"""Raised when an operation times out."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, operation: str, timeout_seconds: int):
|
|
97
|
+
"""Initialize timeout error."""
|
|
98
|
+
super().__init__(
|
|
99
|
+
f"Operation '{operation}' timed out after {timeout_seconds} seconds",
|
|
100
|
+
"Try increasing the timeout or check your network connection"
|
|
101
|
+
)
|
|
102
|
+
self.operation = operation
|
|
103
|
+
self.timeout_seconds = timeout_seconds
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ResourceNotFoundError(AlphAIException):
|
|
107
|
+
"""Raised when a requested resource is not found."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, resource_type: str, resource_id: str):
|
|
110
|
+
"""Initialize resource not found error."""
|
|
111
|
+
super().__init__(
|
|
112
|
+
f"{resource_type} '{resource_id}' not found",
|
|
113
|
+
f"Verify the {resource_type.lower()} ID is correct"
|
|
114
|
+
)
|
|
115
|
+
self.resource_type = resource_type
|
|
116
|
+
self.resource_id = resource_id
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class JupyterError(AlphAIException):
|
|
120
|
+
"""Raised when Jupyter operations fail."""
|
|
121
|
+
pass
|
|
122
|
+
|