lightning-sdk 2025.8.21__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/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 +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 +22 -23
- lightning_sdk/cli/studio/stop.py +22 -26
- lightning_sdk/cli/studio/switch.py +19 -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 +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 +53 -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 +6 -6
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/METADATA +1 -1
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/RECORD +53 -47
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/LICENSE +0 -0
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/WHEEL +0 -0
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
lightning_sdk/api/studio_api.py
CHANGED
|
@@ -11,7 +11,7 @@ from tqdm import tqdm
|
|
|
11
11
|
|
|
12
12
|
from lightning_sdk.api.utils import (
|
|
13
13
|
_create_app,
|
|
14
|
-
|
|
14
|
+
_download_teamspace_files,
|
|
15
15
|
_DummyBody,
|
|
16
16
|
_DummyResponse,
|
|
17
17
|
_FileUploader,
|
|
@@ -25,6 +25,7 @@ from lightning_sdk.constants import _LIGHTNING_DEBUG
|
|
|
25
25
|
from lightning_sdk.lightning_cloud.login import Auth
|
|
26
26
|
from lightning_sdk.lightning_cloud.openapi import (
|
|
27
27
|
CloudspaceIdRunsBody,
|
|
28
|
+
CloudspacesIdBody,
|
|
28
29
|
Externalv1LightningappInstance,
|
|
29
30
|
IdCodeconfigBody,
|
|
30
31
|
IdExecuteBody1,
|
|
@@ -40,6 +41,7 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
40
41
|
V1CloudSpaceState,
|
|
41
42
|
V1ClusterAccelerator,
|
|
42
43
|
V1EndpointType,
|
|
44
|
+
V1EnvVar,
|
|
43
45
|
V1GetCloudSpaceInstanceStatusResponse,
|
|
44
46
|
V1GetLongRunningCommandInCloudSpaceResponse,
|
|
45
47
|
V1LoginRequest,
|
|
@@ -677,7 +679,7 @@ class StudioApi:
|
|
|
677
679
|
if prefix.endswith("/") is False:
|
|
678
680
|
prefix = prefix + "/"
|
|
679
681
|
|
|
680
|
-
|
|
682
|
+
_download_teamspace_files(
|
|
681
683
|
client=self._client,
|
|
682
684
|
teamspace_id=teamspace_id,
|
|
683
685
|
cluster_id=cloud_account,
|
|
@@ -940,3 +942,68 @@ class StudioApi:
|
|
|
940
942
|
plugin_type=plugin_type,
|
|
941
943
|
**other_arguments,
|
|
942
944
|
)
|
|
945
|
+
|
|
946
|
+
def _update_cloudspace(self, studio: V1CloudSpace, teamspace_id: str, key: str, value: Any) -> None:
|
|
947
|
+
body = CloudspacesIdBody(
|
|
948
|
+
code_url=studio.code_url,
|
|
949
|
+
data_connection_mounts=studio.data_connection_mounts,
|
|
950
|
+
description=studio.description,
|
|
951
|
+
display_name=studio.display_name,
|
|
952
|
+
env=studio.env,
|
|
953
|
+
featured=studio.featured,
|
|
954
|
+
hide_files=studio.hide_files,
|
|
955
|
+
is_cloudspace_private=studio.is_cloudspace_private,
|
|
956
|
+
is_code_private=studio.is_code_private,
|
|
957
|
+
is_favorite=studio.is_favorite,
|
|
958
|
+
is_published=studio.is_published,
|
|
959
|
+
license=studio.license,
|
|
960
|
+
license_url=studio.license_url,
|
|
961
|
+
message=studio.message,
|
|
962
|
+
multi_user_edit=studio.multi_user_edit,
|
|
963
|
+
operating_cost=studio.operating_cost,
|
|
964
|
+
paper_authors=studio.paper_authors,
|
|
965
|
+
paper_org=studio.paper_org,
|
|
966
|
+
paper_org_avatar_url=studio.paper_org_avatar_url,
|
|
967
|
+
paper_url=studio.paper_url,
|
|
968
|
+
switch_to_default_machine_on_idle=studio.switch_to_default_machine_on_idle,
|
|
969
|
+
tags=studio.tags,
|
|
970
|
+
thumbnail_file_type=studio.thumbnail_file_type,
|
|
971
|
+
user_metadata=studio.user_metadata,
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
setattr(body, key, value)
|
|
975
|
+
|
|
976
|
+
self._client.cloud_space_service_update_cloud_space(
|
|
977
|
+
id=studio.id,
|
|
978
|
+
project_id=teamspace_id,
|
|
979
|
+
body=body,
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
def set_env(
|
|
983
|
+
self,
|
|
984
|
+
studio: V1CloudSpace,
|
|
985
|
+
teamspace_id: str,
|
|
986
|
+
new_env: Dict[str, str],
|
|
987
|
+
partial: bool = True,
|
|
988
|
+
) -> None:
|
|
989
|
+
"""Set the environment variables for the Studio.
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
new_env: The new environment variables to set.
|
|
993
|
+
partial: Whether to only set the environment variables that are provided.
|
|
994
|
+
If False, existing environment variables that are not in new_env will be removed.
|
|
995
|
+
If True, existing environment variables that are not in new_env will be kept.
|
|
996
|
+
"""
|
|
997
|
+
updated_env_dict = {}
|
|
998
|
+
if partial:
|
|
999
|
+
updated_env_dict = {env.name: env.value for env in studio.env}
|
|
1000
|
+
updated_env_dict.update(new_env)
|
|
1001
|
+
else:
|
|
1002
|
+
updated_env_dict = new_env
|
|
1003
|
+
|
|
1004
|
+
updated_env = [V1EnvVar(name=key, value=value) for key, value in updated_env_dict.items()]
|
|
1005
|
+
|
|
1006
|
+
self._update_cloudspace(studio, teamspace_id, "env", updated_env)
|
|
1007
|
+
|
|
1008
|
+
def get_env(self, studio: V1CloudSpace) -> Dict[str, str]:
|
|
1009
|
+
return {env.name: env.value for env in studio.env}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import
|
|
3
|
-
import zipfile
|
|
2
|
+
import re
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from typing import Dict, List, Optional, Tuple
|
|
6
5
|
|
|
@@ -9,6 +8,7 @@ from tqdm.auto import tqdm
|
|
|
9
8
|
|
|
10
9
|
from lightning_sdk.api.utils import (
|
|
11
10
|
_download_model_files,
|
|
11
|
+
_download_teamspace_files,
|
|
12
12
|
_DummyBody,
|
|
13
13
|
_FileUploader,
|
|
14
14
|
_get_model_version,
|
|
@@ -22,6 +22,8 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
22
22
|
ModelsStoreApi,
|
|
23
23
|
ProjectIdAgentsBody,
|
|
24
24
|
ProjectIdModelsBody,
|
|
25
|
+
ProjectIdSecretsBody,
|
|
26
|
+
SecretsIdBody,
|
|
25
27
|
V1Assistant,
|
|
26
28
|
V1CloudSpace,
|
|
27
29
|
V1ClusterAccelerator,
|
|
@@ -34,6 +36,8 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
34
36
|
V1Project,
|
|
35
37
|
V1ProjectClusterBinding,
|
|
36
38
|
V1PromptSuggestion,
|
|
39
|
+
V1Secret,
|
|
40
|
+
V1SecretType,
|
|
37
41
|
V1UpstreamOpenAI,
|
|
38
42
|
)
|
|
39
43
|
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
@@ -421,39 +425,65 @@ class TeamspaceApi:
|
|
|
421
425
|
# TODO: Update this endpoint to permit basic auth
|
|
422
426
|
auth = Auth()
|
|
423
427
|
auth.authenticate()
|
|
424
|
-
token = self._client.auth_service_login(V1LoginRequest(auth.api_key)).token
|
|
425
428
|
|
|
426
|
-
|
|
427
|
-
"clusterId": cloud_account,
|
|
428
|
-
"prefix": _resolve_teamspace_remote_path(path),
|
|
429
|
-
"token": token,
|
|
430
|
-
}
|
|
429
|
+
prefix = _resolve_teamspace_remote_path(path)
|
|
431
430
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
431
|
+
# ensure we only download as a directory and not the entire prefix
|
|
432
|
+
if prefix.endswith("/") is False:
|
|
433
|
+
prefix = prefix + "/"
|
|
434
|
+
|
|
435
|
+
_download_teamspace_files(
|
|
436
|
+
client=self._client,
|
|
437
|
+
teamspace_id=teamspace_id,
|
|
438
|
+
cluster_id=cloud_account,
|
|
439
|
+
prefix=prefix,
|
|
440
|
+
download_dir=Path(target_path),
|
|
441
|
+
progress_bar=progress_bar,
|
|
436
442
|
)
|
|
437
443
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
444
|
+
def get_secrets(self, teamspace_id: str) -> Dict[str, str]:
|
|
445
|
+
"""Get all secrets for a teamspace."""
|
|
446
|
+
secrets = self._get_secrets(teamspace_id)
|
|
447
|
+
# this returns encrypted values for security. It doesn't make sense to show them,
|
|
448
|
+
# so we just return a placeholder
|
|
449
|
+
# not a security issue to replace in the client as we get the encrypted values from the server.
|
|
450
|
+
return {secret.name: "***REDACTED***" for secret in secrets if secret.type == V1SecretType.UNSPECIFIED}
|
|
445
451
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
pbar_update = lambda x: None
|
|
452
|
+
def set_secret(self, teamspace_id: str, key: str, value: str) -> None:
|
|
453
|
+
"""Set a secret for a teamspace.
|
|
449
454
|
|
|
450
|
-
if
|
|
451
|
-
|
|
455
|
+
This will replace the existing secret if it exists and create a new one if it doesn't.
|
|
456
|
+
"""
|
|
457
|
+
secrets = self._get_secrets(teamspace_id)
|
|
458
|
+
for secret in secrets:
|
|
459
|
+
if secret.name == key:
|
|
460
|
+
return self._update_secret(teamspace_id, secret.id, value)
|
|
461
|
+
return self._create_secret(teamspace_id, key, value)
|
|
462
|
+
|
|
463
|
+
def _get_secrets(self, teamspace_id: str) -> List[V1Secret]:
|
|
464
|
+
return self._client.secret_service_list_secrets(project_id=teamspace_id).secrets
|
|
465
|
+
|
|
466
|
+
def _update_secret(self, teamspace_id: str, secret_id: str, value: str) -> None:
|
|
467
|
+
self._client.secret_service_update_secret(
|
|
468
|
+
body=SecretsIdBody(value=value),
|
|
469
|
+
project_id=teamspace_id,
|
|
470
|
+
id=secret_id,
|
|
471
|
+
)
|
|
452
472
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
473
|
+
def _create_secret(
|
|
474
|
+
self,
|
|
475
|
+
teamspace_id: str,
|
|
476
|
+
key: str,
|
|
477
|
+
value: str,
|
|
478
|
+
) -> None:
|
|
479
|
+
self._client.secret_service_create_secret(
|
|
480
|
+
body=ProjectIdSecretsBody(name=key, value=value, type=V1SecretType.UNSPECIFIED), project_id=teamspace_id
|
|
481
|
+
)
|
|
457
482
|
|
|
458
|
-
|
|
459
|
-
|
|
483
|
+
def verify_secret_name(self, name: str) -> bool:
|
|
484
|
+
"""Verify if a secret name is valid.
|
|
485
|
+
|
|
486
|
+
A valid secret name starts with a letter or underscore, followed by letters, digits, or underscores.
|
|
487
|
+
"""
|
|
488
|
+
pattern = r"^[A-Za-z_][A-Za-z0-9_]*$"
|
|
489
|
+
return re.match(pattern, name) is not None
|
lightning_sdk/api/user_api.py
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List, Union
|
|
2
3
|
|
|
3
4
|
from lightning_sdk.lightning_cloud.login import Auth
|
|
4
5
|
from lightning_sdk.lightning_cloud.openapi import (
|
|
6
|
+
SecretsIdBody1,
|
|
5
7
|
V1CloudSpace,
|
|
8
|
+
V1CreateUserSecretRequest,
|
|
6
9
|
V1GetUserResponse,
|
|
7
10
|
V1ListCloudSpacesResponse,
|
|
8
11
|
V1Membership,
|
|
9
12
|
V1Organization,
|
|
10
13
|
V1SearchUser,
|
|
14
|
+
V1Secret,
|
|
11
15
|
V1UserFeatures,
|
|
12
16
|
)
|
|
17
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_secret_type import V1SecretType
|
|
13
18
|
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
14
19
|
|
|
15
20
|
|
|
@@ -69,3 +74,46 @@ class UserApi:
|
|
|
69
74
|
def _get_feature_flags(self) -> V1UserFeatures:
|
|
70
75
|
resp: V1GetUserResponse = self._client.auth_service_get_user()
|
|
71
76
|
return resp.features
|
|
77
|
+
|
|
78
|
+
def get_secrets(self) -> Dict[str, str]:
|
|
79
|
+
"""Get all secrets for the current user."""
|
|
80
|
+
secrets = self._get_secrets()
|
|
81
|
+
# this returns encrypted values for security. It doesn't make sense to show them,
|
|
82
|
+
# so we just return a placeholder
|
|
83
|
+
# not a security issue to replace in the client as we get the encrypted values from the server.
|
|
84
|
+
return {secret.name: "***REDACTED***" for secret in secrets if secret.type == V1SecretType.UNSPECIFIED}
|
|
85
|
+
|
|
86
|
+
def set_secret(self, key: str, value: str) -> None:
|
|
87
|
+
"""Set a secret for the current user.
|
|
88
|
+
|
|
89
|
+
This will replace the existing secret if it exists and create a new one if it doesn't.
|
|
90
|
+
"""
|
|
91
|
+
secrets = self._get_secrets()
|
|
92
|
+
for secret in secrets:
|
|
93
|
+
if secret.name == key:
|
|
94
|
+
return self._update_secret(secret.id, value)
|
|
95
|
+
return self._create_secret(key, value)
|
|
96
|
+
|
|
97
|
+
def _get_secrets(self) -> List[V1Secret]:
|
|
98
|
+
return self._client.secret_service_list_user_secrets().secrets
|
|
99
|
+
|
|
100
|
+
def _update_secret(self, secret_id: str, value: str) -> None:
|
|
101
|
+
self._client.secret_service_update_user_secret(
|
|
102
|
+
body=SecretsIdBody1(value=value),
|
|
103
|
+
id=secret_id,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _create_secret(
|
|
107
|
+
self,
|
|
108
|
+
key: str,
|
|
109
|
+
value: str,
|
|
110
|
+
) -> None:
|
|
111
|
+
self._client.secret_service_create_user_secret(body=V1CreateUserSecretRequest(name=key, value=value))
|
|
112
|
+
|
|
113
|
+
def verify_secret_name(self, name: str) -> bool:
|
|
114
|
+
"""Verify if a secret name is valid.
|
|
115
|
+
|
|
116
|
+
A valid secret name starts with a letter or underscore, followed by letters, digits, or underscores.
|
|
117
|
+
"""
|
|
118
|
+
pattern = r"^[A-Za-z_][A-Za-z0-9_]*$"
|
|
119
|
+
return re.match(pattern, name) is not None
|
lightning_sdk/api/utils.py
CHANGED
lightning_sdk/cli/config/set.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
-
from lightning_sdk.cli.utils.
|
|
3
|
+
from lightning_sdk.cli.utils.save_to_config import save_teamspace_to_config
|
|
4
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
4
5
|
from lightning_sdk.machine import CloudProvider
|
|
5
|
-
from lightning_sdk.organization import Organization
|
|
6
6
|
from lightning_sdk.studio import Studio
|
|
7
7
|
from lightning_sdk.utils.config import Config, DefaultConfigKeys
|
|
8
8
|
from lightning_sdk.utils.resolve import _resolve_org, _resolve_user
|
|
@@ -59,23 +59,11 @@ def set_studio(studio_name: str) -> None:
|
|
|
59
59
|
@click.argument("teamspace_name")
|
|
60
60
|
def set_teamspace(teamspace_name: str) -> None:
|
|
61
61
|
"""Set the default teamspace name in the config."""
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
teamspace_resolved = resolve_teamspace_owner_name_format(teamspace_name)
|
|
62
|
+
menu = TeamspacesMenu()
|
|
63
|
+
teamspace_resolved = menu(teamspace=teamspace_name)
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
raise ValueError(
|
|
69
|
-
f"Could not resolve teamspace: '{teamspace_name}'. "
|
|
70
|
-
"Teamspace should be specified as 'owner/name'. Does the teamspace exist?"
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
setattr(config, DefaultConfigKeys.teamspace_name, teamspace_resolved.name)
|
|
74
|
-
setattr(config, DefaultConfigKeys.teamspace_owner, teamspace_resolved.owner.name)
|
|
75
|
-
if isinstance(teamspace_resolved.owner, Organization):
|
|
76
|
-
setattr(config, DefaultConfigKeys.teamspace_owner_type, "organization")
|
|
77
|
-
else:
|
|
78
|
-
setattr(config, DefaultConfigKeys.teamspace_owner_type, "user")
|
|
65
|
+
# explicit user action, so overwrite the config
|
|
66
|
+
save_teamspace_to_config(teamspace_resolved, overwrite=True)
|
|
79
67
|
|
|
80
68
|
|
|
81
69
|
@set_value.command("cloud-account")
|
|
@@ -7,7 +7,7 @@ import click
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
9
|
from lightning_sdk import Machine, Studio
|
|
10
|
-
from lightning_sdk.cli.
|
|
10
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
11
11
|
from lightning_sdk.machine import CloudProvider
|
|
12
12
|
|
|
13
13
|
_MACHINE_VALUES = tuple(
|
|
@@ -78,8 +78,8 @@ def studio(
|
|
|
78
78
|
|
|
79
79
|
NAME: the name of the studio to create. If already present within teamspace, will add a random suffix.
|
|
80
80
|
"""
|
|
81
|
-
menu =
|
|
82
|
-
teamspace_resolved = menu
|
|
81
|
+
menu = TeamspacesMenu()
|
|
82
|
+
teamspace_resolved = menu(teamspace)
|
|
83
83
|
|
|
84
84
|
# default cloud account to current studios cloud account if run from studio
|
|
85
85
|
# else it will fall back to teamspace default in the backend
|
|
@@ -5,7 +5,7 @@ from rich.console import Console
|
|
|
5
5
|
|
|
6
6
|
from lightning_sdk.cli.legacy.exceptions import StudioCliError
|
|
7
7
|
from lightning_sdk.cli.legacy.job_and_mmt_action import _JobAndMMTAction
|
|
8
|
-
from lightning_sdk.cli.
|
|
8
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
9
9
|
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
10
10
|
from lightning_sdk.lit_container import LitContainer
|
|
11
11
|
from lightning_sdk.studio import Studio
|
|
@@ -30,8 +30,8 @@ def delete() -> None:
|
|
|
30
30
|
def container(name: str, teamspace: Optional[str] = None) -> None:
|
|
31
31
|
"""Delete the docker container NAME."""
|
|
32
32
|
api = LitContainer()
|
|
33
|
-
menu =
|
|
34
|
-
resolved_teamspace = menu
|
|
33
|
+
menu = TeamspacesMenu()
|
|
34
|
+
resolved_teamspace = menu(teamspace=teamspace)
|
|
35
35
|
try:
|
|
36
36
|
api.delete_container(name, resolved_teamspace.name, resolved_teamspace.owner.name)
|
|
37
37
|
Console().print(f"Container {name} deleted successfully.")
|
|
@@ -10,7 +10,7 @@ from rich.prompt import Confirm
|
|
|
10
10
|
|
|
11
11
|
from lightning_sdk import Teamspace
|
|
12
12
|
from lightning_sdk.api import UserApi
|
|
13
|
-
from lightning_sdk.cli.
|
|
13
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
14
14
|
from lightning_sdk.lightning_cloud import env
|
|
15
15
|
from lightning_sdk.lightning_cloud.login import Auth, AuthServer
|
|
16
16
|
from lightning_sdk.lightning_cloud.openapi import V1CloudSpace
|
|
@@ -74,13 +74,13 @@ def authenticate(mode: _AuthMode, shall_confirm: bool = True) -> None:
|
|
|
74
74
|
def select_teamspace(teamspace: Optional[str], org: Optional[str], user: Optional[str]) -> Teamspace:
|
|
75
75
|
if teamspace is None:
|
|
76
76
|
user = _get_authed_user()
|
|
77
|
-
menu =
|
|
77
|
+
menu = TeamspacesMenu()
|
|
78
78
|
possible_teamspaces = menu._get_possible_teamspaces(user)
|
|
79
79
|
if len(possible_teamspaces) == 1:
|
|
80
80
|
name = next(iter(possible_teamspaces.values()))["name"]
|
|
81
81
|
return Teamspace(name=name, org=org, user=user)
|
|
82
82
|
|
|
83
|
-
return menu
|
|
83
|
+
return menu(teamspace)
|
|
84
84
|
|
|
85
85
|
return _resolve_teamspace(teamspace=teamspace, org=org, user=user)
|
|
86
86
|
|
|
@@ -180,7 +180,7 @@ class _Onboarding:
|
|
|
180
180
|
return select_teamspace(teamspace, org, user)
|
|
181
181
|
|
|
182
182
|
# Run only when user hasn't completed onboarding yet.
|
|
183
|
-
menu =
|
|
183
|
+
menu = TeamspacesMenu()
|
|
184
184
|
self._wait_user_onboarding()
|
|
185
185
|
# Onboarding has been completed - user already selected organization if they could
|
|
186
186
|
possible_teamspaces = menu._get_possible_teamspaces(self.user)
|
|
@@ -11,7 +11,7 @@ from lightning_sdk.api.license_api import LicenseApi
|
|
|
11
11
|
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
12
12
|
from lightning_sdk.cli.legacy.exceptions import StudioCliError
|
|
13
13
|
from lightning_sdk.cli.legacy.studios_menu import _StudiosMenu
|
|
14
|
-
from lightning_sdk.cli.
|
|
14
|
+
from lightning_sdk.cli.utils.teamspace_selection import TeamspacesMenu
|
|
15
15
|
from lightning_sdk.models import download_model
|
|
16
16
|
from lightning_sdk.studio import Studio
|
|
17
17
|
from lightning_sdk.utils.resolve import _get_authed_user, skip_studio_init
|
|
@@ -110,8 +110,8 @@ def folder(
|
|
|
110
110
|
path = _expand_remote_path(path)
|
|
111
111
|
resolved_downloader = _resolve_studio(studio)
|
|
112
112
|
elif teamspace:
|
|
113
|
-
menu =
|
|
114
|
-
resolved_downloader = menu
|
|
113
|
+
menu = TeamspacesMenu()
|
|
114
|
+
resolved_downloader = menu(teamspace)
|
|
115
115
|
else:
|
|
116
116
|
raise ValueError("Either --studio or --teamspace must be provided")
|
|
117
117
|
|
|
@@ -173,8 +173,8 @@ def file(path: str = "", studio: Optional[str] = None, teamspace: Optional[str]
|
|
|
173
173
|
if studio:
|
|
174
174
|
resolved_downloader = _resolve_studio(studio)
|
|
175
175
|
elif teamspace:
|
|
176
|
-
menu =
|
|
177
|
-
resolved_downloader = menu
|
|
176
|
+
menu = TeamspacesMenu()
|
|
177
|
+
resolved_downloader = menu(teamspace)
|
|
178
178
|
else:
|
|
179
179
|
raise ValueError("Either --studio or --teamspace must be provided")
|
|
180
180
|
|
|
@@ -214,8 +214,8 @@ def download_container(
|
|
|
214
214
|
CONTAINER: The name of the container to download.
|
|
215
215
|
"""
|
|
216
216
|
console = Console()
|
|
217
|
-
menu =
|
|
218
|
-
resolved_teamspace = menu
|
|
217
|
+
menu = TeamspacesMenu()
|
|
218
|
+
resolved_teamspace = menu(teamspace)
|
|
219
219
|
with console.status("Downloading container..."):
|
|
220
220
|
api = LitContainerApi()
|
|
221
221
|
api.download_container(container, resolved_teamspace, tag, cloud_account)
|
|
@@ -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(),
|