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/studio.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import glob
|
|
2
2
|
import os
|
|
3
|
+
import threading
|
|
3
4
|
import warnings
|
|
4
5
|
from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Tuple, Union
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ from lightning_sdk.owner import Owner
|
|
|
14
15
|
from lightning_sdk.status import Status
|
|
15
16
|
from lightning_sdk.teamspace import Teamspace
|
|
16
17
|
from lightning_sdk.user import User
|
|
18
|
+
from lightning_sdk.utils.names import random_unique_name
|
|
17
19
|
from lightning_sdk.utils.resolve import (
|
|
18
20
|
_get_org_id,
|
|
19
21
|
_resolve_deprecated_cluster,
|
|
@@ -56,8 +58,8 @@ class Studio:
|
|
|
56
58
|
"""
|
|
57
59
|
|
|
58
60
|
# skips init of studio, only set when using this as a shell for names, ids etc.
|
|
59
|
-
_skip_init =
|
|
60
|
-
_skip_setup =
|
|
61
|
+
_skip_init = threading.local()
|
|
62
|
+
_skip_setup = threading.local()
|
|
61
63
|
|
|
62
64
|
# whether to show progress bars during operations
|
|
63
65
|
show_progress = False
|
|
@@ -79,64 +81,77 @@ class Studio:
|
|
|
79
81
|
self._studio_api = StudioApi()
|
|
80
82
|
self._cloud_account_api = CloudAccountApi()
|
|
81
83
|
|
|
82
|
-
_teamspace =
|
|
83
|
-
if _teamspace is None:
|
|
84
|
-
raise ValueError("Couldn't resolve teamspace from the provided name, org, or user")
|
|
84
|
+
self._teamspace = None
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
# don't resolve anything if we're skipping init
|
|
87
|
+
if not getattr(self._skip_init, "value", False):
|
|
88
|
+
_teamspace = _resolve_teamspace(teamspace=teamspace, org=org, user=user)
|
|
89
|
+
if _teamspace is None:
|
|
90
|
+
raise ValueError("Couldn't resolve teamspace from the provided name, org, or user")
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
self._teamspace = _teamspace
|
|
93
|
+
|
|
94
|
+
self._setup_done = getattr(self._skip_setup, "value", False)
|
|
89
95
|
self._disable_secrets = disable_secrets
|
|
90
96
|
|
|
91
97
|
self._plugins = {}
|
|
98
|
+
self._studio = None
|
|
92
99
|
|
|
93
100
|
cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
|
|
94
101
|
cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
# if we're skipping init, we don't need to resolve the cloud account as then we're not creating a studio
|
|
104
|
+
if self._teamspace is not None:
|
|
105
|
+
_cloud_account = self._cloud_account_api.resolve_cloud_account(
|
|
106
|
+
self._teamspace.id,
|
|
107
|
+
cloud_account=cloud_account,
|
|
108
|
+
cloud_provider=cloud_provider,
|
|
109
|
+
default_cloud_account=self._teamspace.default_cloud_account,
|
|
110
|
+
)
|
|
102
111
|
|
|
103
112
|
# Resolve studio name if not provided: explicit → env (LIGHTNING_CLOUD_SPACE_ID) → config defaults
|
|
104
|
-
if name is None:
|
|
113
|
+
if name is None and not getattr(self._skip_init, "value", False):
|
|
105
114
|
studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
|
|
106
115
|
if studio_id is not None:
|
|
107
116
|
# We're inside a studio, get it by ID
|
|
108
117
|
self._studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
|
|
118
|
+
name = self._studio.name
|
|
109
119
|
else:
|
|
110
120
|
# Try config defaults
|
|
111
121
|
from lightning_sdk.utils.config import Config, DefaultConfigKeys
|
|
112
122
|
|
|
113
123
|
config = Config()
|
|
114
124
|
name = config.get_value(DefaultConfigKeys.studio)
|
|
115
|
-
if name is None:
|
|
125
|
+
if name is None and not create_ok:
|
|
116
126
|
raise ValueError(
|
|
117
127
|
"Cannot autodetect Studio. Either use the SDK from within a Studio or pass a name!"
|
|
118
128
|
)
|
|
119
129
|
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
if self._studio is None and not getattr(self._skip_init, "value", False):
|
|
131
|
+
# If we have a name (explicit or from config), get studio by name
|
|
122
132
|
try:
|
|
133
|
+
if name is None:
|
|
134
|
+
# if we don't have a name, raise an error to get
|
|
135
|
+
# to the exception path and optionally create a studio
|
|
136
|
+
raise ValueError(
|
|
137
|
+
"Cannot autodetect Studio. Either use the SDK from within a Studio or pass a name!"
|
|
138
|
+
)
|
|
123
139
|
self._studio = self._studio_api.get_studio(name, self._teamspace.id)
|
|
124
140
|
except ValueError as e:
|
|
125
141
|
if create_ok:
|
|
142
|
+
name = name or random_unique_name()
|
|
126
143
|
self._studio = self._studio_api.create_studio(
|
|
127
144
|
name,
|
|
128
145
|
self._teamspace.id,
|
|
129
|
-
cloud_account=
|
|
146
|
+
cloud_account=_cloud_account,
|
|
130
147
|
source=source,
|
|
131
148
|
disable_secrets=self._disable_secrets,
|
|
132
149
|
)
|
|
133
150
|
else:
|
|
134
|
-
raise
|
|
135
|
-
|
|
136
|
-
self._cloud_account = self._studio.cluster_id
|
|
151
|
+
raise e
|
|
137
152
|
|
|
138
153
|
if (
|
|
139
|
-
not self._skip_init
|
|
154
|
+
not getattr(self._skip_init, "value", False)
|
|
140
155
|
and _internal_status_to_external_status(
|
|
141
156
|
self._studio_api._get_studio_instance_status_from_object(self._studio)
|
|
142
157
|
)
|
|
@@ -608,6 +623,22 @@ class Studio:
|
|
|
608
623
|
warnings.warn("auto_shutdown_time is deprecated. Use auto_sleep_time instead", DeprecationWarning)
|
|
609
624
|
self.auto_sleep_time = value
|
|
610
625
|
|
|
626
|
+
@property
|
|
627
|
+
def env(self) -> Dict[str, str]:
|
|
628
|
+
self._update_studio_reference()
|
|
629
|
+
return self._studio_api.get_env(self._studio)
|
|
630
|
+
|
|
631
|
+
def set_env(self, new_env: Dict[str, str], partial: bool = True) -> None:
|
|
632
|
+
"""Set the environment variables for the Studio.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
new_env: The new environment variables to set.
|
|
636
|
+
partial: Whether to only set the environment variables that are provided.
|
|
637
|
+
If False, existing environment variables that are not in new_env will be removed.
|
|
638
|
+
If True, existing environment variables that are not in new_env will be kept.
|
|
639
|
+
"""
|
|
640
|
+
self._studio_api.set_env(self._studio, self._teamspace.id, new_env, partial=partial)
|
|
641
|
+
|
|
611
642
|
@property
|
|
612
643
|
def available_plugins(self) -> Mapping[str, str]:
|
|
613
644
|
"""All available plugins to install in the current Studio."""
|
lightning_sdk/teamspace.py
CHANGED
|
@@ -2,7 +2,7 @@ import glob
|
|
|
2
2
|
import os
|
|
3
3
|
import warnings
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
|
6
6
|
|
|
7
7
|
from tqdm.auto import tqdm
|
|
8
8
|
|
|
@@ -21,6 +21,7 @@ from lightning_sdk.utils.resolve import (
|
|
|
21
21
|
_resolve_org,
|
|
22
22
|
_resolve_teamspace_name,
|
|
23
23
|
_resolve_user,
|
|
24
|
+
skip_studio_init,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
if TYPE_CHECKING:
|
|
@@ -125,7 +126,11 @@ class Teamspace:
|
|
|
125
126
|
for cl in cloud_accounts:
|
|
126
127
|
_studios = self._teamspace_api.list_studios(teamspace_id=self.id, cloud_account=cl.cluster_id)
|
|
127
128
|
for s in _studios:
|
|
128
|
-
|
|
129
|
+
with skip_studio_init():
|
|
130
|
+
studio = Studio(name=s.name, teamspace=self, cluster=cl.cluster_name, create_ok=False)
|
|
131
|
+
studio._studio = s
|
|
132
|
+
studio._teamspace = self
|
|
133
|
+
studios.append(studio)
|
|
129
134
|
|
|
130
135
|
return studios
|
|
131
136
|
|
|
@@ -209,6 +214,24 @@ class Teamspace:
|
|
|
209
214
|
|
|
210
215
|
return tuple(mmts)
|
|
211
216
|
|
|
217
|
+
@property
|
|
218
|
+
def secrets(self) -> Dict[str, str]:
|
|
219
|
+
"""All (encrypted) secrets for the teamspace.
|
|
220
|
+
|
|
221
|
+
Note:
|
|
222
|
+
Once created, the secret values are encrypted and cannot be viewed here anymore.
|
|
223
|
+
"""
|
|
224
|
+
return self._teamspace_api.get_secrets(self.id)
|
|
225
|
+
|
|
226
|
+
def set_secret(self, key: str, value: str) -> None:
|
|
227
|
+
"""Set the (encrypted) secrets for the teamspace."""
|
|
228
|
+
if not self._teamspace_api.verify_secret_name(key):
|
|
229
|
+
raise ValueError(
|
|
230
|
+
"Secret keys must only contain alphanumeric characters and underscores and not begin with a number."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
self._teamspace_api.set_secret(self.id, key, value)
|
|
234
|
+
|
|
212
235
|
def list_machines(self, cloud_account: Optional[str] = None) -> List[Machine]:
|
|
213
236
|
if cloud_account is None:
|
|
214
237
|
cloud_account = os.getenv("LIGHTNING_CLUSTER_ID") or self.default_cloud_account
|
lightning_sdk/user.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Dict, Optional
|
|
2
2
|
|
|
3
3
|
from lightning_sdk.api import UserApi
|
|
4
4
|
from lightning_sdk.owner import Owner
|
|
@@ -37,6 +37,24 @@ class User(Owner):
|
|
|
37
37
|
"""The user's ID."""
|
|
38
38
|
return self._user.id
|
|
39
39
|
|
|
40
|
+
@property
|
|
41
|
+
def secrets(self) -> Dict[str, str]:
|
|
42
|
+
"""All (encrypted) secrets for the user.
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
Once created, the secret values are encrypted and cannot be viewed here anymore.
|
|
46
|
+
"""
|
|
47
|
+
return self._user_api.get_secrets()
|
|
48
|
+
|
|
49
|
+
def set_secret(self, key: str, value: str) -> None:
|
|
50
|
+
"""Set a (encrypted) secret for the user."""
|
|
51
|
+
if not self._user_api.verify_secret_name(key):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"Secret keys must only contain alphanumeric characters and underscores and not begin with a number."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
self._user_api.set_secret(key, value)
|
|
57
|
+
|
|
40
58
|
def __repr__(self) -> str:
|
|
41
59
|
"""Returns reader friendly representation."""
|
|
42
60
|
return f"User(name={self.name})"
|
lightning_sdk/utils/config.py
CHANGED
|
@@ -144,6 +144,12 @@ class Config:
|
|
|
144
144
|
sort_keys=True,
|
|
145
145
|
)
|
|
146
146
|
|
|
147
|
+
def get(self, key: str) -> Optional[str]:
|
|
148
|
+
return self.get_value(key)
|
|
149
|
+
|
|
150
|
+
def set(self, key: str, value: str) -> None:
|
|
151
|
+
self._set_nested([key], value)
|
|
152
|
+
|
|
147
153
|
|
|
148
154
|
def _unflatten_dict(flat_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
149
155
|
unflattened_dict = {}
|