ml-dash 0.6.4__py3-none-any.whl → 0.6.6__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.
- ml_dash/__init__.py +1 -2
- ml_dash/auto_start.py +1 -4
- ml_dash/cli.py +7 -1
- ml_dash/cli_commands/create.py +145 -0
- ml_dash/cli_commands/upload.py +17 -4
- ml_dash/client.py +378 -18
- ml_dash/experiment.py +231 -357
- ml_dash/run.py +92 -3
- ml_dash/storage.py +0 -2
- {ml_dash-0.6.4.dist-info → ml_dash-0.6.6.dist-info}/METADATA +1 -1
- {ml_dash-0.6.4.dist-info → ml_dash-0.6.6.dist-info}/RECORD +13 -12
- {ml_dash-0.6.4.dist-info → ml_dash-0.6.6.dist-info}/WHEEL +0 -0
- {ml_dash-0.6.4.dist-info → ml_dash-0.6.6.dist-info}/entry_points.txt +0 -0
ml_dash/__init__.py
CHANGED
|
@@ -37,7 +37,7 @@ Usage:
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
from .client import RemoteClient
|
|
40
|
-
from .experiment import Experiment, OperationMode,
|
|
40
|
+
from .experiment import Experiment, OperationMode, ml_dash_experiment
|
|
41
41
|
from .log import LogBuilder, LogLevel
|
|
42
42
|
from .params import ParametersBuilder
|
|
43
43
|
from .run import RUN
|
|
@@ -49,7 +49,6 @@ __all__ = [
|
|
|
49
49
|
"Experiment",
|
|
50
50
|
"ml_dash_experiment",
|
|
51
51
|
"OperationMode",
|
|
52
|
-
"RunManager",
|
|
53
52
|
"RemoteClient",
|
|
54
53
|
"LocalStorage",
|
|
55
54
|
"LogLevel",
|
ml_dash/auto_start.py
CHANGED
|
@@ -43,10 +43,7 @@ _user = get_jwt_user()
|
|
|
43
43
|
_username = _user["username"] if _user else getpass.getuser()
|
|
44
44
|
_now = datetime.now()
|
|
45
45
|
|
|
46
|
-
dxp = Experiment(
|
|
47
|
-
prefix=f"{_username}/scratch/{_now:%Y-%m-%d/%H%M%S}",
|
|
48
|
-
dash_url="https://api.dash.ml",
|
|
49
|
-
)
|
|
46
|
+
dxp = Experiment()
|
|
50
47
|
|
|
51
48
|
|
|
52
49
|
# Register cleanup handler to complete experiment on Python exit (if still open)
|
ml_dash/cli.py
CHANGED
|
@@ -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
|
|
28
|
+
from .cli_commands import upload, download, list as list_cmd, login, logout, profile, api, create
|
|
29
29
|
|
|
30
30
|
# Authentication commands
|
|
31
31
|
login.add_parser(subparsers)
|
|
@@ -35,6 +35,9 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
35
35
|
# API commands
|
|
36
36
|
api.add_parser(subparsers)
|
|
37
37
|
|
|
38
|
+
# Project commands
|
|
39
|
+
create.add_parser(subparsers)
|
|
40
|
+
|
|
38
41
|
# Data commands
|
|
39
42
|
upload.add_parser(subparsers)
|
|
40
43
|
download.add_parser(subparsers)
|
|
@@ -71,6 +74,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
71
74
|
elif args.command == "profile":
|
|
72
75
|
from .cli_commands import profile
|
|
73
76
|
return profile.cmd_profile(args)
|
|
77
|
+
elif args.command == "create":
|
|
78
|
+
from .cli_commands import create
|
|
79
|
+
return create.cmd_create(args)
|
|
74
80
|
elif args.command == "upload":
|
|
75
81
|
from .cli_commands import upload
|
|
76
82
|
return upload.cmd_upload(args)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Create command for ml-dash CLI - create 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 create command parser."""
|
|
14
|
+
parser = subparsers.add_parser(
|
|
15
|
+
"create",
|
|
16
|
+
help="Create a new project",
|
|
17
|
+
description="""Create a new project in ml-dash.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
# Create a project in current user's namespace
|
|
21
|
+
ml-dash create -p new-project
|
|
22
|
+
|
|
23
|
+
# Create a project in a specific namespace
|
|
24
|
+
ml-dash create -p geyang/new-project
|
|
25
|
+
|
|
26
|
+
# Create with description
|
|
27
|
+
ml-dash create -p geyang/tutorials -d "ML tutorials and examples"
|
|
28
|
+
""",
|
|
29
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"-p", "--prefix",
|
|
33
|
+
type=str,
|
|
34
|
+
required=True,
|
|
35
|
+
help="Project name or namespace/project",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"-d", "--description",
|
|
39
|
+
type=str,
|
|
40
|
+
help="Project description (optional)",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--dash-url",
|
|
44
|
+
type=str,
|
|
45
|
+
help="ML-Dash server URL (default: https://api.dash.ml)",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_create(args) -> int:
|
|
50
|
+
"""Execute create command."""
|
|
51
|
+
console = Console()
|
|
52
|
+
|
|
53
|
+
# Get remote URL
|
|
54
|
+
remote_url = args.dash_url or config.remote_url or "https://api.dash.ml"
|
|
55
|
+
|
|
56
|
+
# Parse the prefix
|
|
57
|
+
prefix = args.prefix.strip("/")
|
|
58
|
+
parts = prefix.split("/")
|
|
59
|
+
|
|
60
|
+
if len(parts) > 2:
|
|
61
|
+
console.print(
|
|
62
|
+
f"[red]Error:[/red] Prefix can have at most 2 parts (namespace/project).\n"
|
|
63
|
+
f"Got: {args.prefix}\n\n"
|
|
64
|
+
f"Examples:\n"
|
|
65
|
+
f" ml-dash create -p new-project\n"
|
|
66
|
+
f" ml-dash create -p geyang/new-project"
|
|
67
|
+
)
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
if len(parts) == 1:
|
|
71
|
+
# Format: project (use current user's namespace)
|
|
72
|
+
namespace = None
|
|
73
|
+
project_name = parts[0]
|
|
74
|
+
else:
|
|
75
|
+
# Format: namespace/project
|
|
76
|
+
namespace = parts[0]
|
|
77
|
+
project_name = parts[1]
|
|
78
|
+
|
|
79
|
+
return _create_project(
|
|
80
|
+
namespace=namespace,
|
|
81
|
+
project_name=project_name,
|
|
82
|
+
description=args.description,
|
|
83
|
+
dash_url=remote_url,
|
|
84
|
+
console=console,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _create_project(
|
|
89
|
+
namespace: Optional[str],
|
|
90
|
+
project_name: str,
|
|
91
|
+
description: Optional[str],
|
|
92
|
+
dash_url: str,
|
|
93
|
+
console: Console,
|
|
94
|
+
) -> int:
|
|
95
|
+
"""Create a new project."""
|
|
96
|
+
try:
|
|
97
|
+
# Initialize client (namespace will be auto-fetched from server if not provided)
|
|
98
|
+
client = RemoteClient(base_url=dash_url, namespace=namespace)
|
|
99
|
+
|
|
100
|
+
# Get namespace (triggers server query if not set)
|
|
101
|
+
namespace = client.namespace
|
|
102
|
+
|
|
103
|
+
if not namespace:
|
|
104
|
+
console.print("[red]Error:[/red] Could not determine namespace. Please login first.")
|
|
105
|
+
return 1
|
|
106
|
+
|
|
107
|
+
console.print(f"[dim]Creating project '{project_name}' in namespace '{namespace}'[/dim]")
|
|
108
|
+
|
|
109
|
+
# Create project using unified node API
|
|
110
|
+
response = client._client.post(
|
|
111
|
+
f"/namespaces/{namespace}/nodes",
|
|
112
|
+
json={
|
|
113
|
+
"type": "PROJECT",
|
|
114
|
+
"name": project_name,
|
|
115
|
+
"slug": project_name,
|
|
116
|
+
"description": description or "",
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
response.raise_for_status()
|
|
120
|
+
result = response.json()
|
|
121
|
+
|
|
122
|
+
# Extract project info
|
|
123
|
+
project = result.get("project", {})
|
|
124
|
+
project_id = project.get("id")
|
|
125
|
+
project_slug = project.get("slug")
|
|
126
|
+
|
|
127
|
+
# Success message
|
|
128
|
+
console.print(f"[green]✓[/green] Project created successfully!")
|
|
129
|
+
console.print(f" Name: [bold]{project_slug}[/bold]")
|
|
130
|
+
console.print(f" Namespace: [bold]{namespace}[/bold]")
|
|
131
|
+
console.print(f" ID: {project_id}")
|
|
132
|
+
if description:
|
|
133
|
+
console.print(f" Description: {description}")
|
|
134
|
+
console.print(f"\n View at: https://dash.ml/@{namespace}/{project_slug}")
|
|
135
|
+
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
# Check if it's a 409 conflict (project already exists)
|
|
140
|
+
if hasattr(e, 'response') and hasattr(e.response, 'status_code') and e.response.status_code == 409:
|
|
141
|
+
console.print(f"[yellow]⚠[/yellow] Project '[bold]{project_name}[/bold]' already exists in namespace '[bold]{namespace}[/bold]'")
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
console.print(f"[red]Error creating project:[/red] {e}")
|
|
145
|
+
return 1
|
ml_dash/cli_commands/upload.py
CHANGED
|
@@ -265,7 +265,11 @@ def discover_experiments(
|
|
|
265
265
|
|
|
266
266
|
Args:
|
|
267
267
|
local_path: Root path of local storage
|
|
268
|
-
project_filter:
|
|
268
|
+
project_filter: Either a simple project name (e.g., "proj1") or a glob
|
|
269
|
+
pattern for the full path (e.g., "tom/*/exp*"). If the
|
|
270
|
+
filter contains '/', '*', or '?', it's treated as a glob
|
|
271
|
+
pattern matched against the full relative path. Otherwise,
|
|
272
|
+
it's matched exactly against the project name.
|
|
269
273
|
experiment_filter: Only discover this experiment (requires project_filter)
|
|
270
274
|
|
|
271
275
|
Returns:
|
|
@@ -319,9 +323,18 @@ def discover_experiments(
|
|
|
319
323
|
|
|
320
324
|
# Apply filters with glob pattern support
|
|
321
325
|
if project_filter:
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
326
|
+
# Check if project_filter is a glob pattern or simple project name
|
|
327
|
+
is_glob_pattern = any(c in project_filter for c in ['*', '?', '/'])
|
|
328
|
+
|
|
329
|
+
if is_glob_pattern:
|
|
330
|
+
# Treat as glob pattern - match against full relative path
|
|
331
|
+
if not fnmatch.fnmatch(full_relative_path, project_filter):
|
|
332
|
+
continue
|
|
333
|
+
else:
|
|
334
|
+
# Treat as simple project name - match against parsed project
|
|
335
|
+
if project_name != project_filter:
|
|
336
|
+
continue
|
|
337
|
+
|
|
325
338
|
if experiment_filter and exp_name != experiment_filter:
|
|
326
339
|
continue
|
|
327
340
|
|