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
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,78 @@ class Studio:
|
|
|
79
81
|
self._studio_api = StudioApi()
|
|
80
82
|
self._cloud_account_api = CloudAccountApi()
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
raise ValueError("Couldn't resolve teamspace from the provided name, org, or user")
|
|
84
|
+
self._prevent_refetch = False
|
|
85
|
+
self._teamspace = None
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
# don't resolve anything if we're skipping init
|
|
88
|
+
if not getattr(self._skip_init, "value", False):
|
|
89
|
+
_teamspace = _resolve_teamspace(teamspace=teamspace, org=org, user=user)
|
|
90
|
+
if _teamspace is None:
|
|
91
|
+
raise ValueError("Couldn't resolve teamspace from the provided name, org, or user")
|
|
87
92
|
|
|
88
|
-
|
|
93
|
+
self._teamspace = _teamspace
|
|
94
|
+
|
|
95
|
+
self._setup_done = getattr(self._skip_setup, "value", False)
|
|
89
96
|
self._disable_secrets = disable_secrets
|
|
90
97
|
|
|
91
98
|
self._plugins = {}
|
|
99
|
+
self._studio = None
|
|
92
100
|
|
|
93
101
|
cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
|
|
94
102
|
cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
|
|
95
103
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
# if we're skipping init, we don't need to resolve the cloud account as then we're not creating a studio
|
|
105
|
+
if self._teamspace is not None:
|
|
106
|
+
_cloud_account = self._cloud_account_api.resolve_cloud_account(
|
|
107
|
+
self._teamspace.id,
|
|
108
|
+
cloud_account=cloud_account,
|
|
109
|
+
cloud_provider=cloud_provider,
|
|
110
|
+
default_cloud_account=self._teamspace.default_cloud_account,
|
|
111
|
+
)
|
|
102
112
|
|
|
103
113
|
# Resolve studio name if not provided: explicit → env (LIGHTNING_CLOUD_SPACE_ID) → config defaults
|
|
104
|
-
if name is None:
|
|
114
|
+
if name is None and not getattr(self._skip_init, "value", False):
|
|
105
115
|
studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
|
|
106
116
|
if studio_id is not None:
|
|
107
117
|
# We're inside a studio, get it by ID
|
|
108
118
|
self._studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
|
|
119
|
+
name = self._studio.name
|
|
109
120
|
else:
|
|
110
121
|
# Try config defaults
|
|
111
122
|
from lightning_sdk.utils.config import Config, DefaultConfigKeys
|
|
112
123
|
|
|
113
124
|
config = Config()
|
|
114
125
|
name = config.get_value(DefaultConfigKeys.studio)
|
|
115
|
-
if name is None:
|
|
126
|
+
if name is None and not create_ok:
|
|
116
127
|
raise ValueError(
|
|
117
128
|
"Cannot autodetect Studio. Either use the SDK from within a Studio or pass a name!"
|
|
118
129
|
)
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
if self._studio is None and not getattr(self._skip_init, "value", False):
|
|
132
|
+
# If we have a name (explicit or from config), get studio by name
|
|
122
133
|
try:
|
|
134
|
+
if name is None:
|
|
135
|
+
# if we don't have a name, raise an error to get
|
|
136
|
+
# to the exception path and optionally create a studio
|
|
137
|
+
raise ValueError(
|
|
138
|
+
"Cannot autodetect Studio. Either use the SDK from within a Studio or pass a name!"
|
|
139
|
+
)
|
|
123
140
|
self._studio = self._studio_api.get_studio(name, self._teamspace.id)
|
|
124
141
|
except ValueError as e:
|
|
125
142
|
if create_ok:
|
|
143
|
+
name = name or random_unique_name()
|
|
126
144
|
self._studio = self._studio_api.create_studio(
|
|
127
145
|
name,
|
|
128
146
|
self._teamspace.id,
|
|
129
|
-
cloud_account=
|
|
147
|
+
cloud_account=_cloud_account,
|
|
130
148
|
source=source,
|
|
131
149
|
disable_secrets=self._disable_secrets,
|
|
132
150
|
)
|
|
133
151
|
else:
|
|
134
|
-
raise
|
|
135
|
-
|
|
136
|
-
self._cloud_account = self._studio.cluster_id
|
|
152
|
+
raise e
|
|
137
153
|
|
|
138
154
|
if (
|
|
139
|
-
not self._skip_init
|
|
155
|
+
not getattr(self._skip_init, "value", False)
|
|
140
156
|
and _internal_status_to_external_status(
|
|
141
157
|
self._studio_api._get_studio_instance_status_from_object(self._studio)
|
|
142
158
|
)
|
|
@@ -608,6 +624,22 @@ class Studio:
|
|
|
608
624
|
warnings.warn("auto_shutdown_time is deprecated. Use auto_sleep_time instead", DeprecationWarning)
|
|
609
625
|
self.auto_sleep_time = value
|
|
610
626
|
|
|
627
|
+
@property
|
|
628
|
+
def env(self) -> Dict[str, str]:
|
|
629
|
+
self._update_studio_reference()
|
|
630
|
+
return self._studio_api.get_env(self._studio)
|
|
631
|
+
|
|
632
|
+
def set_env(self, new_env: Dict[str, str], partial: bool = True) -> None:
|
|
633
|
+
"""Set the environment variables for the Studio.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
new_env: The new environment variables to set.
|
|
637
|
+
partial: Whether to only set the environment variables that are provided.
|
|
638
|
+
If False, existing environment variables that are not in new_env will be removed.
|
|
639
|
+
If True, existing environment variables that are not in new_env will be kept.
|
|
640
|
+
"""
|
|
641
|
+
self._studio_api.set_env(self._studio, self._teamspace.id, new_env, partial=partial)
|
|
642
|
+
|
|
611
643
|
@property
|
|
612
644
|
def available_plugins(self) -> Mapping[str, str]:
|
|
613
645
|
"""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 = {}
|