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.
Files changed (53) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/studio_api.py +69 -2
  3. lightning_sdk/api/teamspace_api.py +60 -30
  4. lightning_sdk/api/user_api.py +49 -1
  5. lightning_sdk/api/utils.py +1 -1
  6. lightning_sdk/cli/config/set.py +6 -18
  7. lightning_sdk/cli/legacy/create.py +3 -3
  8. lightning_sdk/cli/legacy/delete.py +3 -3
  9. lightning_sdk/cli/legacy/deploy/_auth.py +4 -4
  10. lightning_sdk/cli/legacy/download.py +7 -7
  11. lightning_sdk/cli/legacy/job_and_mmt_action.py +4 -4
  12. lightning_sdk/cli/legacy/list.py +9 -9
  13. lightning_sdk/cli/legacy/open.py +3 -3
  14. lightning_sdk/cli/legacy/upload.py +3 -3
  15. lightning_sdk/cli/studio/create.py +14 -23
  16. lightning_sdk/cli/studio/delete.py +28 -27
  17. lightning_sdk/cli/studio/list.py +5 -6
  18. lightning_sdk/cli/studio/ssh.py +19 -22
  19. lightning_sdk/cli/studio/start.py +22 -23
  20. lightning_sdk/cli/studio/stop.py +22 -26
  21. lightning_sdk/cli/studio/switch.py +19 -23
  22. lightning_sdk/cli/utils/resolve.py +1 -1
  23. lightning_sdk/cli/utils/save_to_config.py +27 -0
  24. lightning_sdk/cli/utils/studio_selection.py +106 -0
  25. lightning_sdk/cli/utils/teamspace_selection.py +125 -0
  26. lightning_sdk/lightning_cloud/openapi/__init__.py +2 -0
  27. lightning_sdk/lightning_cloud/openapi/api/billing_service_api.py +85 -0
  28. lightning_sdk/lightning_cloud/openapi/api/k8_s_cluster_service_api.py +101 -0
  29. lightning_sdk/lightning_cloud/openapi/models/__init__.py +2 -0
  30. lightning_sdk/lightning_cloud/openapi/models/externalv1_user_status.py +27 -1
  31. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_metrics.py +270 -36
  32. lightning_sdk/lightning_cloud/openapi/models/v1_container_metrics.py +21 -21
  33. lightning_sdk/lightning_cloud/openapi/models/v1_list_cluster_metric_timestamps_response.py +123 -0
  34. lightning_sdk/lightning_cloud/openapi/models/v1_namespace_metrics.py +11 -11
  35. lightning_sdk/lightning_cloud/openapi/models/v1_namespace_user_metrics.py +16 -16
  36. lightning_sdk/lightning_cloud/openapi/models/v1_node_metrics.py +156 -26
  37. lightning_sdk/lightning_cloud/openapi/models/v1_pod_metrics.py +145 -41
  38. lightning_sdk/lightning_cloud/openapi/models/v1_purchase_annual_upsell_response.py +123 -0
  39. lightning_sdk/lightning_cloud/openapi/models/v1_storage_asset.py +107 -3
  40. lightning_sdk/llm/public_assistants.py +4 -0
  41. lightning_sdk/studio.py +53 -22
  42. lightning_sdk/teamspace.py +25 -2
  43. lightning_sdk/user.py +19 -1
  44. lightning_sdk/utils/config.py +6 -0
  45. lightning_sdk/utils/names.py +1179 -0
  46. lightning_sdk/utils/progress.py +2 -2
  47. lightning_sdk/utils/resolve.py +6 -6
  48. {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/METADATA +1 -1
  49. {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/RECORD +53 -47
  50. {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/LICENSE +0 -0
  51. {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/WHEEL +0 -0
  52. {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/entry_points.txt +0 -0
  53. {lightning_sdk-2025.8.21.dist-info → lightning_sdk-2025.8.26.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py CHANGED
@@ -32,6 +32,6 @@ __all__ = [
32
32
  "User",
33
33
  ]
34
34
 
35
- __version__ = "2025.08.21"
35
+ __version__ = "2025.08.26"
36
36
  _check_version_and_prompt_upgrade(__version__)
37
37
  _set_tqdm_envvars_noninteractive()
@@ -11,7 +11,7 @@ from tqdm import tqdm
11
11
 
12
12
  from lightning_sdk.api.utils import (
13
13
  _create_app,
14
- _download_studio_files,
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
- _download_studio_files(
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 tempfile
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
- query_params = {
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
- r = requests.get(
433
- f"{self._client.api_client.configuration.host}/v1/projects/{teamspace_id}/artifacts/download",
434
- params=query_params,
435
- stream=True,
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
- if progress_bar:
439
- pbar = tqdm(
440
- desc=f"Downloading {os.path.split(path)[1]}",
441
- unit="B",
442
- unit_scale=True,
443
- unit_divisor=1000,
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
- pbar_update = pbar.update
447
- else:
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 target_path:
451
- os.makedirs(target_path, exist_ok=True)
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
- with tempfile.TemporaryFile() as f:
454
- for chunk in r.iter_content(chunk_size=4096 * 8):
455
- f.write(chunk)
456
- pbar_update(len(chunk))
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
- with zipfile.ZipFile(f) as z:
459
- z.extractall(target_path)
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
@@ -1,15 +1,20 @@
1
- from typing import List, Union
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
@@ -586,7 +586,7 @@ def _download_model_files(
586
586
  return response.filepaths
587
587
 
588
588
 
589
- def _download_studio_files(
589
+ def _download_teamspace_files(
590
590
  client: LightningClient,
591
591
  teamspace_id: str,
592
592
  cluster_id: str,
@@ -1,8 +1,8 @@
1
1
  import click
2
2
 
3
- from lightning_sdk.cli.utils.resolve import resolve_teamspace_owner_name_format
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
- config = Config()
63
-
64
- teamspace_resolved = resolve_teamspace_owner_name_format(teamspace_name)
62
+ menu = TeamspacesMenu()
63
+ teamspace_resolved = menu(teamspace=teamspace_name)
65
64
 
66
- if teamspace_resolved is None:
67
- # TODO: make this a generic CLI error
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.legacy.teamspace_menu import _TeamspacesMenu
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 = _TeamspacesMenu()
82
- teamspace_resolved = menu._resolve_teamspace(teamspace)
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.legacy.teamspace_menu import _TeamspacesMenu
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 = _TeamspacesMenu()
34
- resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
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.legacy.teamspace_menu import _TeamspacesMenu
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 = _TeamspacesMenu()
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._resolve_teamspace(teamspace)
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 = _TeamspacesMenu()
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.legacy.teamspace_menu import _TeamspacesMenu
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 = _TeamspacesMenu()
114
- resolved_downloader = menu._resolve_teamspace(teamspace)
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 = _TeamspacesMenu()
177
- resolved_downloader = menu._resolve_teamspace(teamspace)
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 = _TeamspacesMenu()
218
- resolved_teamspace = menu._resolve_teamspace(teamspace)
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.legacy.teamspace_menu import _TeamspacesMenu
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(_TeamspacesMenu, _JobsMenu, _MMTsMenu):
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._resolve_teamspace(teamspace)
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._resolve_teamspace(teamspace)
36
+ resolved_teamspace = self(teamspace)
37
37
  return self._resolve_mmt(name, teamspace=resolved_teamspace)
@@ -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.legacy.teamspace_menu import _TeamspacesMenu
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 = _TeamspacesMenu()
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._resolve_teamspace(teamspace=teamspace)
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 = _TeamspacesMenu()
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._resolve_teamspace(teamspace=teamspace)
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 = _TeamspacesMenu()
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._resolve_teamspace(teamspace=teamspace)
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 = _TeamspacesMenu()
245
+ menu = TeamspacesMenu()
246
246
  clusters_menu = _ClustersMenu()
247
- resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
247
+ resolved_teamspace = menu(teamspace=teamspace)
248
248
 
249
249
  if not cloud_account:
250
250
  cloud_account = clusters_menu._resolve_cluster(resolved_teamspace)
@@ -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 = _TeamspacesMenu()
53
- resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
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.legacy.teamspace_menu import _TeamspacesMenu
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 = _TeamspacesMenu()
141
- teamspace = menu._resolve_teamspace(teamspace)
140
+ menu = TeamspacesMenu()
141
+ teamspace = menu(teamspace)
142
142
  console = Console()
143
143
  with Progress(
144
144
  SpinnerColumn(),