ml-dash 0.6.10__tar.gz → 0.6.11__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.
- {ml_dash-0.6.10 → ml_dash-0.6.11}/PKG-INFO +1 -1
- {ml_dash-0.6.10 → ml_dash-0.6.11}/pyproject.toml +1 -1
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli.py +5 -1
- ml_dash-0.6.11/src/ml_dash/cli_commands/remove.py +161 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/client.py +24 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/experiment.py +8 -4
- {ml_dash-0.6.10 → ml_dash-0.6.11}/LICENSE +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/README.md +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/__init__.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auth/__init__.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auth/constants.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auth/device_flow.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auth/device_secret.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auth/exceptions.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auth/token_storage.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/auto_start.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/buffer.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/__init__.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/api.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/create.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/download.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/list.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/login.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/logout.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/profile.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/cli_commands/upload.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/config.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/files.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/log.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/metric.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/params.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/py.typed +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/remote_auto_start.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/run.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/snowflake.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/storage.py +0 -0
- {ml_dash-0.6.10 → ml_dash-0.6.11}/src/ml_dash/track.py +0 -0
|
@@ -25,7 +25,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
# Import and add command parsers
|
|
28
|
-
from .cli_commands import upload, download, list as list_cmd, login, logout, profile, api, create
|
|
28
|
+
from .cli_commands import upload, download, list as list_cmd, login, logout, profile, api, create, remove
|
|
29
29
|
|
|
30
30
|
# Authentication commands
|
|
31
31
|
login.add_parser(subparsers)
|
|
@@ -37,6 +37,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
37
37
|
|
|
38
38
|
# Project commands
|
|
39
39
|
create.add_parser(subparsers)
|
|
40
|
+
remove.add_parser(subparsers)
|
|
40
41
|
|
|
41
42
|
# Data commands
|
|
42
43
|
upload.add_parser(subparsers)
|
|
@@ -77,6 +78,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
77
78
|
elif args.command == "create":
|
|
78
79
|
from .cli_commands import create
|
|
79
80
|
return create.cmd_create(args)
|
|
81
|
+
elif args.command == "remove":
|
|
82
|
+
from .cli_commands import remove
|
|
83
|
+
return remove.cmd_remove(args)
|
|
80
84
|
elif args.command == "upload":
|
|
81
85
|
from .cli_commands import upload
|
|
82
86
|
return upload.cmd_upload(args)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Remove command for ml-dash CLI - delete projects."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from ml_dash.client import RemoteClient
|
|
9
|
+
from ml_dash.config import config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def add_parser(subparsers):
|
|
13
|
+
"""Add remove command parser."""
|
|
14
|
+
parser = subparsers.add_parser(
|
|
15
|
+
"remove",
|
|
16
|
+
help="Delete a project",
|
|
17
|
+
description="""Delete a project from ml-dash.
|
|
18
|
+
|
|
19
|
+
WARNING: This will delete the project and all its experiments, metrics, files, and logs.
|
|
20
|
+
This action cannot be undone.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
# Delete a project in current user's namespace
|
|
24
|
+
ml-dash remove -p my-project
|
|
25
|
+
|
|
26
|
+
# Delete a project in a specific namespace
|
|
27
|
+
ml-dash remove -p geyang/old-project
|
|
28
|
+
|
|
29
|
+
# Skip confirmation prompt (use with caution!)
|
|
30
|
+
ml-dash remove -p my-project -y
|
|
31
|
+
""",
|
|
32
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"-p", "--prefix",
|
|
36
|
+
type=str,
|
|
37
|
+
required=True,
|
|
38
|
+
help="Project name or namespace/project",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"-y", "--yes",
|
|
42
|
+
action="store_true",
|
|
43
|
+
help="Skip confirmation prompt",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--dash-url",
|
|
47
|
+
type=str,
|
|
48
|
+
help="ML-Dash server URL (default: https://api.dash.ml)",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def cmd_remove(args) -> int:
|
|
53
|
+
"""Execute remove command."""
|
|
54
|
+
console = Console()
|
|
55
|
+
|
|
56
|
+
# Get remote URL
|
|
57
|
+
remote_url = args.dash_url or config.remote_url or "https://api.dash.ml"
|
|
58
|
+
|
|
59
|
+
# Parse the prefix
|
|
60
|
+
prefix = args.prefix.strip("/")
|
|
61
|
+
parts = prefix.split("/")
|
|
62
|
+
|
|
63
|
+
if len(parts) > 2:
|
|
64
|
+
console.print(
|
|
65
|
+
f"[red]Error:[/red] Prefix can have at most 2 parts (namespace/project).\n"
|
|
66
|
+
f"Got: {args.prefix}\n\n"
|
|
67
|
+
f"Examples:\n"
|
|
68
|
+
f" ml-dash remove -p my-project\n"
|
|
69
|
+
f" ml-dash remove -p geyang/old-project"
|
|
70
|
+
)
|
|
71
|
+
return 1
|
|
72
|
+
|
|
73
|
+
if len(parts) == 1:
|
|
74
|
+
# Format: project (use current user's namespace)
|
|
75
|
+
namespace = None
|
|
76
|
+
project_name = parts[0]
|
|
77
|
+
else:
|
|
78
|
+
# Format: namespace/project
|
|
79
|
+
namespace = parts[0]
|
|
80
|
+
project_name = parts[1]
|
|
81
|
+
|
|
82
|
+
return _remove_project(
|
|
83
|
+
namespace=namespace,
|
|
84
|
+
project_name=project_name,
|
|
85
|
+
dash_url=remote_url,
|
|
86
|
+
skip_confirm=args.yes,
|
|
87
|
+
console=console,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _remove_project(
|
|
92
|
+
namespace: Optional[str],
|
|
93
|
+
project_name: str,
|
|
94
|
+
dash_url: str,
|
|
95
|
+
skip_confirm: bool,
|
|
96
|
+
console: Console,
|
|
97
|
+
) -> int:
|
|
98
|
+
"""Remove a project."""
|
|
99
|
+
try:
|
|
100
|
+
# Initialize client (namespace will be auto-fetched from server if not provided)
|
|
101
|
+
client = RemoteClient(base_url=dash_url, namespace=namespace)
|
|
102
|
+
|
|
103
|
+
# Get namespace (triggers server query if not set)
|
|
104
|
+
namespace = client.namespace
|
|
105
|
+
|
|
106
|
+
if not namespace:
|
|
107
|
+
console.print("[red]Error:[/red] Could not determine namespace. Please login first.")
|
|
108
|
+
return 1
|
|
109
|
+
|
|
110
|
+
full_path = f"{namespace}/{project_name}"
|
|
111
|
+
|
|
112
|
+
# Get project ID to verify it exists
|
|
113
|
+
project_id = client._get_project_id(project_name)
|
|
114
|
+
if not project_id:
|
|
115
|
+
console.print(f"[yellow]⚠[/yellow] Project '[bold]{full_path}[/bold]' not found.")
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
# Confirmation prompt (unless -y flag is used)
|
|
119
|
+
if not skip_confirm:
|
|
120
|
+
console.print(
|
|
121
|
+
f"\n[red bold]⚠ WARNING ⚠[/red bold]\n\n"
|
|
122
|
+
f"You are about to delete project: [bold]{full_path}[/bold]\n"
|
|
123
|
+
f"This will permanently delete:\n"
|
|
124
|
+
f" • All experiments in this project\n"
|
|
125
|
+
f" • All metrics and logs\n"
|
|
126
|
+
f" • All uploaded files\n\n"
|
|
127
|
+
f"[red]This action CANNOT be undone.[/red]\n"
|
|
128
|
+
)
|
|
129
|
+
confirm = console.input("Type the project name to confirm deletion: ")
|
|
130
|
+
if confirm.strip() != project_name:
|
|
131
|
+
console.print("\n[yellow]Deletion cancelled.[/yellow]")
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
console.print(f"\n[dim]Deleting project '{full_path}'...[/dim]")
|
|
135
|
+
|
|
136
|
+
# Delete project using client method
|
|
137
|
+
result = client.delete_project(project_name)
|
|
138
|
+
|
|
139
|
+
# Success message
|
|
140
|
+
console.print(f"[green]✓[/green] {result.get('message', 'Project deleted successfully!')}")
|
|
141
|
+
console.print(f" Name: [bold]{project_name}[/bold]")
|
|
142
|
+
console.print(f" Namespace: [bold]{namespace}[/bold]")
|
|
143
|
+
console.print(f" Project ID: {project_id}")
|
|
144
|
+
console.print(f" Deleted nodes: {result.get('deleted', 0)}")
|
|
145
|
+
console.print(f" Deleted experiments: {result.get('experiments', 0)}")
|
|
146
|
+
|
|
147
|
+
return 0
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
# Check if it's a 404 not found
|
|
151
|
+
if hasattr(e, 'response') and hasattr(e.response, 'status_code') and e.response.status_code == 404:
|
|
152
|
+
console.print(f"[yellow]⚠[/yellow] Project '[bold]{project_name}[/bold]' not found in namespace '[bold]{namespace}[/bold]'")
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
# Check if it's a 403 forbidden
|
|
156
|
+
if hasattr(e, 'response') and hasattr(e.response, 'status_code') and e.response.status_code == 403:
|
|
157
|
+
console.print(f"[red]Error:[/red] Permission denied. You don't have permission to delete this project.")
|
|
158
|
+
return 1
|
|
159
|
+
|
|
160
|
+
console.print(f"[red]Error deleting project:[/red] {e}")
|
|
161
|
+
return 1
|
|
@@ -349,6 +349,30 @@ class RemoteClient:
|
|
|
349
349
|
# Project not found - return None to let server auto-create it
|
|
350
350
|
return None
|
|
351
351
|
|
|
352
|
+
def delete_project(self, project_slug: str) -> Dict[str, Any]:
|
|
353
|
+
"""
|
|
354
|
+
Delete a project and all its experiments, metrics, files, and logs.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
project_slug: Project slug
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Dict with projectId, deleted count, experiments count, and message
|
|
361
|
+
|
|
362
|
+
Raises:
|
|
363
|
+
httpx.HTTPStatusError: If request fails
|
|
364
|
+
ValueError: If project not found
|
|
365
|
+
"""
|
|
366
|
+
# Get project ID first
|
|
367
|
+
project_id = self._get_project_id(project_slug)
|
|
368
|
+
if not project_id:
|
|
369
|
+
raise ValueError(f"Project '{project_slug}' not found in namespace '{self.namespace}'")
|
|
370
|
+
|
|
371
|
+
# Delete using project-specific endpoint
|
|
372
|
+
response = self._client.delete(f"/projects/{project_id}")
|
|
373
|
+
response.raise_for_status()
|
|
374
|
+
return response.json()
|
|
375
|
+
|
|
352
376
|
def _get_experiment_node_id(self, experiment_id: str) -> str:
|
|
353
377
|
"""
|
|
354
378
|
Resolve node ID from experiment ID using GraphQL.
|
|
@@ -259,15 +259,17 @@ class Experiment:
|
|
|
259
259
|
from rich.console import Console
|
|
260
260
|
|
|
261
261
|
console = Console()
|
|
262
|
+
experiment_url = f"https://dash.ml/{self.run.prefix}"
|
|
262
263
|
console.print(
|
|
263
264
|
f"[dim]✓ Experiment started: [bold]{self.run.name}[/bold] (project: {self.run.project})[/dim]\n"
|
|
264
265
|
f"[dim]View your data, statistics, and plots online at:[/dim] "
|
|
265
|
-
f"[link=
|
|
266
|
+
f"[link={experiment_url}]{experiment_url}[/link]"
|
|
266
267
|
)
|
|
267
268
|
except ImportError:
|
|
268
269
|
# Fallback if rich is not available
|
|
270
|
+
experiment_url = f"https://dash.ml/{self.run.prefix}"
|
|
269
271
|
print(f"✓ Experiment started: {self.run.name} (project: {self.run.project})")
|
|
270
|
-
print("View your data at:
|
|
272
|
+
print(f"View your data at: {experiment_url}")
|
|
271
273
|
|
|
272
274
|
except Exception as e:
|
|
273
275
|
# Check if it's an authentication error
|
|
@@ -381,18 +383,20 @@ class Experiment:
|
|
|
381
383
|
from rich.console import Console
|
|
382
384
|
|
|
383
385
|
console = Console()
|
|
386
|
+
experiment_url = f"https://dash.ml/{self.run.prefix}"
|
|
384
387
|
console.print(
|
|
385
388
|
f"[{status_color}]{status_emoji} Experiment {status.lower()}: "
|
|
386
389
|
f"[bold]{self.run.name}[/bold] (project: {self.run.project})[/{status_color}]\n"
|
|
387
390
|
f"[dim]View results, statistics, and plots online at:[/dim] "
|
|
388
|
-
f"[link=
|
|
391
|
+
f"[link={experiment_url}]{experiment_url}[/link]"
|
|
389
392
|
)
|
|
390
393
|
except ImportError:
|
|
391
394
|
# Fallback if rich is not available
|
|
395
|
+
experiment_url = f"https://dash.ml/{self.run.prefix}"
|
|
392
396
|
print(
|
|
393
397
|
f"{status_emoji} Experiment {status.lower()}: {self.run.name} (project: {self.run.project})"
|
|
394
398
|
)
|
|
395
|
-
print("View results at:
|
|
399
|
+
print(f"View results at: {experiment_url}")
|
|
396
400
|
|
|
397
401
|
except Exception as e:
|
|
398
402
|
# Log error but don't fail the close operation
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|