lightning-sdk 2025.8.21__py3-none-any.whl → 2025.8.28__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/studio_api.py +69 -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 +3 -3
- 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/upload.py +3 -3
- lightning_sdk/cli/studio/create.py +16 -24
- lightning_sdk/cli/studio/delete.py +28 -27
- lightning_sdk/cli/studio/list.py +29 -15
- lightning_sdk/cli/studio/ssh.py +19 -22
- lightning_sdk/cli/studio/start.py +25 -25
- lightning_sdk/cli/studio/stop.py +25 -28
- lightning_sdk/cli/studio/switch.py +21 -24
- lightning_sdk/cli/utils/resolve.py +1 -1
- lightning_sdk/cli/utils/richt_print.py +24 -0
- 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 +2 -0
- lightning_sdk/lightning_cloud/openapi/api/billing_service_api.py +85 -0
- lightning_sdk/lightning_cloud/openapi/api/k8_s_cluster_service_api.py +101 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +2 -0
- 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 +145 -41
- lightning_sdk/lightning_cloud/openapi/models/v1_purchase_annual_upsell_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_storage_asset.py +107 -3
- lightning_sdk/llm/public_assistants.py +4 -0
- lightning_sdk/studio.py +54 -22
- 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 +2 -2
- lightning_sdk/utils/resolve.py +17 -6
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.28.dist-info}/METADATA +1 -1
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.28.dist-info}/RECORD +54 -48
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.28.dist-info}/LICENSE +0 -0
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.28.dist-info}/WHEEL +0 -0
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.28.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.28.dist-info}/top_level.txt +0 -0
|
@@ -4,14 +4,16 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.utils.
|
|
7
|
+
from lightning_sdk.cli.utils.richt_print import studio_name_link
|
|
8
|
+
from lightning_sdk.cli.utils.save_to_config import save_teamspace_to_config
|
|
9
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
8
10
|
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
9
11
|
from lightning_sdk.machine import CloudProvider
|
|
10
12
|
from lightning_sdk.studio import Studio
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@click.command("create")
|
|
14
|
-
@click.
|
|
16
|
+
@click.option("--name", help="The name of the studio to create. If not provided, a random name will be generated.")
|
|
15
17
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
16
18
|
@click.option(
|
|
17
19
|
"--cloud-provider",
|
|
@@ -24,7 +26,7 @@ from lightning_sdk.studio import Studio
|
|
|
24
26
|
type=click.STRING,
|
|
25
27
|
)
|
|
26
28
|
def create_studio(
|
|
27
|
-
|
|
29
|
+
name: Optional[str] = None,
|
|
28
30
|
teamspace: Optional[str] = None,
|
|
29
31
|
cloud_provider: Optional[str] = None,
|
|
30
32
|
cloud_account: Optional[str] = None,
|
|
@@ -32,37 +34,27 @@ def create_studio(
|
|
|
32
34
|
"""Create a new Studio.
|
|
33
35
|
|
|
34
36
|
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.
|
|
37
|
+
lightning studio create
|
|
40
38
|
"""
|
|
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
|
|
39
|
+
menu = TeamspacesMenu()
|
|
40
|
+
|
|
41
|
+
resolved_teamspace = menu(teamspace)
|
|
42
|
+
save_teamspace_to_config(resolved_teamspace, overwrite=False)
|
|
50
43
|
|
|
51
44
|
if cloud_provider is not None:
|
|
52
45
|
cloud_provider = CloudProvider(cloud_provider)
|
|
53
46
|
|
|
54
47
|
try:
|
|
55
48
|
studio = Studio(
|
|
56
|
-
|
|
49
|
+
name=name,
|
|
57
50
|
teamspace=resolved_teamspace,
|
|
58
51
|
create_ok=True,
|
|
59
52
|
cloud_provider=cloud_provider,
|
|
60
53
|
cloud_account=cloud_account,
|
|
61
54
|
)
|
|
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
|
|
55
|
+
except (RuntimeError, ValueError, ApiException):
|
|
56
|
+
if name:
|
|
57
|
+
raise ValueError(f"Could not create Studio: '{name}'. Does the Studio exist?") from None
|
|
58
|
+
raise ValueError(f"Could not create Studio: '{name}'. Please provide a Studio name") from None
|
|
67
59
|
|
|
68
|
-
click.echo(f"Studio
|
|
60
|
+
click.echo(f"Studio {studio_name_link(studio)} 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,31 +6,40 @@ 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.
|
|
10
|
-
from lightning_sdk.cli.utils.
|
|
9
|
+
from lightning_sdk.cli.utils.richt_print import rich_to_str, studio_name_link
|
|
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
|
|
13
|
+
from lightning_sdk.utils.resolve import _get_authed_user, prevent_refetch_studio
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
@click.command("list")
|
|
15
17
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
18
|
+
@click.option(
|
|
19
|
+
"--all",
|
|
20
|
+
is_flag=True,
|
|
21
|
+
flag_value=True,
|
|
22
|
+
default=False,
|
|
23
|
+
help="List all studios, not just the ones belonging to the authed user",
|
|
24
|
+
)
|
|
16
25
|
@click.option(
|
|
17
26
|
"--sort-by",
|
|
18
27
|
default=None,
|
|
19
28
|
type=click.Choice(["name", "teamspace", "status", "machine", "cloud-account"], case_sensitive=False),
|
|
20
29
|
help="the attribute to sort the studios by.",
|
|
21
30
|
)
|
|
22
|
-
def list_studios(teamspace: Optional[str] = None, sort_by: Optional[str] = None) -> None:
|
|
31
|
+
def list_studios(teamspace: Optional[str] = None, all: bool = False, sort_by: Optional[str] = None) -> None: # noqa: A002
|
|
23
32
|
"""List Studios in a teamspace.
|
|
24
33
|
|
|
25
34
|
Example:
|
|
26
35
|
lightning studio list --teamspace owner/teamspace
|
|
27
36
|
|
|
28
37
|
"""
|
|
29
|
-
|
|
38
|
+
menu = TeamspacesMenu()
|
|
39
|
+
teamspace_resolved = menu(teamspace=teamspace)
|
|
40
|
+
save_teamspace_to_config(teamspace_resolved, overwrite=False)
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
# TODO: make this a generic CLI error
|
|
33
|
-
raise ValueError(f"Could not resolve teamspace: {teamspace}")
|
|
42
|
+
user = _get_authed_user()
|
|
34
43
|
|
|
35
44
|
studios = teamspace_resolved.studios
|
|
36
45
|
|
|
@@ -43,14 +52,19 @@ def list_studios(teamspace: Optional[str] = None, sort_by: Optional[str] = None)
|
|
|
43
52
|
table.add_column("Machine")
|
|
44
53
|
table.add_column("Cloud account")
|
|
45
54
|
|
|
46
|
-
for studio in sorted(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
for studio in sorted(
|
|
56
|
+
filter(lambda s: all or s._studio.user_id == user.id, studios), key=_sort_studios_key(sort_by)
|
|
57
|
+
):
|
|
58
|
+
with prevent_refetch_studio(studio):
|
|
59
|
+
table.add_row(
|
|
60
|
+
# cannot convert to ascii here, as the final rich table has to be converted to ascii
|
|
61
|
+
# otherwise the lack of support for linking in some terminals causes formatting issues.
|
|
62
|
+
studio_name_link(studio, to_ascii=False),
|
|
63
|
+
f"{studio.teamspace.owner.name}/{studio.teamspace.name}",
|
|
64
|
+
str(studio.status),
|
|
65
|
+
str(studio.machine) if studio.machine is not None else None, # when None the cell is empty
|
|
66
|
+
str(cloud_account_to_display_name(studio.cloud_account, studio.teamspace.id)),
|
|
67
|
+
)
|
|
54
68
|
|
|
55
69
|
click.echo(rich_to_str(table), color=True)
|
|
56
70
|
|
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,23 @@ 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.richt_print import studio_name_link
|
|
8
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
9
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
10
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
11
|
from lightning_sdk.machine import CloudProvider, Machine
|
|
10
12
|
from lightning_sdk.studio import Studio
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@click.command("start")
|
|
14
|
-
@click.
|
|
16
|
+
@click.option(
|
|
17
|
+
"--name",
|
|
18
|
+
help=(
|
|
19
|
+
"The name of the studio to start. "
|
|
20
|
+
"If not provided, will try to infer from environment, "
|
|
21
|
+
"use the default value from the config or prompt for interactive selection."
|
|
22
|
+
),
|
|
23
|
+
)
|
|
15
24
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
16
25
|
@click.option("--create", is_flag=True, help="Create the studio if it doesn't exist")
|
|
17
26
|
@click.option(
|
|
@@ -34,10 +43,10 @@ from lightning_sdk.studio import Studio
|
|
|
34
43
|
type=click.STRING,
|
|
35
44
|
)
|
|
36
45
|
def start_studio(
|
|
37
|
-
|
|
46
|
+
name: Optional[str] = None,
|
|
38
47
|
teamspace: Optional[str] = None,
|
|
39
48
|
create: bool = False,
|
|
40
|
-
machine:
|
|
49
|
+
machine: str = "CPU",
|
|
41
50
|
interruptible: bool = False,
|
|
42
51
|
cloud_provider: Optional[str] = None,
|
|
43
52
|
cloud_account: Optional[str] = None,
|
|
@@ -45,38 +54,29 @@ def start_studio(
|
|
|
45
54
|
"""Start a Studio.
|
|
46
55
|
|
|
47
56
|
Example:
|
|
48
|
-
lightning studio start
|
|
57
|
+
lightning studio start --name my-studio
|
|
49
58
|
|
|
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
59
|
"""
|
|
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
|
|
60
|
+
menu = TeamspacesMenu()
|
|
61
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
63
62
|
|
|
64
63
|
if cloud_provider is not None:
|
|
65
64
|
cloud_provider = CloudProvider(cloud_provider)
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
if not create:
|
|
67
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
68
|
+
studio = menu(studio=name)
|
|
69
|
+
else:
|
|
68
70
|
studio = Studio(
|
|
69
|
-
|
|
71
|
+
name=name,
|
|
70
72
|
teamspace=resolved_teamspace,
|
|
71
73
|
create_ok=create,
|
|
72
74
|
cloud_provider=cloud_provider,
|
|
73
75
|
cloud_account=cloud_account,
|
|
74
76
|
)
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
77
|
+
|
|
78
|
+
save_studio_to_config(studio)
|
|
79
79
|
|
|
80
80
|
Studio.show_progress = True
|
|
81
81
|
studio.start(machine, interruptible=interruptible)
|
|
82
|
-
click.echo(f"Studio
|
|
82
|
+
click.echo(f"Studio {studio_name_link(studio)} started successfully")
|
lightning_sdk/cli/studio/stop.py
CHANGED
|
@@ -4,41 +4,38 @@ 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.richt_print import studio_name_link
|
|
8
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
9
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
10
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@click.command("stop")
|
|
12
|
-
@click.
|
|
14
|
+
@click.option(
|
|
15
|
+
"--name",
|
|
16
|
+
help=(
|
|
17
|
+
"The name of the studio to start. "
|
|
18
|
+
"If not provided, will try to infer from environment, "
|
|
19
|
+
"use the default value from the config or prompt for interactive selection."
|
|
20
|
+
),
|
|
21
|
+
)
|
|
13
22
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
14
|
-
def stop_studio(
|
|
23
|
+
def stop_studio(name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
15
24
|
"""Stop a Studio.
|
|
16
25
|
|
|
17
26
|
Example:
|
|
18
|
-
lightning studio stop
|
|
27
|
+
lightning studio stop --name my-studio
|
|
19
28
|
|
|
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
29
|
"""
|
|
24
30
|
# missing studio_name and teamspace are handled by the studio class
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
click.echo(f"Studio '{studio.name}' stopped successfully")
|
|
31
|
+
menu = TeamspacesMenu()
|
|
32
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
33
|
+
|
|
34
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
35
|
+
studio = menu(studio=name)
|
|
36
|
+
|
|
37
|
+
studio.stop()
|
|
38
|
+
|
|
39
|
+
save_studio_to_config(studio)
|
|
40
|
+
|
|
41
|
+
click.echo(f"Studio {studio_name_link(studio)} stopped successfully")
|
|
@@ -4,14 +4,23 @@ 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.richt_print import studio_name_link
|
|
8
|
+
from lightning_sdk.cli.utils.save_to_config import save_studio_to_config
|
|
9
|
+
from lightning_sdk.cli.utils.studio_selection import StudiosMenu
|
|
10
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
11
|
from lightning_sdk.machine import Machine
|
|
10
12
|
from lightning_sdk.studio import Studio
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@click.command("switch")
|
|
14
|
-
@click.
|
|
16
|
+
@click.option(
|
|
17
|
+
"--name",
|
|
18
|
+
help=(
|
|
19
|
+
"The name of the studio to start. "
|
|
20
|
+
"If not provided, will try to infer from environment, "
|
|
21
|
+
"use the default value from the config or prompt for interactive selection."
|
|
22
|
+
),
|
|
23
|
+
)
|
|
15
24
|
@click.option("--teamspace", help="Override default teamspace (format: owner/teamspace)")
|
|
16
25
|
@click.option(
|
|
17
26
|
"--machine",
|
|
@@ -20,34 +29,22 @@ from lightning_sdk.studio import Studio
|
|
|
20
29
|
)
|
|
21
30
|
@click.option("--interruptible", is_flag=True, help="Switch the studio to an interruptible instance.")
|
|
22
31
|
def switch_studio(
|
|
23
|
-
|
|
32
|
+
name: Optional[str] = None,
|
|
24
33
|
teamspace: Optional[str] = None,
|
|
25
34
|
machine: Optional[str] = None,
|
|
26
35
|
interruptible: bool = False,
|
|
27
36
|
) -> None:
|
|
28
37
|
"""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
|
|
38
|
+
menu = TeamspacesMenu()
|
|
39
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
40
|
+
|
|
41
|
+
menu = StudiosMenu(resolved_teamspace)
|
|
42
|
+
studio = menu(studio=name)
|
|
48
43
|
|
|
49
44
|
resolved_machine = Machine.from_str(machine)
|
|
50
45
|
Studio.show_progress = True
|
|
51
46
|
studio.switch_machine(resolved_machine, interruptible=interruptible)
|
|
52
47
|
|
|
53
|
-
|
|
48
|
+
save_studio_to_config(studio)
|
|
49
|
+
|
|
50
|
+
click.echo(f"Studio {studio_name_link(studio)} 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)
|
|
@@ -3,9 +3,33 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
|
|
6
|
+
from lightning_sdk.studio import Studio
|
|
7
|
+
from lightning_sdk.utils.resolve import _get_studio_url
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
def rich_to_str(*renderables: Any) -> str:
|
|
8
11
|
with open(os.devnull, "w") as f:
|
|
9
12
|
console = Console(file=f, record=True)
|
|
10
13
|
console.print(*renderables)
|
|
11
14
|
return console.export_text(styles=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# not supported on all terminals (e.g. older ones). in that case it's a name without a link
|
|
18
|
+
# see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#hyperlinks-aka-html-like-anchors-in-terminal-emulators
|
|
19
|
+
# for details and
|
|
20
|
+
# https://github.com/Alhadis/OSC8-Adoption/blob/main/README.md for status of OSC8 adoption
|
|
21
|
+
def studio_name_link(studio: Studio, to_ascii: bool = True) -> str:
|
|
22
|
+
"""Hyperlink a studio name.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
studio: the studio whose name to print and link to the studio url
|
|
26
|
+
to_ascii: whether return a plain ascii string with characters for linking converted to ascii as well.
|
|
27
|
+
if False, returns the rich markup directly.
|
|
28
|
+
"""
|
|
29
|
+
url = _get_studio_url(studio)
|
|
30
|
+
|
|
31
|
+
studio_link_markup = f"[link={url}]{studio.name}[/link]"
|
|
32
|
+
if not to_ascii:
|
|
33
|
+
return studio_link_markup
|
|
34
|
+
|
|
35
|
+
return rich_to_str(studio_link_markup).strip("\n")
|
|
@@ -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
|