lightning-sdk 2025.8.19.post0__py3-none-any.whl → 2025.8.26__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.
- lightning_sdk/__init__.py +1 -1
- lightning_sdk/api/llm_api.py +6 -2
- lightning_sdk/api/studio_api.py +168 -2
- lightning_sdk/api/teamspace_api.py +60 -30
- lightning_sdk/api/user_api.py +49 -1
- lightning_sdk/api/utils.py +1 -1
- lightning_sdk/cli/config/set.py +6 -18
- lightning_sdk/cli/legacy/create.py +12 -14
- lightning_sdk/cli/legacy/delete.py +3 -3
- lightning_sdk/cli/legacy/deploy/_auth.py +4 -4
- lightning_sdk/cli/legacy/download.py +7 -7
- lightning_sdk/cli/legacy/job_and_mmt_action.py +4 -4
- lightning_sdk/cli/legacy/list.py +9 -9
- lightning_sdk/cli/legacy/open.py +3 -3
- lightning_sdk/cli/legacy/start.py +1 -0
- lightning_sdk/cli/legacy/switch.py +1 -0
- lightning_sdk/cli/legacy/upload.py +3 -3
- lightning_sdk/cli/studio/create.py +14 -23
- lightning_sdk/cli/studio/delete.py +28 -27
- lightning_sdk/cli/studio/list.py +5 -6
- lightning_sdk/cli/studio/ssh.py +19 -22
- lightning_sdk/cli/studio/start.py +23 -23
- lightning_sdk/cli/studio/stop.py +22 -26
- lightning_sdk/cli/studio/switch.py +20 -23
- lightning_sdk/cli/utils/resolve.py +1 -1
- lightning_sdk/cli/utils/save_to_config.py +27 -0
- lightning_sdk/cli/utils/studio_selection.py +106 -0
- lightning_sdk/cli/utils/teamspace_selection.py +125 -0
- lightning_sdk/lightning_cloud/openapi/__init__.py +3 -0
- lightning_sdk/lightning_cloud/openapi/api/billing_service_api.py +170 -0
- lightning_sdk/lightning_cloud/openapi/api/k8_s_cluster_service_api.py +101 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +3 -0
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +15 -15
- lightning_sdk/lightning_cloud/openapi/models/externalv1_user_status.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_metrics.py +270 -36
- lightning_sdk/lightning_cloud/openapi/models/v1_container_metrics.py +21 -21
- lightning_sdk/lightning_cloud/openapi/models/v1_list_cluster_metric_timestamps_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_namespace_metrics.py +11 -11
- lightning_sdk/lightning_cloud/openapi/models/v1_namespace_user_metrics.py +16 -16
- lightning_sdk/lightning_cloud/openapi/models/v1_node_metrics.py +156 -26
- lightning_sdk/lightning_cloud/openapi/models/v1_pod_metrics.py +281 -21
- lightning_sdk/lightning_cloud/openapi/models/v1_project_cluster_binding.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_purchase_annual_upsell_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_quote_annual_upsell_response.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_storage_asset.py +107 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +1 -27
- lightning_sdk/llm/llm.py +2 -2
- lightning_sdk/llm/public_assistants.py +4 -0
- lightning_sdk/studio.py +92 -28
- lightning_sdk/teamspace.py +25 -2
- lightning_sdk/user.py +19 -1
- lightning_sdk/utils/config.py +6 -0
- lightning_sdk/utils/names.py +1179 -0
- lightning_sdk/utils/progress.py +284 -0
- lightning_sdk/utils/resolve.py +6 -6
- {lightning_sdk-2025.8.19.post0.dist-info → lightning_sdk-2025.8.26.dist-info}/METADATA +1 -1
- {lightning_sdk-2025.8.19.post0.dist-info → lightning_sdk-2025.8.26.dist-info}/RECORD +61 -53
- {lightning_sdk-2025.8.19.post0.dist-info → lightning_sdk-2025.8.26.dist-info}/LICENSE +0 -0
- {lightning_sdk-2025.8.19.post0.dist-info → lightning_sdk-2025.8.26.dist-info}/WHEEL +0 -0
- {lightning_sdk-2025.8.19.post0.dist-info → lightning_sdk-2025.8.26.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-2025.8.19.post0.dist-info → lightning_sdk-2025.8.26.dist-info}/top_level.txt +0 -0
|
@@ -2,12 +2,12 @@ from typing import Optional
|
|
|
2
2
|
|
|
3
3
|
from lightning_sdk.cli.legacy.jobs_menu import _JobsMenu
|
|
4
4
|
from lightning_sdk.cli.legacy.mmts_menu import _MMTsMenu
|
|
5
|
-
from lightning_sdk.cli.
|
|
5
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
6
6
|
from lightning_sdk.job import Job
|
|
7
7
|
from lightning_sdk.mmt import MMT
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class _JobAndMMTAction(
|
|
10
|
+
class _JobAndMMTAction(TeamspacesMenu, _JobsMenu, _MMTsMenu):
|
|
11
11
|
"""Inspect resources of the Lightning AI platform to get additional details as JSON."""
|
|
12
12
|
|
|
13
13
|
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> Job:
|
|
@@ -20,7 +20,7 @@ class _JobAndMMTAction(_TeamspacesMenu, _JobsMenu, _MMTsMenu):
|
|
|
20
20
|
If not specified can be selected interactively.
|
|
21
21
|
|
|
22
22
|
"""
|
|
23
|
-
resolved_teamspace = self
|
|
23
|
+
resolved_teamspace = self(teamspace)
|
|
24
24
|
return self._resolve_job(name, teamspace=resolved_teamspace)
|
|
25
25
|
|
|
26
26
|
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> MMT:
|
|
@@ -33,5 +33,5 @@ class _JobAndMMTAction(_TeamspacesMenu, _JobsMenu, _MMTsMenu):
|
|
|
33
33
|
If not specified can be selected interactively.
|
|
34
34
|
|
|
35
35
|
"""
|
|
36
|
-
resolved_teamspace = self
|
|
36
|
+
resolved_teamspace = self(teamspace)
|
|
37
37
|
return self._resolve_mmt(name, teamspace=resolved_teamspace)
|
lightning_sdk/cli/legacy/list.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing_extensions import Literal
|
|
|
8
8
|
|
|
9
9
|
from lightning_sdk import Job, Machine, Studio, Teamspace
|
|
10
10
|
from lightning_sdk.cli.legacy.clusters_menu import _ClustersMenu
|
|
11
|
-
from lightning_sdk.cli.
|
|
11
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
12
12
|
from lightning_sdk.lightning_cloud.openapi import V1MultiMachineJob
|
|
13
13
|
from lightning_sdk.lit_container import LitContainer
|
|
14
14
|
from lightning_sdk.utils.resolve import _get_authed_user
|
|
@@ -48,7 +48,7 @@ def studios(
|
|
|
48
48
|
) -> None:
|
|
49
49
|
"""List studios for a given teamspace."""
|
|
50
50
|
studios = []
|
|
51
|
-
menu =
|
|
51
|
+
menu = TeamspacesMenu()
|
|
52
52
|
if all and not teamspace:
|
|
53
53
|
user = _get_authed_user()
|
|
54
54
|
possible_teamspaces = menu._get_possible_teamspaces(user)
|
|
@@ -56,7 +56,7 @@ def studios(
|
|
|
56
56
|
teamspace = Teamspace(**ts)
|
|
57
57
|
studios.extend(teamspace.studios)
|
|
58
58
|
else:
|
|
59
|
-
resolved_teamspace = menu
|
|
59
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
60
60
|
studios = resolved_teamspace.studios
|
|
61
61
|
|
|
62
62
|
table = Table(
|
|
@@ -111,7 +111,7 @@ def jobs(
|
|
|
111
111
|
) -> None:
|
|
112
112
|
"""List jobs for a given teamspace."""
|
|
113
113
|
jobs = []
|
|
114
|
-
menu =
|
|
114
|
+
menu = TeamspacesMenu()
|
|
115
115
|
if all and not teamspace:
|
|
116
116
|
user = _get_authed_user()
|
|
117
117
|
possible_teamspaces = menu._get_possible_teamspaces(user)
|
|
@@ -119,7 +119,7 @@ def jobs(
|
|
|
119
119
|
teamspace = Teamspace(**ts)
|
|
120
120
|
jobs.extend(teamspace.jobs)
|
|
121
121
|
else:
|
|
122
|
-
resolved_teamspace = menu
|
|
122
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
123
123
|
jobs = resolved_teamspace.jobs
|
|
124
124
|
|
|
125
125
|
table = Table(pad_edge=True)
|
|
@@ -182,7 +182,7 @@ def mmts(
|
|
|
182
182
|
) -> None:
|
|
183
183
|
"""List multi-machine jobs for a given teamspace."""
|
|
184
184
|
jobs = []
|
|
185
|
-
menu =
|
|
185
|
+
menu = TeamspacesMenu()
|
|
186
186
|
if all and not teamspace:
|
|
187
187
|
user = _get_authed_user()
|
|
188
188
|
possible_teamspaces = menu._get_possible_teamspaces(user)
|
|
@@ -190,7 +190,7 @@ def mmts(
|
|
|
190
190
|
teamspace = Teamspace(**ts)
|
|
191
191
|
jobs.extend(teamspace.multi_machine_jobs)
|
|
192
192
|
else:
|
|
193
|
-
resolved_teamspace = menu
|
|
193
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
194
194
|
jobs = resolved_teamspace.multi_machine_jobs
|
|
195
195
|
|
|
196
196
|
table = Table(pad_edge=True)
|
|
@@ -242,9 +242,9 @@ def mmts(
|
|
|
242
242
|
def containers(teamspace: Optional[str] = None, cloud_account: Optional[str] = None) -> None:
|
|
243
243
|
"""Display the list of available containers."""
|
|
244
244
|
api = LitContainer()
|
|
245
|
-
menu =
|
|
245
|
+
menu = TeamspacesMenu()
|
|
246
246
|
clusters_menu = _ClustersMenu()
|
|
247
|
-
resolved_teamspace = menu
|
|
247
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
248
248
|
|
|
249
249
|
if not cloud_account:
|
|
250
250
|
cloud_account = clusters_menu._resolve_cluster(resolved_teamspace)
|
lightning_sdk/cli/legacy/open.py
CHANGED
|
@@ -6,8 +6,8 @@ from typing import Optional
|
|
|
6
6
|
import click
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
|
-
from lightning_sdk.cli.legacy.teamspace_menu import _TeamspacesMenu
|
|
10
9
|
from lightning_sdk.cli.legacy.upload import _upload_folder
|
|
10
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
11
11
|
from lightning_sdk.studio import Studio
|
|
12
12
|
from lightning_sdk.teamspace import Teamspace
|
|
13
13
|
from lightning_sdk.utils.resolve import _get_studio_url
|
|
@@ -49,8 +49,8 @@ def open(path: str = ".", teamspace: Optional[str] = None, cloud_account: Option
|
|
|
49
49
|
try:
|
|
50
50
|
resolved_teamspace = Teamspace()
|
|
51
51
|
except ValueError:
|
|
52
|
-
menu =
|
|
53
|
-
resolved_teamspace = menu
|
|
52
|
+
menu = TeamspacesMenu()
|
|
53
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
54
54
|
|
|
55
55
|
# default cloud account to current studios cloud account if run from studio
|
|
56
56
|
# else it will fall back to teamspace default in the backend
|
|
@@ -16,7 +16,7 @@ from lightning_sdk.api.lit_container_api import DockerNotRunningError, LCRAuthFa
|
|
|
16
16
|
from lightning_sdk.api.utils import _get_cloud_url
|
|
17
17
|
from lightning_sdk.cli.legacy.exceptions import StudioCliError
|
|
18
18
|
from lightning_sdk.cli.legacy.studios_menu import _StudiosMenu
|
|
19
|
-
from lightning_sdk.cli.
|
|
19
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
20
20
|
from lightning_sdk.constants import _LIGHTNING_DEBUG
|
|
21
21
|
from lightning_sdk.models import upload_model as _upload_model
|
|
22
22
|
from lightning_sdk.studio import Studio
|
|
@@ -137,8 +137,8 @@ def upload_container(
|
|
|
137
137
|
platform: Optional[str] = "linux/amd64",
|
|
138
138
|
) -> None:
|
|
139
139
|
"""Upload a container to Lightning AI's container registry."""
|
|
140
|
-
menu =
|
|
141
|
-
teamspace = menu
|
|
140
|
+
menu = TeamspacesMenu()
|
|
141
|
+
teamspace = menu(teamspace)
|
|
142
142
|
console = Console()
|
|
143
143
|
with Progress(
|
|
144
144
|
SpinnerColumn(),
|
|
@@ -4,14 +4,15 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.utils.
|
|
7
|
+
from lightning_sdk.cli.utils.save_to_config import save_teamspace_to_config
|
|
8
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
8
9
|
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
9
10
|
from lightning_sdk.machine import CloudProvider
|
|
10
11
|
from lightning_sdk.studio import Studio
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@click.command("create")
|
|
14
|
-
@click.
|
|
15
|
+
@click.option("--name", help="The name of the studio to create. If not provided, a random name will be generated.")
|
|
15
16
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
16
17
|
@click.option(
|
|
17
18
|
"--cloud-provider",
|
|
@@ -24,7 +25,7 @@ from lightning_sdk.studio import Studio
|
|
|
24
25
|
type=click.STRING,
|
|
25
26
|
)
|
|
26
27
|
def create_studio(
|
|
27
|
-
|
|
28
|
+
name: Optional[str] = None,
|
|
28
29
|
teamspace: Optional[str] = None,
|
|
29
30
|
cloud_provider: Optional[str] = None,
|
|
30
31
|
cloud_account: Optional[str] = None,
|
|
@@ -32,37 +33,27 @@ def create_studio(
|
|
|
32
33
|
"""Create a new Studio.
|
|
33
34
|
|
|
34
35
|
Example:
|
|
35
|
-
lightning studio create
|
|
36
|
-
|
|
37
|
-
STUDIO_NAME: the name of the studio to create.
|
|
38
|
-
|
|
39
|
-
If STUDIO_NAME is not provided, will try to infer from environment or use the default value from the config.
|
|
36
|
+
lightning studio create
|
|
40
37
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
f"Could not resolve teamspace: '{teamspace}'. Teamspace should be specified as 'owner/name'. "
|
|
46
|
-
"Does the teamspace exist?"
|
|
47
|
-
)
|
|
48
|
-
else:
|
|
49
|
-
resolved_teamspace = None
|
|
38
|
+
menu = TeamspacesMenu()
|
|
39
|
+
|
|
40
|
+
resolved_teamspace = menu(teamspace)
|
|
41
|
+
save_teamspace_to_config(resolved_teamspace, overwrite=False)
|
|
50
42
|
|
|
51
43
|
if cloud_provider is not None:
|
|
52
44
|
cloud_provider = CloudProvider(cloud_provider)
|
|
53
45
|
|
|
54
46
|
try:
|
|
55
47
|
studio = Studio(
|
|
56
|
-
|
|
48
|
+
name=name,
|
|
57
49
|
teamspace=resolved_teamspace,
|
|
58
50
|
create_ok=True,
|
|
59
51
|
cloud_provider=cloud_provider,
|
|
60
52
|
cloud_account=cloud_account,
|
|
61
53
|
)
|
|
62
|
-
except (RuntimeError, ValueError, ApiException)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raise ValueError(f"Could not create Studio: '{studio_name}'. Please provide a Studio name") from None
|
|
54
|
+
except (RuntimeError, ValueError, ApiException):
|
|
55
|
+
if name:
|
|
56
|
+
raise ValueError(f"Could not create Studio: '{name}'. Does the Studio exist?") from None
|
|
57
|
+
raise ValueError(f"Could not create Studio: '{name}'. Please provide a Studio name") from None
|
|
67
58
|
|
|
68
59
|
click.echo(f"Studio '{studio.name}' created successfully")
|
|
@@ -4,41 +4,42 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.utils.
|
|
8
|
-
from lightning_sdk.
|
|
7
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
8
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@click.command("delete")
|
|
12
|
-
@click.
|
|
12
|
+
@click.option(
|
|
13
|
+
"--name",
|
|
14
|
+
help=(
|
|
15
|
+
"The name of the studio to start. "
|
|
16
|
+
"If not provided, will try to infer from environment, "
|
|
17
|
+
"use the default value from the config or prompt for interactive selection."
|
|
18
|
+
),
|
|
19
|
+
)
|
|
13
20
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
14
|
-
def delete_studio(
|
|
21
|
+
def delete_studio(name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
15
22
|
"""Delete a Studio.
|
|
16
23
|
|
|
17
24
|
Example:
|
|
18
|
-
lightning studio delete
|
|
25
|
+
lightning studio delete --name my-studio
|
|
19
26
|
|
|
20
|
-
STUDIO_NAME: the name of the studio to delete.
|
|
21
|
-
|
|
22
|
-
If STUDIO_NAME is not provided, will try to infer from environment or use the default value from the config.
|
|
23
27
|
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if studio_name:
|
|
41
|
-
raise ValueError(f"Could not delete Studio: '{studio_name}'. Does the Studio exist?") from None
|
|
42
|
-
raise ValueError("No studio name provided. Use 'lightning studio delete <name>' to delete a studio.") from None
|
|
28
|
+
menu = TeamspacesMenu()
|
|
29
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
30
|
+
|
|
31
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
32
|
+
studio = menu(studio=name)
|
|
33
|
+
|
|
34
|
+
studio_name = f"{studio.teamspace.owner.name}/{studio.teamspace.name}/{studio.name}"
|
|
35
|
+
confirmed = click.confirm(
|
|
36
|
+
f"Are you sure you want to delete studio '{studio_name}'?",
|
|
37
|
+
abort=True,
|
|
38
|
+
)
|
|
39
|
+
if not confirmed:
|
|
40
|
+
click.echo("Studio deletion cancelled")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
studio.delete()
|
|
43
44
|
|
|
44
45
|
click.echo(f"Studio '{studio.name}' deleted successfully")
|
lightning_sdk/cli/studio/list.py
CHANGED
|
@@ -6,8 +6,9 @@ import click
|
|
|
6
6
|
from rich.table import Table
|
|
7
7
|
|
|
8
8
|
from lightning_sdk.cli.utils.cloud_account_map import cloud_account_to_display_name
|
|
9
|
-
from lightning_sdk.cli.utils.resolve import resolve_teamspace_owner_name_format
|
|
10
9
|
from lightning_sdk.cli.utils.richt_print import rich_to_str
|
|
10
|
+
from lightning_sdk.cli.utils.save_to_config import save_teamspace_to_config
|
|
11
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
11
12
|
from lightning_sdk.studio import Studio
|
|
12
13
|
|
|
13
14
|
|
|
@@ -26,11 +27,9 @@ def list_studios(teamspace: Optional[str] = None, sort_by: Optional[str] = None)
|
|
|
26
27
|
lightning studio list --teamspace owner/teamspace
|
|
27
28
|
|
|
28
29
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# TODO: make this a generic CLI error
|
|
33
|
-
raise ValueError(f"Could not resolve teamspace: {teamspace}")
|
|
30
|
+
menu = TeamspacesMenu()
|
|
31
|
+
teamspace_resolved = menu(teamspace=teamspace)
|
|
32
|
+
save_teamspace_to_config(teamspace_resolved, overwrite=False)
|
|
34
33
|
|
|
35
34
|
studios = teamspace_resolved.studios
|
|
36
35
|
|
lightning_sdk/cli/studio/ssh.py
CHANGED
|
@@ -9,14 +9,22 @@ from typing import List, Optional
|
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
11
|
|
|
12
|
-
from lightning_sdk.cli.utils.
|
|
12
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
13
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
14
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
13
15
|
from lightning_sdk.lightning_cloud.login import Auth
|
|
14
|
-
from lightning_sdk.studio import Studio
|
|
15
16
|
from lightning_sdk.utils.config import _DEFAULT_CONFIG_FILE_PATH
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@click.command("ssh")
|
|
19
|
-
@click.
|
|
20
|
+
@click.option(
|
|
21
|
+
"--name",
|
|
22
|
+
help=(
|
|
23
|
+
"The name of the studio to start. "
|
|
24
|
+
"If not provided, will try to infer from environment, "
|
|
25
|
+
"use the default value from the config or prompt for interactive selection."
|
|
26
|
+
),
|
|
27
|
+
)
|
|
20
28
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)", type=click.STRING)
|
|
21
29
|
@click.option(
|
|
22
30
|
"--option",
|
|
@@ -25,33 +33,22 @@ from lightning_sdk.utils.config import _DEFAULT_CONFIG_FILE_PATH
|
|
|
25
33
|
multiple=True,
|
|
26
34
|
type=click.STRING,
|
|
27
35
|
)
|
|
28
|
-
def ssh_studio(
|
|
29
|
-
studio_name: Optional[str] = None, teamspace: Optional[str] = None, option: Optional[List[str]] = None
|
|
30
|
-
) -> None:
|
|
36
|
+
def ssh_studio(name: Optional[str] = None, teamspace: Optional[str] = None, option: Optional[List[str]] = None) -> None:
|
|
31
37
|
"""SSH into a Studio.
|
|
32
38
|
|
|
33
39
|
Example:
|
|
34
|
-
lightning studio ssh
|
|
35
|
-
|
|
36
|
-
STUDIO_NAME: the name of the studio to SSH into.
|
|
37
|
-
|
|
38
|
-
If STUDIO_NAME is not provided, will try to infer from environment or use the default value from the config.
|
|
40
|
+
lightning studio ssh --name my-studio
|
|
39
41
|
"""
|
|
40
42
|
auth = Auth()
|
|
41
43
|
auth.authenticate()
|
|
42
44
|
ssh_private_key_path = _download_ssh_keys(auth.api_key, force_download=False)
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
else:
|
|
52
|
-
resolved_teamspace = None
|
|
53
|
-
|
|
54
|
-
studio = Studio(studio_name, teamspace=resolved_teamspace)
|
|
46
|
+
menu = TeamspacesMenu()
|
|
47
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
48
|
+
|
|
49
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
50
|
+
studio = menu(studio=name)
|
|
51
|
+
save_studio_to_config(studio)
|
|
55
52
|
|
|
56
53
|
ssh_options = " -o " + " -o ".join(option) if option else ""
|
|
57
54
|
ssh_command = f"ssh -i {ssh_private_key_path}{ssh_options} s_{studio._studio.id}@ssh.lightning.ai"
|
|
@@ -4,14 +4,22 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.utils.
|
|
8
|
-
from lightning_sdk.
|
|
7
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
8
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
9
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
10
|
from lightning_sdk.machine import CloudProvider, Machine
|
|
10
11
|
from lightning_sdk.studio import Studio
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@click.command("start")
|
|
14
|
-
@click.
|
|
15
|
+
@click.option(
|
|
16
|
+
"--name",
|
|
17
|
+
help=(
|
|
18
|
+
"The name of the studio to start. "
|
|
19
|
+
"If not provided, will try to infer from environment, "
|
|
20
|
+
"use the default value from the config or prompt for interactive selection."
|
|
21
|
+
),
|
|
22
|
+
)
|
|
15
23
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
16
24
|
@click.option("--create", is_flag=True, help="Create the studio if it doesn't exist")
|
|
17
25
|
@click.option(
|
|
@@ -34,7 +42,7 @@ from lightning_sdk.studio import Studio
|
|
|
34
42
|
type=click.STRING,
|
|
35
43
|
)
|
|
36
44
|
def start_studio(
|
|
37
|
-
|
|
45
|
+
name: Optional[str] = None,
|
|
38
46
|
teamspace: Optional[str] = None,
|
|
39
47
|
create: bool = False,
|
|
40
48
|
machine: Optional[str] = None,
|
|
@@ -45,37 +53,29 @@ def start_studio(
|
|
|
45
53
|
"""Start a Studio.
|
|
46
54
|
|
|
47
55
|
Example:
|
|
48
|
-
lightning studio start
|
|
56
|
+
lightning studio start --name my-studio
|
|
49
57
|
|
|
50
|
-
STUDIO_NAME: the name of the studio to start.
|
|
51
|
-
|
|
52
|
-
If STUDIO_NAME is not provided, will try to infer from environment or use the default value from the config.
|
|
53
58
|
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if resolved_teamspace is None:
|
|
57
|
-
raise ValueError(
|
|
58
|
-
f"Could not resolve teamspace: '{teamspace}'. Teamspace should be specified as 'owner/name'. "
|
|
59
|
-
"Does the teamspace exist?"
|
|
60
|
-
)
|
|
61
|
-
else:
|
|
62
|
-
resolved_teamspace = None
|
|
59
|
+
menu = TeamspacesMenu()
|
|
60
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
63
61
|
|
|
64
62
|
if cloud_provider is not None:
|
|
65
63
|
cloud_provider = CloudProvider(cloud_provider)
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
if not create:
|
|
66
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
67
|
+
studio = menu(studio=name)
|
|
68
|
+
else:
|
|
68
69
|
studio = Studio(
|
|
69
|
-
|
|
70
|
+
name=name,
|
|
70
71
|
teamspace=resolved_teamspace,
|
|
71
72
|
create_ok=create,
|
|
72
73
|
cloud_provider=cloud_provider,
|
|
73
74
|
cloud_account=cloud_account,
|
|
74
75
|
)
|
|
75
|
-
except (RuntimeError, ValueError, ApiException):
|
|
76
|
-
if studio_name:
|
|
77
|
-
raise ValueError(f"Could not start Studio: '{studio_name}'. Does the Studio exist?") from None
|
|
78
|
-
raise ValueError(f"Could not start Studio: '{studio_name}'. Please provide a Studio name") from None
|
|
79
76
|
|
|
77
|
+
save_studio_to_config(studio)
|
|
78
|
+
|
|
79
|
+
Studio.show_progress = True
|
|
80
80
|
studio.start(machine, interruptible=interruptible)
|
|
81
81
|
click.echo(f"Studio '{studio.name}' started successfully")
|
lightning_sdk/cli/studio/stop.py
CHANGED
|
@@ -4,41 +4,37 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.utils.
|
|
8
|
-
from lightning_sdk.
|
|
7
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
8
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
9
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@click.command("stop")
|
|
12
|
-
@click.
|
|
13
|
+
@click.option(
|
|
14
|
+
"--name",
|
|
15
|
+
help=(
|
|
16
|
+
"The name of the studio to start. "
|
|
17
|
+
"If not provided, will try to infer from environment, "
|
|
18
|
+
"use the default value from the config or prompt for interactive selection."
|
|
19
|
+
),
|
|
20
|
+
)
|
|
13
21
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
14
|
-
def stop_studio(
|
|
22
|
+
def stop_studio(name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
15
23
|
"""Stop a Studio.
|
|
16
24
|
|
|
17
25
|
Example:
|
|
18
|
-
lightning studio stop
|
|
26
|
+
lightning studio stop --name my-studio
|
|
19
27
|
|
|
20
|
-
STUDIO_NAME: the name of the studio to stop.
|
|
21
|
-
|
|
22
|
-
If STUDIO_NAME is not provided, will try to infer from environment or use the default value from the config.
|
|
23
28
|
"""
|
|
24
29
|
# missing studio_name and teamspace are handled by the studio class
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
try:
|
|
36
|
-
studio = Studio(studio_name, teamspace=resolved_teamspace)
|
|
37
|
-
studio.stop()
|
|
38
|
-
except Exception:
|
|
39
|
-
# TODO: make this a generic CLI error
|
|
40
|
-
if studio_name:
|
|
41
|
-
raise ValueError(f"Could not stop studio: '{studio_name}'. Does the studio exist?") from None
|
|
42
|
-
raise ValueError("No studio name provided. Use 'lightning studio stop <name>' to stop a studio.") from None
|
|
30
|
+
menu = TeamspacesMenu()
|
|
31
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
32
|
+
|
|
33
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
34
|
+
studio = menu(studio=name)
|
|
35
|
+
|
|
36
|
+
studio.stop()
|
|
37
|
+
|
|
38
|
+
save_studio_to_config(studio)
|
|
43
39
|
|
|
44
40
|
click.echo(f"Studio '{studio.name}' stopped successfully")
|
|
@@ -4,14 +4,22 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.utils.
|
|
8
|
-
from lightning_sdk.
|
|
7
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
8
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
9
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
10
|
from lightning_sdk.machine import Machine
|
|
10
11
|
from lightning_sdk.studio import Studio
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@click.command("switch")
|
|
14
|
-
@click.
|
|
15
|
+
@click.option(
|
|
16
|
+
"--name",
|
|
17
|
+
help=(
|
|
18
|
+
"The name of the studio to start. "
|
|
19
|
+
"If not provided, will try to infer from environment, "
|
|
20
|
+
"use the default value from the config or prompt for interactive selection."
|
|
21
|
+
),
|
|
22
|
+
)
|
|
15
23
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
16
24
|
@click.option(
|
|
17
25
|
"--machine",
|
|
@@ -20,33 +28,22 @@ from lightning_sdk.studio import Studio
|
|
|
20
28
|
)
|
|
21
29
|
@click.option("--interruptible", is_flag=True, help="Switch the studio to an interruptible instance.")
|
|
22
30
|
def switch_studio(
|
|
23
|
-
|
|
31
|
+
name: Optional[str] = None,
|
|
24
32
|
teamspace: Optional[str] = None,
|
|
25
33
|
machine: Optional[str] = None,
|
|
26
34
|
interruptible: bool = False,
|
|
27
35
|
) -> None:
|
|
28
36
|
"""Switch a Studio to a different machine type."""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"Does the teamspace exist?"
|
|
35
|
-
)
|
|
36
|
-
else:
|
|
37
|
-
resolved_teamspace = None
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
studio = Studio(
|
|
41
|
-
studio_name,
|
|
42
|
-
teamspace=resolved_teamspace,
|
|
43
|
-
)
|
|
44
|
-
except (RuntimeError, ValueError, ApiException):
|
|
45
|
-
if studio_name:
|
|
46
|
-
raise ValueError(f"Could not switch Studio: '{studio_name}'. Does the Studio exist?") from None
|
|
47
|
-
raise ValueError(f"Could not switch Studio: '{studio_name}'. Please provide a Studio name") from None
|
|
37
|
+
menu = TeamspacesMenu()
|
|
38
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
39
|
+
|
|
40
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
41
|
+
studio = menu(studio=name)
|
|
48
42
|
|
|
49
43
|
resolved_machine = Machine.from_str(machine)
|
|
44
|
+
Studio.show_progress = True
|
|
50
45
|
studio.switch_machine(resolved_machine, interruptible=interruptible)
|
|
51
46
|
|
|
47
|
+
save_studio_to_config(studio)
|
|
48
|
+
|
|
52
49
|
click.echo(f"Studio '{studio.name}' switched to machine '{resolved_machine}' successfully")
|
|
@@ -4,7 +4,7 @@ from lightning_sdk.teamspace import Teamspace
|
|
|
4
4
|
from lightning_sdk.utils.resolve import _resolve_teamspace
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def resolve_teamspace_owner_name_format(teamspace_name: str) -> Optional[Teamspace]:
|
|
7
|
+
def resolve_teamspace_owner_name_format(teamspace_name: Optional[str]) -> Optional[Teamspace]:
|
|
8
8
|
teamspace_resolved = None
|
|
9
9
|
if teamspace_name is None:
|
|
10
10
|
return _resolve_teamspace(None, None, None)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from lightning_sdk.organization import Organization
|
|
2
|
+
from lightning_sdk.studio import Studio
|
|
3
|
+
from lightning_sdk.teamspace import Teamspace
|
|
4
|
+
from lightning_sdk.utils.config import Config, DefaultConfigKeys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def save_teamspace_to_config(teamspace: Teamspace, overwrite: bool = False) -> None:
|
|
8
|
+
saved = _save_to_config_if_not_exists(DefaultConfigKeys.teamspace_name, teamspace.name, overwrite)
|
|
9
|
+
saved = _save_to_config_if_not_exists(DefaultConfigKeys.teamspace_owner, teamspace.owner.name, overwrite=saved)
|
|
10
|
+
saved = _save_to_config_if_not_exists(
|
|
11
|
+
DefaultConfigKeys.teamspace_owner_type,
|
|
12
|
+
"organization" if isinstance(teamspace.owner, Organization) else "user",
|
|
13
|
+
overwrite=saved,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def save_studio_to_config(studio: Studio, overwrite: bool = False) -> None:
|
|
18
|
+
saved = _save_to_config_if_not_exists(DefaultConfigKeys.studio, studio.name, overwrite)
|
|
19
|
+
save_teamspace_to_config(studio.teamspace, overwrite=saved)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _save_to_config_if_not_exists(key: str, value: str, overwrite: bool = False) -> bool:
|
|
23
|
+
cfg = Config()
|
|
24
|
+
if overwrite or cfg.get(key) is None:
|
|
25
|
+
cfg.set(key, value)
|
|
26
|
+
return True
|
|
27
|
+
return False
|