lightning-sdk 2025.7.10__py3-none-any.whl → 2025.7.22__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 +3 -2
- lightning_sdk/api/cloud_account_api.py +154 -0
- lightning_sdk/api/deployment_api.py +11 -0
- lightning_sdk/api/job_api.py +9 -0
- lightning_sdk/api/llm_api.py +11 -6
- lightning_sdk/api/mmt_api.py +9 -0
- lightning_sdk/api/pipeline_api.py +4 -3
- lightning_sdk/api/studio_api.py +19 -5
- lightning_sdk/cli/clusters_menu.py +3 -3
- lightning_sdk/cli/create.py +22 -10
- lightning_sdk/cli/deploy/_auth.py +19 -3
- lightning_sdk/cli/deploy/serve.py +18 -4
- lightning_sdk/cli/entrypoint.py +1 -1
- lightning_sdk/cli/start.py +37 -7
- lightning_sdk/deployment/deployment.py +8 -0
- lightning_sdk/job/base.py +37 -5
- lightning_sdk/job/job.py +28 -4
- lightning_sdk/job/v1.py +10 -1
- lightning_sdk/job/v2.py +15 -1
- lightning_sdk/lightning_cloud/openapi/__init__.py +15 -1
- lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +335 -0
- lightning_sdk/lightning_cloud/openapi/api/billing_service_api.py +214 -0
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +5 -1
- lightning_sdk/lightning_cloud/openapi/api/user_service_api.py +11 -11
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +15 -1
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/blogposts_id_body.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/{v1_list_new_features_for_user_response.py → conversations_id_body1.py} +23 -23
- lightning_sdk/lightning_cloud/openapi/models/messages_id_body.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/project_id_schedules_body.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/project_id_storage_body.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/protobuf_null_value.py +102 -0
- lightning_sdk/lightning_cloud/openapi/models/schedules_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/storage_complete_body.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body1.py +3 -55
- lightning_sdk/lightning_cloud/openapi/models/user_id_upgradetrigger_body.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_ai_pod_v1.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_artifact.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_assistant_session_daily_aggregated.py +357 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_blog_post.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_type.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_complete_upload.py +3 -55
- lightning_sdk/lightning_cloud/openapi/models/v1_conversation.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_billing_upgrade_trigger_record_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_create_blog_post_request.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_checkout_session_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_subscription_checkout_session_request.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_function_tool.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_artifacts_page_response.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_get_assistant_session_daily_aggregated_response.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_direct_v1.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_lightningapp_instance_artifact.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_like_status.py +104 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_notification_dialogs_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_published_managed_endpoint_models_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_message.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_presigned_url.py +1 -53
- lightning_sdk/lightning_cloud/openapi/models/v1_project.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_quote_subscription_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_schedule.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_tool.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_conversation_like_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_conversation_message_like_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +105 -261
- lightning_sdk/lightning_cloud/openapi/models/v1_volume.py +27 -1
- lightning_sdk/llm/llm.py +32 -5
- lightning_sdk/llm/public_assistants.json +3 -1
- lightning_sdk/machine.py +24 -1
- lightning_sdk/mmt/base.py +20 -2
- lightning_sdk/mmt/mmt.py +25 -3
- lightning_sdk/mmt/v1.py +7 -1
- lightning_sdk/mmt/v2.py +21 -2
- lightning_sdk/organization.py +4 -0
- lightning_sdk/pipeline/pipeline.py +16 -5
- lightning_sdk/pipeline/printer.py +5 -3
- lightning_sdk/pipeline/schedule.py +844 -1
- lightning_sdk/pipeline/steps.py +19 -4
- lightning_sdk/sandbox.py +4 -1
- lightning_sdk/serve.py +2 -0
- lightning_sdk/studio.py +79 -39
- lightning_sdk/teamspace.py +14 -8
- lightning_sdk/utils/resolve.py +29 -2
- {lightning_sdk-2025.7.10.dist-info → lightning_sdk-2025.7.22.dist-info}/METADATA +1 -1
- {lightning_sdk-2025.7.10.dist-info → lightning_sdk-2025.7.22.dist-info}/RECORD +92 -78
- lightning_sdk/api/cluster_api.py +0 -119
- /lightning_sdk/cli/{inspect.py → inspection.py} +0 -0
- {lightning_sdk-2025.7.10.dist-info → lightning_sdk-2025.7.22.dist-info}/LICENSE +0 -0
- {lightning_sdk-2025.7.10.dist-info → lightning_sdk-2025.7.22.dist-info}/WHEEL +0 -0
- {lightning_sdk-2025.7.10.dist-info → lightning_sdk-2025.7.22.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-2025.7.10.dist-info → lightning_sdk-2025.7.22.dist-info}/top_level.txt +0 -0
lightning_sdk/pipeline/steps.py
CHANGED
|
@@ -25,11 +25,11 @@ from lightning_sdk.lightning_cloud.openapi.models import (
|
|
|
25
25
|
from lightning_sdk.machine import Machine
|
|
26
26
|
from lightning_sdk.mmt.v2 import MMTApiV2
|
|
27
27
|
from lightning_sdk.pipeline.utils import DEFAULT, _get_studio, _to_wait_for, _validate_cloud_account
|
|
28
|
-
from lightning_sdk.studio import Studio
|
|
28
|
+
from lightning_sdk.studio import CloudAccountApi, Studio
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
31
|
from lightning_sdk.organization import Organization
|
|
32
|
-
from lightning_sdk.teamspace import Teamspace
|
|
32
|
+
from lightning_sdk.teamspace import CloudProvider, Teamspace
|
|
33
33
|
from lightning_sdk.user import User
|
|
34
34
|
|
|
35
35
|
|
|
@@ -57,6 +57,7 @@ class DeploymentStep:
|
|
|
57
57
|
custom_domain: Optional[str] = None,
|
|
58
58
|
quantity: Optional[int] = None,
|
|
59
59
|
include_credentials: Optional[bool] = None,
|
|
60
|
+
max_runtime: Optional[int] = None,
|
|
60
61
|
wait_for: Optional[Union[str, List[str]]] = DEFAULT,
|
|
61
62
|
) -> None:
|
|
62
63
|
self.name = name
|
|
@@ -96,6 +97,7 @@ class DeploymentStep:
|
|
|
96
97
|
self.custom_domain = custom_domain
|
|
97
98
|
self.quantity = quantity
|
|
98
99
|
self.include_credentials = include_credentials or True
|
|
100
|
+
self.max_runtime = max_runtime
|
|
99
101
|
self.wait_for = wait_for
|
|
100
102
|
|
|
101
103
|
def to_proto(
|
|
@@ -124,6 +126,7 @@ class DeploymentStep:
|
|
|
124
126
|
quantity=self.quantity,
|
|
125
127
|
cloudspace_id=self.studio._studio.id if self.studio else None,
|
|
126
128
|
include_credentials=self.include_credentials,
|
|
129
|
+
max_runtime=self.max_runtime,
|
|
127
130
|
),
|
|
128
131
|
strategy=to_strategy(self.release_strategy),
|
|
129
132
|
),
|
|
@@ -144,12 +147,14 @@ class JobStep:
|
|
|
144
147
|
org: Union[str, "Organization", None] = None,
|
|
145
148
|
user: Union[str, "User", None] = None,
|
|
146
149
|
cloud_account: Optional[str] = None,
|
|
150
|
+
cloud_provider: Optional[Union["CloudProvider", str]] = None,
|
|
147
151
|
env: Optional[Dict[str, str]] = None,
|
|
148
152
|
interruptible: bool = False,
|
|
149
153
|
image_credentials: Optional[str] = None,
|
|
150
154
|
cloud_account_auth: bool = False,
|
|
151
155
|
entrypoint: str = "sh -c",
|
|
152
156
|
path_mappings: Optional[Dict[str, str]] = None,
|
|
157
|
+
max_runtime: Optional[int] = None,
|
|
153
158
|
wait_for: Union[str, List[str], None] = DEFAULT,
|
|
154
159
|
) -> None:
|
|
155
160
|
self.name = name
|
|
@@ -168,12 +173,14 @@ class JobStep:
|
|
|
168
173
|
self.org = org
|
|
169
174
|
self.user = user
|
|
170
175
|
self.cloud_account = cloud_account or "" if self.studio is None else self.studio.cloud_account
|
|
176
|
+
self.cloud_provider = cloud_provider
|
|
171
177
|
self.env = env
|
|
172
178
|
self.interruptible = interruptible
|
|
173
179
|
self.image_credentials = image_credentials
|
|
174
180
|
self.cloud_account_auth = cloud_account_auth
|
|
175
181
|
self.entrypoint = entrypoint
|
|
176
182
|
self.path_mappings = path_mappings
|
|
183
|
+
self.max_runtime = max_runtime
|
|
177
184
|
self.wait_for = wait_for
|
|
178
185
|
|
|
179
186
|
def to_proto(
|
|
@@ -186,12 +193,16 @@ class JobStep:
|
|
|
186
193
|
elif studio.cloud_account != self.cloud_account:
|
|
187
194
|
raise ValueError("The provided cloud account doesn't match the studio")
|
|
188
195
|
|
|
189
|
-
|
|
196
|
+
resolved_cloud_account = CloudAccountApi().resolve_cloud_account(
|
|
197
|
+
teamspace.id, self.cloud_account, self.cloud_provider, teamspace.default_cloud_account
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
_validate_cloud_account(cloud_account, resolved_cloud_account, shared_filesystem)
|
|
190
201
|
|
|
191
202
|
body = JobApiV2._create_job_body(
|
|
192
203
|
name=self.name,
|
|
193
204
|
command=self.command,
|
|
194
|
-
cloud_account=
|
|
205
|
+
cloud_account=resolved_cloud_account or cloud_account,
|
|
195
206
|
studio_id=studio._studio.id if isinstance(studio, Studio) else None,
|
|
196
207
|
image=self.image,
|
|
197
208
|
machine=self.machine,
|
|
@@ -203,6 +214,7 @@ class JobStep:
|
|
|
203
214
|
path_mappings=self.path_mappings,
|
|
204
215
|
artifacts_local=None,
|
|
205
216
|
artifacts_remote=None,
|
|
217
|
+
max_runtime=self.max_runtime,
|
|
206
218
|
)
|
|
207
219
|
|
|
208
220
|
return V1PipelineStep(
|
|
@@ -234,6 +246,7 @@ class MMTStep:
|
|
|
234
246
|
cloud_account_auth: bool = False,
|
|
235
247
|
entrypoint: str = "sh -c",
|
|
236
248
|
path_mappings: Optional[Dict[str, str]] = None,
|
|
249
|
+
max_runtime: Optional[int] = None,
|
|
237
250
|
wait_for: Optional[Union[str, List[str]]] = DEFAULT,
|
|
238
251
|
) -> None:
|
|
239
252
|
self.machine = machine or Machine.CPU
|
|
@@ -258,6 +271,7 @@ class MMTStep:
|
|
|
258
271
|
self.cloud_account_auth = cloud_account_auth
|
|
259
272
|
self.entrypoint = entrypoint
|
|
260
273
|
self.path_mappings = path_mappings
|
|
274
|
+
self.max_runtime = max_runtime
|
|
261
275
|
self.wait_for = wait_for
|
|
262
276
|
|
|
263
277
|
def to_proto(
|
|
@@ -288,6 +302,7 @@ class MMTStep:
|
|
|
288
302
|
path_mappings=self.path_mappings,
|
|
289
303
|
artifacts_local=None, # deprecated in favor of path_mappings
|
|
290
304
|
artifacts_remote=None, # deprecated in favor of path_mappings
|
|
305
|
+
max_runtime=self.max_runtime,
|
|
291
306
|
)
|
|
292
307
|
|
|
293
308
|
return V1PipelineStep(
|
lightning_sdk/sandbox.py
CHANGED
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from typing import Optional, Type, Union
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.machine import Machine
|
|
7
|
+
from lightning_sdk.machine import CloudProvider, Machine
|
|
8
8
|
from lightning_sdk.organization import Organization
|
|
9
9
|
from lightning_sdk.status import Status
|
|
10
10
|
from lightning_sdk.studio import Studio
|
|
@@ -31,6 +31,7 @@ class _Sandbox:
|
|
|
31
31
|
org: The organization to use for the sandbox.
|
|
32
32
|
user: The user to use for the sandbox.
|
|
33
33
|
cloud_account: The cloud account to use for the sandbox.
|
|
34
|
+
cloud_provider: Selects the cloud account based on the available cloud accounts and the specified provider.
|
|
34
35
|
disable_secrets: If true, user secrets such as LIGHTNING_API_KEY are not stored in the sandbox.
|
|
35
36
|
|
|
36
37
|
Example:
|
|
@@ -48,6 +49,7 @@ class _Sandbox:
|
|
|
48
49
|
org: Optional[Union[str, Organization]] = None,
|
|
49
50
|
user: Optional[Union[str, User]] = None,
|
|
50
51
|
cloud_account: Optional[str] = None,
|
|
52
|
+
cloud_provider: Optional[Union[CloudProvider, str]] = None,
|
|
51
53
|
disable_secrets: bool = True,
|
|
52
54
|
) -> None:
|
|
53
55
|
if name is None:
|
|
@@ -62,6 +64,7 @@ class _Sandbox:
|
|
|
62
64
|
org=org,
|
|
63
65
|
user=user,
|
|
64
66
|
cloud_account=cloud_account,
|
|
67
|
+
cloud_provider=cloud_provider,
|
|
65
68
|
disable_secrets=disable_secrets,
|
|
66
69
|
)
|
|
67
70
|
|
lightning_sdk/serve.py
CHANGED
|
@@ -101,6 +101,8 @@ Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additio
|
|
|
101
101
|
|
|
102
102
|
[bold]To push the container to a registry:[/bold]
|
|
103
103
|
> [underline]docker push {tag}[/underline]
|
|
104
|
+
|
|
105
|
+
Check out [blue][link=https://lightning.ai/docs/litserve/features]the docs[/link][/blue] for more details.
|
|
104
106
|
"""
|
|
105
107
|
console.print(success_msg)
|
|
106
108
|
return os.path.abspath("Dockerfile")
|
lightning_sdk/studio.py
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import glob
|
|
2
2
|
import os
|
|
3
3
|
import warnings
|
|
4
|
-
from enum import Enum
|
|
5
4
|
from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Tuple, Union
|
|
6
5
|
|
|
7
6
|
from tqdm.auto import tqdm
|
|
8
7
|
|
|
9
|
-
from lightning_sdk.api.
|
|
8
|
+
from lightning_sdk.api.cloud_account_api import CloudAccountApi
|
|
10
9
|
from lightning_sdk.api.studio_api import StudioApi
|
|
11
10
|
from lightning_sdk.api.utils import _machine_to_compute_name
|
|
12
11
|
from lightning_sdk.constants import _LIGHTNING_DEBUG
|
|
13
|
-
from lightning_sdk.
|
|
14
|
-
from lightning_sdk.machine import Machine
|
|
12
|
+
from lightning_sdk.machine import CloudProvider, Machine
|
|
15
13
|
from lightning_sdk.organization import Organization
|
|
16
14
|
from lightning_sdk.owner import Owner
|
|
17
15
|
from lightning_sdk.status import Status
|
|
18
16
|
from lightning_sdk.teamspace import Teamspace
|
|
19
17
|
from lightning_sdk.user import User
|
|
20
|
-
from lightning_sdk.utils.resolve import
|
|
18
|
+
from lightning_sdk.utils.resolve import (
|
|
19
|
+
_resolve_deprecated_cluster,
|
|
20
|
+
_resolve_deprecated_provider,
|
|
21
|
+
_resolve_teamspace,
|
|
22
|
+
_setup_logger,
|
|
23
|
+
)
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
23
26
|
from lightning_sdk.job import Job
|
|
@@ -27,19 +30,6 @@ if TYPE_CHECKING:
|
|
|
27
30
|
_logger = _setup_logger(__name__)
|
|
28
31
|
|
|
29
32
|
|
|
30
|
-
class Provider(Enum):
|
|
31
|
-
# Machine providers based on v1CloudProvider
|
|
32
|
-
AWS = "AWS"
|
|
33
|
-
GCP = "GCP"
|
|
34
|
-
VULTR = "VULTR"
|
|
35
|
-
LAMBDA_LABS = "LAMBDA_LABS"
|
|
36
|
-
DGX = "DGX"
|
|
37
|
-
VOLTAGE_PARK = "VOLTAGE_PARK"
|
|
38
|
-
NEBIUS = "NEBIUS"
|
|
39
|
-
CLOUDFLARE = "CLOUDFLARE"
|
|
40
|
-
LIGHTNING = "LIGHTNING"
|
|
41
|
-
|
|
42
|
-
|
|
43
33
|
class Studio:
|
|
44
34
|
"""A single Lightning AI Studio.
|
|
45
35
|
|
|
@@ -53,6 +43,9 @@ class Studio:
|
|
|
53
43
|
user: the name of the user owning the :param`teamspace` in case it is owned directly by a user instead of an org
|
|
54
44
|
cloud_account: the name of the cloud account, the studio should be created on.
|
|
55
45
|
Doesn't matter when the studio already exists.
|
|
46
|
+
cloud_account_provider: The provider to select the cloud-account from.
|
|
47
|
+
If set, must be in agreement with the provider from the cloud_account (if specified).
|
|
48
|
+
If not specified, falls backto the teamspace default cloud account.
|
|
56
49
|
create_ok: whether the studio will be created if it does not yet exist. Defaults to True
|
|
57
50
|
provider: the provider of the machine, the studio should be created on.
|
|
58
51
|
|
|
@@ -72,34 +65,37 @@ class Studio:
|
|
|
72
65
|
org: Optional[Union[str, Organization]] = None,
|
|
73
66
|
user: Optional[Union[str, User]] = None,
|
|
74
67
|
cloud_account: Optional[str] = None,
|
|
68
|
+
cloud_provider: Optional[Union[CloudProvider, str]] = None,
|
|
75
69
|
create_ok: bool = True,
|
|
76
70
|
cluster: Optional[str] = None, # deprecated in favor of cloud_account
|
|
77
|
-
|
|
78
|
-
source: Optional[V1CloudSpaceSourceType] = None,
|
|
71
|
+
source: Optional[str] = None,
|
|
79
72
|
disable_secrets: bool = False,
|
|
73
|
+
provider: Optional[Union[CloudProvider, str]] = None, # deprecated in favor of cloud_provider
|
|
80
74
|
) -> None:
|
|
81
75
|
self._studio_api = StudioApi()
|
|
82
|
-
self.
|
|
76
|
+
self._cloud_account_api = CloudAccountApi()
|
|
77
|
+
|
|
78
|
+
_teamspace = _resolve_teamspace(teamspace=teamspace, org=org, user=user)
|
|
79
|
+
if _teamspace is None:
|
|
80
|
+
raise ValueError("Couldn't resolve teamspace from the provided name, org, or user")
|
|
83
81
|
|
|
84
|
-
self._teamspace =
|
|
82
|
+
self._teamspace = _teamspace
|
|
85
83
|
self._cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
|
|
84
|
+
|
|
86
85
|
self._setup_done = False
|
|
87
86
|
self._disable_secrets = disable_secrets
|
|
88
87
|
|
|
89
88
|
self._plugins = {}
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
|
|
91
|
+
cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
self._teamspace.id,
|
|
101
|
-
self._teamspace.owner.id,
|
|
102
|
-
)[provider.value]
|
|
93
|
+
self._cloud_account = self._cloud_account_api.resolve_cloud_account(
|
|
94
|
+
self._teamspace.id,
|
|
95
|
+
cloud_account=cloud_account,
|
|
96
|
+
cloud_provider=cloud_provider,
|
|
97
|
+
default_cloud_account=self._teamspace.default_cloud_account,
|
|
98
|
+
)
|
|
103
99
|
|
|
104
100
|
if name is None:
|
|
105
101
|
studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
|
|
@@ -121,6 +117,8 @@ class Studio:
|
|
|
121
117
|
else:
|
|
122
118
|
raise ValueError(f"Studio {name} does not exist.") from e
|
|
123
119
|
|
|
120
|
+
self._cloud_account = self._studio.cluster_id
|
|
121
|
+
|
|
124
122
|
if (
|
|
125
123
|
not self._skip_init
|
|
126
124
|
and _internal_status_to_external_status(
|
|
@@ -198,8 +196,23 @@ class Studio:
|
|
|
198
196
|
def cloud_account(self) -> str:
|
|
199
197
|
return self._studio.cluster_id
|
|
200
198
|
|
|
201
|
-
def start(
|
|
202
|
-
|
|
199
|
+
def start(
|
|
200
|
+
self,
|
|
201
|
+
machine: Union[Machine, str] = Machine.CPU,
|
|
202
|
+
interruptible: Optional[bool] = None,
|
|
203
|
+
max_runtime: Optional[int] = None,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Starts a Studio on the specified machine type (default: CPU-4).
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
machine: the machine type to start the studio on. Defaults to CPU-4
|
|
209
|
+
interruptible: whether to use interruptible machines
|
|
210
|
+
max_runtime: the duration (in seconds) for which to allocate the machine.
|
|
211
|
+
Irrelevant for most machines, required for some of the top-end machines on GCP.
|
|
212
|
+
If in doubt, set it. Won't have an effect on machines not requiring it.
|
|
213
|
+
Defaults to 3h
|
|
214
|
+
|
|
215
|
+
"""
|
|
203
216
|
status = self.status
|
|
204
217
|
|
|
205
218
|
if interruptible is None:
|
|
@@ -221,7 +234,9 @@ class Studio:
|
|
|
221
234
|
|
|
222
235
|
if status != Status.Stopped:
|
|
223
236
|
raise RuntimeError(f"Cannot start a studio that is not stopped. Studio {self.name} is {status}.")
|
|
224
|
-
self._studio_api.start_studio(
|
|
237
|
+
self._studio_api.start_studio(
|
|
238
|
+
self._studio.id, self._teamspace.id, machine, interruptible=interruptible, max_runtime=max_runtime
|
|
239
|
+
)
|
|
225
240
|
|
|
226
241
|
self._setup()
|
|
227
242
|
|
|
@@ -236,9 +251,34 @@ class Studio:
|
|
|
236
251
|
"""Deletes the current Studio."""
|
|
237
252
|
self._studio_api.delete_studio(self._studio.id, self._teamspace.id)
|
|
238
253
|
|
|
239
|
-
def duplicate(self) -> "Studio":
|
|
240
|
-
"""Duplicates the existing Studio
|
|
241
|
-
|
|
254
|
+
def duplicate(self, target_teamspace: Optional[Union["Teamspace", str]] = None) -> "Studio":
|
|
255
|
+
"""Duplicates the existing Studio.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
target_teamspace: the teamspace to duplicate the studio to.
|
|
259
|
+
Must have the same owner as the source teamspace.
|
|
260
|
+
If not provided, defaults to current teamspace.
|
|
261
|
+
"""
|
|
262
|
+
if target_teamspace is None:
|
|
263
|
+
target_teamspace_id = self._teamspace.id
|
|
264
|
+
else:
|
|
265
|
+
target_teamspace = _resolve_teamspace(
|
|
266
|
+
target_teamspace,
|
|
267
|
+
org=self._teamspace.owner if isinstance(self._teamspace.owner, Organization) else None,
|
|
268
|
+
user=self._teamspace.owner if isinstance(self._teamspace.owner, User) else None,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if target_teamspace is None:
|
|
272
|
+
raise ValueError(
|
|
273
|
+
f"Could not resolve target teamspace {target_teamspace} "
|
|
274
|
+
f"with owner {self.teamspace.owner} for duplication!"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
target_teamspace_id = target_teamspace.id
|
|
278
|
+
|
|
279
|
+
kwargs = self._studio_api.duplicate_studio(
|
|
280
|
+
studio_id=self._studio.id, teamspace_id=self._teamspace.id, target_teamspace_id=target_teamspace_id
|
|
281
|
+
)
|
|
242
282
|
return Studio(**kwargs)
|
|
243
283
|
|
|
244
284
|
def switch_machine(self, machine: Union[Machine, str], interruptible: bool = False) -> None:
|
lightning_sdk/teamspace.py
CHANGED
|
@@ -107,8 +107,8 @@ class Teamspace:
|
|
|
107
107
|
from lightning_sdk.studio import Studio
|
|
108
108
|
|
|
109
109
|
studios = []
|
|
110
|
-
|
|
111
|
-
for cl in
|
|
110
|
+
cloud_accounts = self._teamspace_api.list_cloud_accounts(teamspace_id=self.id)
|
|
111
|
+
for cl in cloud_accounts:
|
|
112
112
|
_studios = self._teamspace_api.list_studios(teamspace_id=self.id, cloud_account=cl.cluster_id)
|
|
113
113
|
for s in _studios:
|
|
114
114
|
studios.append(Studio(name=s.name, teamspace=self, cluster=cl.cluster_name, create_ok=False))
|
|
@@ -116,8 +116,11 @@ class Teamspace:
|
|
|
116
116
|
return studios
|
|
117
117
|
|
|
118
118
|
@property
|
|
119
|
-
def default_cloud_account(self) -> str:
|
|
120
|
-
|
|
119
|
+
def default_cloud_account(self) -> Optional[str]:
|
|
120
|
+
owner_preferred_cluster = (
|
|
121
|
+
getattr(self.owner, "default_cloud_account", None) if isinstance(self.owner, Organization) else None
|
|
122
|
+
)
|
|
123
|
+
return self._teamspace.project_settings.preferred_cluster or owner_preferred_cluster
|
|
121
124
|
|
|
122
125
|
@property
|
|
123
126
|
def start_studios_on_interruptible(self) -> bool:
|
|
@@ -126,8 +129,8 @@ class Teamspace:
|
|
|
126
129
|
@property
|
|
127
130
|
def cloud_accounts(self) -> List[str]:
|
|
128
131
|
"""All cloud accounts associated with that teamspace."""
|
|
129
|
-
|
|
130
|
-
return [cl.cluster_name for cl in
|
|
132
|
+
cloud_accounts = self._teamspace_api.list_cloud_accounts(teamspace_id=self.id)
|
|
133
|
+
return [cl.cluster_name for cl in cloud_accounts]
|
|
131
134
|
|
|
132
135
|
@property
|
|
133
136
|
def cloud_account_objs(self) -> List[V1ProjectClusterBinding]:
|
|
@@ -196,7 +199,10 @@ class Teamspace:
|
|
|
196
199
|
if cloud_account is None:
|
|
197
200
|
cloud_account = os.getenv("LIGHTNING_CLUSTER_ID") or self.default_cloud_account
|
|
198
201
|
|
|
199
|
-
|
|
202
|
+
if cloud_account is None:
|
|
203
|
+
raise RuntimeError("Could not resolve cloud account")
|
|
204
|
+
|
|
205
|
+
cloud_machines = self._teamspace_api.list_machines(self.id, cloud_account=cloud_account)
|
|
200
206
|
return [
|
|
201
207
|
Machine(
|
|
202
208
|
cluster_machine.instance_id,
|
|
@@ -208,7 +214,7 @@ class Teamspace:
|
|
|
208
214
|
if cluster_machine.available_in_seconds_spot
|
|
209
215
|
else None,
|
|
210
216
|
)
|
|
211
|
-
for cluster_machine in
|
|
217
|
+
for cluster_machine in cloud_machines
|
|
212
218
|
]
|
|
213
219
|
|
|
214
220
|
def __eq__(self, other: "Teamspace") -> bool:
|
lightning_sdk/utils/resolve.py
CHANGED
|
@@ -6,7 +6,8 @@ from typing import TYPE_CHECKING, Generator, List, Optional, Tuple, Union
|
|
|
6
6
|
|
|
7
7
|
from lightning_sdk.api import TeamspaceApi, UserApi
|
|
8
8
|
from lightning_sdk.api.utils import _get_cloud_url
|
|
9
|
-
from lightning_sdk.
|
|
9
|
+
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
10
|
+
from lightning_sdk.machine import CloudProvider, Machine
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from lightning_sdk.organization import Organization
|
|
@@ -48,6 +49,26 @@ def _resolve_deprecated_cloud_compute(machine: Machine, cloud_compute: Optional[
|
|
|
48
49
|
return machine
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def _resolve_deprecated_provider(
|
|
53
|
+
cloud_provider: Optional[Union[CloudProvider, str]], provider: Optional[Union[CloudProvider, str]]
|
|
54
|
+
) -> Optional[Union[CloudProvider, str]]:
|
|
55
|
+
if provider is not None:
|
|
56
|
+
if cloud_provider is not None:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"Cannot use both 'provider' and 'cloud_provider' at the same time."
|
|
59
|
+
"Please don't set the 'provider' as it will be deprecated!"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
warnings.warn(
|
|
63
|
+
"The 'provider' argument will be deprecated in the future! "
|
|
64
|
+
"Please consider using the 'cloud_provider' argument instead!",
|
|
65
|
+
DeprecationWarning,
|
|
66
|
+
)
|
|
67
|
+
return provider
|
|
68
|
+
|
|
69
|
+
return cloud_provider
|
|
70
|
+
|
|
71
|
+
|
|
51
72
|
def _resolve_deprecated_cluster(cloud_account: Optional[str], cluster: Optional[str]) -> Optional[str]:
|
|
52
73
|
if cluster is not None:
|
|
53
74
|
if cloud_account is not None:
|
|
@@ -85,7 +106,13 @@ def _resolve_org(org: Optional[Union[str, "Organization"]]) -> Optional["Organiz
|
|
|
85
106
|
|
|
86
107
|
from lightning_sdk.organization import Organization
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
try:
|
|
110
|
+
return Organization(name=org)
|
|
111
|
+
# Handle case where user name is mistakenly used as organization name
|
|
112
|
+
except ApiException as ae:
|
|
113
|
+
if ae.status == 404:
|
|
114
|
+
raise ValueError(f"Organization '{org}' does not exist or you are not a member of it.") from ae
|
|
115
|
+
raise RuntimeError(f"Failed to resolve organization '{org}': {ae}") from ae
|
|
89
116
|
|
|
90
117
|
|
|
91
118
|
def _resolve_user_name(name: Optional[str]) -> Optional[str]:
|