lightning-sdk 2025.7.22__py3-none-any.whl → 2025.7.31__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/cloud_account_api.py +58 -8
- lightning_sdk/api/job_api.py +74 -11
- lightning_sdk/api/llm_api.py +1 -1
- lightning_sdk/api/mmt_api.py +35 -5
- lightning_sdk/api/studio_api.py +32 -3
- lightning_sdk/api/utils.py +6 -2
- lightning_sdk/cli/create.py +3 -1
- lightning_sdk/cli/deploy/serve.py +3 -1
- lightning_sdk/cli/download.py +25 -1
- lightning_sdk/cli/entrypoint.py +3 -1
- lightning_sdk/cli/list.py +5 -1
- lightning_sdk/cli/run.py +3 -1
- lightning_sdk/cli/start.py +3 -1
- lightning_sdk/cli/switch.py +3 -1
- lightning_sdk/job/v2.py +7 -1
- lightning_sdk/job/work.py +5 -1
- lightning_sdk/lightning_cloud/openapi/__init__.py +6 -1
- lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +106 -13
- lightning_sdk/lightning_cloud/openapi/api/cloudy_service_api.py +295 -0
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +93 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +6 -1
- lightning_sdk/lightning_cloud/openapi/models/agentmanagedendpoints_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/metricsstream_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/project_id_schedules_body.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/schedules_id_body.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/user_id_upgradetrigger_body.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/user_user_id_body.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_billing_subscription.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloudy_settings.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_conversation_response_chunk.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_subscription_checkout_session_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_function_call.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_cluster_health_response.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_direct_v1.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/{v1_list_published_managed_endpoint_models_response.py → v1_list_published_managed_endpoints_response.py} +23 -23
- lightning_sdk/lightning_cloud/openapi/models/v1_managed_endpoint.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_managed_model.py +95 -17
- lightning_sdk/lightning_cloud/openapi/models/v1_resource_visibility.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_response_choice.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_schedule.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_service_health.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_slurm_v1.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_slurm_v1_status.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_tool_call.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +79 -53
- lightning_sdk/lightning_cloud/openapi/models/v1_volume_state.py +1 -0
- lightning_sdk/llm/llm.py +41 -7
- lightning_sdk/llm/public_assistants.json +32 -8
- lightning_sdk/machine.py +139 -43
- lightning_sdk/mmt/v2.py +6 -1
- lightning_sdk/models.py +1 -1
- lightning_sdk/studio.py +12 -5
- lightning_sdk/teamspace.py +5 -2
- lightning_sdk/utils/resolve.py +8 -0
- {lightning_sdk-2025.7.22.dist-info → lightning_sdk-2025.7.31.dist-info}/METADATA +7 -5
- {lightning_sdk-2025.7.22.dist-info → lightning_sdk-2025.7.31.dist-info}/RECORD +64 -59
- {lightning_sdk-2025.7.22.dist-info → lightning_sdk-2025.7.31.dist-info}/LICENSE +0 -0
- {lightning_sdk-2025.7.22.dist-info → lightning_sdk-2025.7.31.dist-info}/WHEEL +0 -0
- {lightning_sdk-2025.7.22.dist-info → lightning_sdk-2025.7.31.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-2025.7.22.dist-info → lightning_sdk-2025.7.31.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
1
2
|
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
|
2
3
|
|
|
3
4
|
from lightning_sdk.lightning_cloud.openapi import (
|
|
@@ -6,6 +7,7 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
6
7
|
V1ClusterType,
|
|
7
8
|
V1ExternalCluster,
|
|
8
9
|
V1ListClusterAcceleratorsResponse,
|
|
10
|
+
V1ListDefaultClusterAcceleratorsResponse,
|
|
9
11
|
)
|
|
10
12
|
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
11
13
|
|
|
@@ -33,6 +35,7 @@ class CloudAccountApi:
|
|
|
33
35
|
raise ValueError(f"CloudAccount {cloud_account_id} does not exist")
|
|
34
36
|
return res
|
|
35
37
|
|
|
38
|
+
@lru_cache(maxsize=None) # noqa: B019
|
|
36
39
|
def list_cloud_accounts(self, teamspace_id: str) -> List[V1ExternalCluster]:
|
|
37
40
|
"""Lists the cloud accounts for a given teamspace.
|
|
38
41
|
|
|
@@ -42,10 +45,22 @@ class CloudAccountApi:
|
|
|
42
45
|
Returns:
|
|
43
46
|
A list of cloud accounts
|
|
44
47
|
"""
|
|
45
|
-
|
|
48
|
+
res_project = self._client.cluster_service_list_project_clusters(
|
|
46
49
|
project_id=teamspace_id,
|
|
47
50
|
)
|
|
48
|
-
|
|
51
|
+
res_global = self._client.cluster_service_list_clusters(
|
|
52
|
+
project_id=teamspace_id,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# can't use set here because the cloud_accounts are not hashable
|
|
56
|
+
cloud_accounts = []
|
|
57
|
+
cloud_account_ids = []
|
|
58
|
+
for cloud_account in res_project.clusters + res_global.clusters:
|
|
59
|
+
if cloud_account.id not in cloud_account_ids:
|
|
60
|
+
cloud_accounts.append(cloud_account)
|
|
61
|
+
cloud_account_ids.append(cloud_account.id)
|
|
62
|
+
|
|
63
|
+
return cloud_accounts
|
|
49
64
|
|
|
50
65
|
def get_cloud_account_non_org(self, teamspace_id: str, cloud_account_id: str) -> Optional[V1ExternalCluster]:
|
|
51
66
|
for cluster in self.list_cloud_accounts(teamspace_id=teamspace_id):
|
|
@@ -54,21 +69,52 @@ class CloudAccountApi:
|
|
|
54
69
|
|
|
55
70
|
return None
|
|
56
71
|
|
|
57
|
-
|
|
72
|
+
@lru_cache(maxsize=None) # noqa: B019
|
|
73
|
+
def list_cloud_account_accelerators(
|
|
74
|
+
self,
|
|
75
|
+
teamspace_id: str,
|
|
76
|
+
cloud_account_id: str,
|
|
77
|
+
org_id: str,
|
|
78
|
+
) -> Union[V1ListClusterAcceleratorsResponse, V1ListDefaultClusterAcceleratorsResponse]:
|
|
58
79
|
"""Lists the accelerators for a given cloud account.
|
|
59
80
|
|
|
60
81
|
Args:
|
|
61
82
|
cloud_account_id: cluster ID to list accelerators for
|
|
62
|
-
org_id: The owning org of this project
|
|
63
83
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
84
|
+
# map cloud_account to provider
|
|
85
|
+
cloud_provider = None
|
|
86
|
+
is_default = True
|
|
87
|
+
for cloud_account in self.list_cloud_accounts(teamspace_id=teamspace_id):
|
|
88
|
+
if cloud_account.id == cloud_account_id:
|
|
89
|
+
is_default = cloud_account.spec.cluster_type == V1ClusterType.GLOBAL
|
|
90
|
+
cloud_provider = self._get_cloud_account_provider(cloud_account)
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
if cloud_provider is None:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"Cloud Account {cloud_account_id} is not a default cloud account. Are you in the correct teamspace?"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if is_default:
|
|
99
|
+
res = self._list_default_cluster_accelerators(teamspace_id=teamspace_id, cloud_provider=cloud_provider)
|
|
100
|
+
else:
|
|
101
|
+
res = self._client.cluster_service_list_cluster_accelerators(
|
|
102
|
+
id=cloud_account_id,
|
|
103
|
+
org_id=org_id,
|
|
104
|
+
)
|
|
105
|
+
|
|
68
106
|
if not res:
|
|
69
107
|
raise ValueError(f"CloudAccount {cloud_account_id} does not exist")
|
|
70
108
|
return res
|
|
71
109
|
|
|
110
|
+
def _list_default_cluster_accelerators(
|
|
111
|
+
self, teamspace_id: str, cloud_provider: Union[str, "CloudProvider"]
|
|
112
|
+
) -> V1ListDefaultClusterAcceleratorsResponse:
|
|
113
|
+
return self._client.cluster_service_list_default_cluster_accelerators(
|
|
114
|
+
project_id=teamspace_id, cloud_provider=str(cloud_provider)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@lru_cache(maxsize=None) # noqa: B019
|
|
72
118
|
def list_global_cloud_accounts(self, teamspace_id: str) -> List[V1ExternalCluster]:
|
|
73
119
|
"""Lists the accelerators for a given teamspace.
|
|
74
120
|
|
|
@@ -79,6 +125,10 @@ class CloudAccountApi:
|
|
|
79
125
|
if not cloud_accounts:
|
|
80
126
|
raise ValueError(f"Teamspace {teamspace_id} does not exist")
|
|
81
127
|
filtered_cloud_accounts = filter(lambda x: x.spec.cluster_type == V1ClusterType.GLOBAL, cloud_accounts)
|
|
128
|
+
# TODO: remove aggregate filter once finished
|
|
129
|
+
filtered_cloud_accounts = filter(
|
|
130
|
+
lambda x: x.spec.driver != V1CloudProvider.LIGHTNING_AGGREGATE, filtered_cloud_accounts
|
|
131
|
+
)
|
|
82
132
|
return list(filtered_cloud_accounts)
|
|
83
133
|
|
|
84
134
|
def get_cloud_account_provider_mapping(self, teamspace_id: str) -> Dict["CloudProvider", str]:
|
lightning_sdk/api/job_api.py
CHANGED
|
@@ -17,7 +17,7 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
17
17
|
JobsIdBody1,
|
|
18
18
|
ProjectIdJobsBody,
|
|
19
19
|
V1CloudSpace,
|
|
20
|
-
|
|
20
|
+
V1ClusterAccelerator,
|
|
21
21
|
V1DownloadJobLogsResponse,
|
|
22
22
|
V1DownloadLightningappInstanceLogsResponse,
|
|
23
23
|
V1EnvVar,
|
|
@@ -94,15 +94,48 @@ class JobApiV1:
|
|
|
94
94
|
def get_work(self, job_id: str, teamspace_id: str, work_id: str) -> Externalv1Lightningwork:
|
|
95
95
|
return self._client.lightningwork_service_get_lightningwork(project_id=teamspace_id, app_id=job_id, id=work_id)
|
|
96
96
|
|
|
97
|
-
def get_machine_from_work(self, work: Externalv1Lightningwork) -> Machine:
|
|
97
|
+
def get_machine_from_work(self, work: Externalv1Lightningwork, org_id: str) -> Machine:
|
|
98
98
|
spec: V1LightningworkSpec = work.spec
|
|
99
99
|
# prefer user-requested config if specified
|
|
100
100
|
user_requested_compute_config: V1UserRequestedComputeConfig = spec.user_requested_compute_config
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
accelerators = self._get_machines_for_cloud_account(
|
|
102
|
+
teamspace_id=work.project_id,
|
|
103
|
+
cloud_account_id=spec.cluster_id,
|
|
104
|
+
org_id=org_id,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
identifier = None
|
|
108
|
+
|
|
109
|
+
if user_requested_compute_config and user_requested_compute_config.name:
|
|
110
|
+
identifier = user_requested_compute_config.name
|
|
111
|
+
else:
|
|
112
|
+
identifier = spec.compute_config.instance_type
|
|
113
|
+
|
|
114
|
+
for accelerator in accelerators:
|
|
115
|
+
if identifier in (
|
|
116
|
+
accelerator.slug,
|
|
117
|
+
accelerator.slug_multi_cloud,
|
|
118
|
+
accelerator.instance_id,
|
|
119
|
+
):
|
|
120
|
+
return Machine.from_str(accelerator.slug_multi_cloud)
|
|
121
|
+
|
|
122
|
+
return Machine.from_str(identifier)
|
|
104
123
|
|
|
105
|
-
|
|
124
|
+
def _get_machines_for_cloud_account(
|
|
125
|
+
self, teamspace_id: str, cloud_account_id: str, org_id: str
|
|
126
|
+
) -> List[V1ClusterAccelerator]:
|
|
127
|
+
from lightning_sdk.api.cloud_account_api import CloudAccountApi
|
|
128
|
+
|
|
129
|
+
cloud_account_api = CloudAccountApi()
|
|
130
|
+
accelerators = cloud_account_api.list_cloud_account_accelerators(
|
|
131
|
+
teamspace_id=teamspace_id,
|
|
132
|
+
cloud_account_id=cloud_account_id,
|
|
133
|
+
org_id=org_id,
|
|
134
|
+
)
|
|
135
|
+
if not accelerators:
|
|
136
|
+
return []
|
|
137
|
+
|
|
138
|
+
return list(filter(lambda acc: acc.enabled, accelerators.accelerator))
|
|
106
139
|
|
|
107
140
|
def get_studio_name(self, job: Externalv1LightningappInstance) -> str:
|
|
108
141
|
cs: V1CloudSpace = self._client.cloud_space_service_get_cloud_space(
|
|
@@ -215,7 +248,7 @@ class JobApiV2:
|
|
|
215
248
|
path_mappings: Optional[Dict[str, str]],
|
|
216
249
|
artifacts_local: Optional[str], # deprecated in favor of path_mappings
|
|
217
250
|
artifacts_remote: Optional[str], # deprecated in favor of path_mappings
|
|
218
|
-
max_runtime: Optional[
|
|
251
|
+
max_runtime: Optional[int] = None,
|
|
219
252
|
) -> V1Job:
|
|
220
253
|
body = self._create_job_body(
|
|
221
254
|
name=name,
|
|
@@ -380,11 +413,41 @@ class JobApiV2:
|
|
|
380
413
|
return Status.Stopping
|
|
381
414
|
return Status.Pending
|
|
382
415
|
|
|
383
|
-
def _get_job_machine_from_spec(self, spec: V1JobSpec) -> "Machine":
|
|
384
|
-
|
|
385
|
-
|
|
416
|
+
def _get_job_machine_from_spec(self, spec: V1JobSpec, teamspace_id: str, org_id: str) -> "Machine":
|
|
417
|
+
accelerators = self._get_machines_for_cloud_account(
|
|
418
|
+
teamspace_id=teamspace_id,
|
|
419
|
+
cloud_account_id=spec.cluster_id,
|
|
420
|
+
org_id=org_id,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
for accelerator in accelerators:
|
|
424
|
+
possible_identifiers = (
|
|
425
|
+
accelerator.slug,
|
|
426
|
+
accelerator.slug_multi_cloud,
|
|
427
|
+
accelerator.instance_id,
|
|
428
|
+
)
|
|
429
|
+
if (spec.instance_name and spec.instance_name in possible_identifiers) or (
|
|
430
|
+
spec.instance_type and spec.instance_type in possible_identifiers
|
|
431
|
+
):
|
|
432
|
+
return Machine.from_str(accelerator.slug_multi_cloud)
|
|
433
|
+
|
|
434
|
+
return Machine.from_str(spec.instance_name or spec.instance_type)
|
|
435
|
+
|
|
436
|
+
def _get_machines_for_cloud_account(
|
|
437
|
+
self, teamspace_id: str, cloud_account_id: str, org_id: str
|
|
438
|
+
) -> List[V1ClusterAccelerator]:
|
|
439
|
+
from lightning_sdk.api.cloud_account_api import CloudAccountApi
|
|
440
|
+
|
|
441
|
+
cloud_account_api = CloudAccountApi()
|
|
442
|
+
accelerators = cloud_account_api.list_cloud_account_accelerators(
|
|
443
|
+
teamspace_id=teamspace_id,
|
|
444
|
+
cloud_account_id=cloud_account_id,
|
|
445
|
+
org_id=org_id,
|
|
446
|
+
)
|
|
447
|
+
if not accelerators:
|
|
448
|
+
return []
|
|
386
449
|
|
|
387
|
-
return
|
|
450
|
+
return list(filter(lambda acc: acc.enabled, accelerators.accelerator))
|
|
388
451
|
|
|
389
452
|
def get_total_cost(self, job: V1Job) -> float:
|
|
390
453
|
return job.total_cost
|
lightning_sdk/api/llm_api.py
CHANGED
|
@@ -98,7 +98,7 @@ class LLMApi:
|
|
|
98
98
|
{"contentType": "text", "parts": [prompt]},
|
|
99
99
|
],
|
|
100
100
|
},
|
|
101
|
-
"
|
|
101
|
+
"max_tokens": max_completion_tokens,
|
|
102
102
|
"conversation_id": conversation_id,
|
|
103
103
|
"billing_project_id": billing_project_id,
|
|
104
104
|
"name": name,
|
lightning_sdk/api/mmt_api.py
CHANGED
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
import time
|
|
3
3
|
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
|
4
4
|
|
|
5
|
-
from lightning_sdk.api.job_api import JobApiV1
|
|
5
|
+
from lightning_sdk.api.job_api import JobApiV1, V1ClusterAccelerator
|
|
6
6
|
from lightning_sdk.api.utils import (
|
|
7
7
|
_create_app,
|
|
8
8
|
_machine_to_compute_name,
|
|
@@ -247,11 +247,41 @@ class MMTApiV2:
|
|
|
247
247
|
def get_command(self, job: V1MultiMachineJob) -> str:
|
|
248
248
|
return job.spec.command
|
|
249
249
|
|
|
250
|
-
def _get_job_machine_from_spec(self, spec: V1JobSpec) -> "Machine":
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
def _get_job_machine_from_spec(self, spec: V1JobSpec, teamspace_id: str, org_id: str) -> "Machine":
|
|
251
|
+
accelerators = self._get_machines_for_cloud_account(
|
|
252
|
+
teamspace_id=teamspace_id,
|
|
253
|
+
cloud_account_id=spec.cluster_id,
|
|
254
|
+
org_id=org_id,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
for accelerator in accelerators:
|
|
258
|
+
possible_identifiers = (
|
|
259
|
+
accelerator.slug,
|
|
260
|
+
accelerator.slug_multi_cloud,
|
|
261
|
+
accelerator.instance_id,
|
|
262
|
+
)
|
|
263
|
+
if (spec.instance_name and spec.instance_name in possible_identifiers) or (
|
|
264
|
+
spec.instance_type and spec.instance_type in possible_identifiers
|
|
265
|
+
):
|
|
266
|
+
return Machine.from_str(accelerator.slug_multi_cloud)
|
|
267
|
+
|
|
268
|
+
return Machine.from_str(spec.instance_name or spec.instance_type)
|
|
269
|
+
|
|
270
|
+
def _get_machines_for_cloud_account(
|
|
271
|
+
self, teamspace_id: str, cloud_account_id: str, org_id: str
|
|
272
|
+
) -> List[V1ClusterAccelerator]:
|
|
273
|
+
from lightning_sdk.api.cloud_account_api import CloudAccountApi
|
|
274
|
+
|
|
275
|
+
cloud_account_api = CloudAccountApi()
|
|
276
|
+
accelerators = cloud_account_api.list_cloud_account_accelerators(
|
|
277
|
+
teamspace_id=teamspace_id,
|
|
278
|
+
cloud_account_id=cloud_account_id,
|
|
279
|
+
org_id=org_id,
|
|
280
|
+
)
|
|
281
|
+
if not accelerators:
|
|
282
|
+
return []
|
|
253
283
|
|
|
254
|
-
return
|
|
284
|
+
return list(filter(lambda acc: acc.enabled, accelerators.accelerator))
|
|
255
285
|
|
|
256
286
|
def get_total_cost(self, job: V1MultiMachineJob) -> float:
|
|
257
287
|
return job.total_cost
|
lightning_sdk/api/studio_api.py
CHANGED
|
@@ -4,7 +4,7 @@ import tempfile
|
|
|
4
4
|
import time
|
|
5
5
|
import zipfile
|
|
6
6
|
from threading import Event, Thread
|
|
7
|
-
from typing import Any, Dict, Generator, Mapping, Optional, Tuple, Union
|
|
7
|
+
from typing import Any, Dict, Generator, List, Mapping, Optional, Tuple, Union
|
|
8
8
|
|
|
9
9
|
import backoff
|
|
10
10
|
import requests
|
|
@@ -37,6 +37,7 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
37
37
|
V1CloudSpaceSeedFile,
|
|
38
38
|
V1CloudSpaceSourceType,
|
|
39
39
|
V1CloudSpaceState,
|
|
40
|
+
V1ClusterAccelerator,
|
|
40
41
|
V1EndpointType,
|
|
41
42
|
V1GetCloudSpaceInstanceStatusResponse,
|
|
42
43
|
V1GetLongRunningCommandInCloudSpaceResponse,
|
|
@@ -267,12 +268,24 @@ class StudioApi:
|
|
|
267
268
|
break
|
|
268
269
|
time.sleep(1)
|
|
269
270
|
|
|
270
|
-
def get_machine(self, studio_id: str, teamspace_id: str) -> Machine:
|
|
271
|
+
def get_machine(self, studio_id: str, teamspace_id: str, cloud_account_id: str, org_id: str) -> Machine:
|
|
271
272
|
"""Get the current machine type the given Studio is running on."""
|
|
272
273
|
response: V1CloudSpaceInstanceConfig = self._client.cloud_space_service_get_cloud_space_instance_config(
|
|
273
274
|
project_id=teamspace_id, id=studio_id
|
|
274
275
|
)
|
|
275
|
-
|
|
276
|
+
accelerators = self._get_machines_for_cloud_account(
|
|
277
|
+
teamspace_id=teamspace_id, cloud_account_id=cloud_account_id, org_id=org_id
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
for accelerator in accelerators:
|
|
281
|
+
if response.compute_config.name in (
|
|
282
|
+
accelerator.slug,
|
|
283
|
+
accelerator.slug_multi_cloud,
|
|
284
|
+
accelerator.instance_id,
|
|
285
|
+
):
|
|
286
|
+
return Machine.from_str(accelerator.slug_multi_cloud)
|
|
287
|
+
|
|
288
|
+
return Machine.from_str(response.compute_config.name)
|
|
276
289
|
|
|
277
290
|
def get_interruptible(self, studio_id: str, teamspace_id: str) -> bool:
|
|
278
291
|
"""Get whether the Studio is running on a interruptible instance."""
|
|
@@ -282,6 +295,22 @@ class StudioApi:
|
|
|
282
295
|
|
|
283
296
|
return response.compute_config.spot
|
|
284
297
|
|
|
298
|
+
def _get_machines_for_cloud_account(
|
|
299
|
+
self, teamspace_id: str, cloud_account_id: str, org_id: str
|
|
300
|
+
) -> List[V1ClusterAccelerator]:
|
|
301
|
+
from lightning_sdk.api.cloud_account_api import CloudAccountApi
|
|
302
|
+
|
|
303
|
+
cloud_account_api = CloudAccountApi()
|
|
304
|
+
accelerators = cloud_account_api.list_cloud_account_accelerators(
|
|
305
|
+
teamspace_id=teamspace_id,
|
|
306
|
+
cloud_account_id=cloud_account_id,
|
|
307
|
+
org_id=org_id,
|
|
308
|
+
)
|
|
309
|
+
if not accelerators:
|
|
310
|
+
return []
|
|
311
|
+
|
|
312
|
+
return list(filter(lambda acc: acc.enabled, accelerators.accelerator))
|
|
313
|
+
|
|
285
314
|
def _get_detached_command_status(
|
|
286
315
|
self, studio_id: str, teamspace_id: str, session_id: str
|
|
287
316
|
) -> V1GetLongRunningCommandInCloudSpaceResponse:
|
lightning_sdk/api/utils.py
CHANGED
|
@@ -331,7 +331,9 @@ class _DummyResponse:
|
|
|
331
331
|
|
|
332
332
|
def _machine_to_compute_name(machine: Union[Machine, str]) -> str:
|
|
333
333
|
if isinstance(machine, Machine):
|
|
334
|
-
|
|
334
|
+
if machine.instance_type is not None:
|
|
335
|
+
return machine.instance_type
|
|
336
|
+
return machine.slug
|
|
335
337
|
return machine
|
|
336
338
|
|
|
337
339
|
|
|
@@ -352,7 +354,9 @@ def _get_registry_url() -> str:
|
|
|
352
354
|
|
|
353
355
|
|
|
354
356
|
def _sanitize_studio_remote_path(path: str, studio_id: str) -> str:
|
|
355
|
-
|
|
357
|
+
path = path.replace("/teamspace/studios/this_studio/", "")
|
|
358
|
+
root = f"/cloudspaces/{studio_id}/code/content/"
|
|
359
|
+
return os.path.join(root, path)
|
|
356
360
|
|
|
357
361
|
|
|
358
362
|
def _resolve_teamspace_remote_path(path: str) -> str:
|
lightning_sdk/cli/create.py
CHANGED
|
@@ -12,7 +12,9 @@ from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
|
12
12
|
from lightning_sdk.machine import CloudProvider
|
|
13
13
|
from lightning_sdk.utils.resolve import _resolve_deprecated_provider
|
|
14
14
|
|
|
15
|
-
_MACHINE_VALUES = tuple(
|
|
15
|
+
_MACHINE_VALUES = tuple(
|
|
16
|
+
[machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine) and machine._include_in_cli]
|
|
17
|
+
)
|
|
16
18
|
_PROVIDER_VALUES = tuple([provider.value for provider in CloudProvider])
|
|
17
19
|
|
|
18
20
|
|
|
@@ -25,7 +25,9 @@ from lightning_sdk.cli.deploy._auth import (
|
|
|
25
25
|
from lightning_sdk.cli.deploy.devbox import _handle_devbox
|
|
26
26
|
from lightning_sdk.serve import _LitServeDeployer
|
|
27
27
|
|
|
28
|
-
_MACHINE_VALUES = tuple(
|
|
28
|
+
_MACHINE_VALUES = tuple(
|
|
29
|
+
[machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine) and machine._include_in_cli]
|
|
30
|
+
)
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class _ServeGroup(click.Group):
|
lightning_sdk/cli/download.py
CHANGED
|
@@ -17,6 +17,27 @@ from lightning_sdk.studio import Studio
|
|
|
17
17
|
from lightning_sdk.utils.resolve import _get_authed_user, skip_studio_init
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _expand_remote_path(path: str) -> str:
|
|
21
|
+
"""Expand and normalize remote CLI paths.
|
|
22
|
+
|
|
23
|
+
- Strips leading `~/` or `~`
|
|
24
|
+
- Expands `~` to the user's home but returns relative to it
|
|
25
|
+
- Returns an empty string if path is empty or `~`
|
|
26
|
+
"""
|
|
27
|
+
if not path:
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
local_home = os.path.expanduser("~")
|
|
31
|
+
|
|
32
|
+
# Expand to absolute path and remove the local home prefix if present
|
|
33
|
+
path = os.path.expanduser(path)
|
|
34
|
+
if path.startswith(local_home):
|
|
35
|
+
path = path[len(local_home) :]
|
|
36
|
+
|
|
37
|
+
# Remove any leading "/" or "~" remnants
|
|
38
|
+
return path.lstrip("/~")
|
|
39
|
+
|
|
40
|
+
|
|
20
41
|
@click.group(name="download")
|
|
21
42
|
def download() -> None:
|
|
22
43
|
"""Download resources from Lightning AI."""
|
|
@@ -86,17 +107,20 @@ def folder(
|
|
|
86
107
|
raise ValueError("Either --studio or --teamspace must be provided, not both")
|
|
87
108
|
|
|
88
109
|
if studio:
|
|
110
|
+
path = _expand_remote_path(path)
|
|
89
111
|
resolved_downloader = _resolve_studio(studio)
|
|
90
112
|
elif teamspace:
|
|
91
113
|
menu = _TeamspacesMenu()
|
|
92
114
|
resolved_downloader = menu._resolve_teamspace(teamspace)
|
|
115
|
+
else:
|
|
116
|
+
raise ValueError("Either --studio or --teamspace must be provided")
|
|
93
117
|
|
|
94
118
|
if not path:
|
|
95
119
|
local_path /= resolved_downloader.name
|
|
96
120
|
path = ""
|
|
97
121
|
|
|
98
122
|
try:
|
|
99
|
-
if not path:
|
|
123
|
+
if not path and teamspace:
|
|
100
124
|
raise FileNotFoundError()
|
|
101
125
|
resolved_downloader.download_folder(remote_path=path, target_path=str(local_path))
|
|
102
126
|
except Exception as e:
|
lightning_sdk/cli/entrypoint.py
CHANGED
|
@@ -50,7 +50,9 @@ def _notify_exception(exception_type: Type[BaseException], value: BaseException,
|
|
|
50
50
|
renderables.append(Text("\n\nFull traceback:\n", style="bold yellow"))
|
|
51
51
|
renderables.append(Syntax(tb_text, "python", theme="monokai", line_numbers=False, word_wrap=True))
|
|
52
52
|
else:
|
|
53
|
-
renderables.append(Text("\n\
|
|
53
|
+
renderables.append(Text("\n\n🐞 To view the full traceback, set: LIGHTNING_DEBUG=1"))
|
|
54
|
+
|
|
55
|
+
renderables.append(Text("\n📘 Need help? Run: lightning <command> --help", style="cyan"))
|
|
54
56
|
|
|
55
57
|
console.print(Panel(Group(*renderables), title="⚡ Lightning CLI Error", border_style="red"))
|
|
56
58
|
|
lightning_sdk/cli/list.py
CHANGED
|
@@ -274,7 +274,11 @@ def machines() -> None:
|
|
|
274
274
|
table.add_column("Name")
|
|
275
275
|
|
|
276
276
|
# Get all machine types from the enum
|
|
277
|
-
machine_types = [
|
|
277
|
+
machine_types = [
|
|
278
|
+
name
|
|
279
|
+
for name in dir(Machine)
|
|
280
|
+
if isinstance(getattr(Machine, name), Machine) and getattr(Machine, name)._include_in_cli
|
|
281
|
+
]
|
|
278
282
|
|
|
279
283
|
# Add rows to table
|
|
280
284
|
for name in sorted(machine_types):
|
lightning_sdk/cli/run.py
CHANGED
|
@@ -8,7 +8,9 @@ from lightning_sdk.machine import Machine
|
|
|
8
8
|
from lightning_sdk.mmt import MMT
|
|
9
9
|
from lightning_sdk.teamspace import Teamspace
|
|
10
10
|
|
|
11
|
-
_MACHINE_VALUES = tuple(
|
|
11
|
+
_MACHINE_VALUES = tuple(
|
|
12
|
+
[machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine) and machine._include_in_cli]
|
|
13
|
+
)
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
@click.group(name="run")
|
lightning_sdk/cli/start.py
CHANGED
|
@@ -6,7 +6,9 @@ from lightning_sdk import Machine, Studio
|
|
|
6
6
|
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
7
7
|
from lightning_sdk.machine import CloudProvider
|
|
8
8
|
|
|
9
|
-
_MACHINE_VALUES = tuple(
|
|
9
|
+
_MACHINE_VALUES = tuple(
|
|
10
|
+
[machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine) and machine._include_in_cli]
|
|
11
|
+
)
|
|
10
12
|
_PROVIDER_VALUES = tuple([provider.value for provider in CloudProvider])
|
|
11
13
|
|
|
12
14
|
|
lightning_sdk/cli/switch.py
CHANGED
|
@@ -5,7 +5,9 @@ import click
|
|
|
5
5
|
from lightning_sdk import Machine, Studio
|
|
6
6
|
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
7
7
|
|
|
8
|
-
_MACHINE_VALUES = tuple(
|
|
8
|
+
_MACHINE_VALUES = tuple(
|
|
9
|
+
[machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine) and machine._include_in_cli]
|
|
10
|
+
)
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@click.group("switch")
|
lightning_sdk/job/v2.py
CHANGED
|
@@ -4,6 +4,7 @@ from lightning_sdk.api.job_api import JobApiV2
|
|
|
4
4
|
from lightning_sdk.api.utils import _get_cloud_url
|
|
5
5
|
from lightning_sdk.job.base import _BaseJob
|
|
6
6
|
from lightning_sdk.status import Status
|
|
7
|
+
from lightning_sdk.utils.resolve import _get_org_id
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from lightning_sdk.machine import CloudProvider, Machine
|
|
@@ -165,7 +166,12 @@ class _JobV2(_BaseJob):
|
|
|
165
166
|
def machine(self) -> Union["Machine", str]:
|
|
166
167
|
"""The machine type the job is running on."""
|
|
167
168
|
# only fetch the job it it hasn't been fetched yet as machine cannot change over time
|
|
168
|
-
|
|
169
|
+
|
|
170
|
+
return self._job_api._get_job_machine_from_spec(
|
|
171
|
+
self._guaranteed_job.spec,
|
|
172
|
+
self.teamspace.id,
|
|
173
|
+
_get_org_id(self.teamspace),
|
|
174
|
+
)
|
|
169
175
|
|
|
170
176
|
@property
|
|
171
177
|
def artifact_path(self) -> Optional[str]:
|
lightning_sdk/job/work.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Any, Optional, Protocol, Union
|
|
2
2
|
|
|
3
3
|
from lightning_sdk.api.job_api import JobApiV1
|
|
4
|
+
from lightning_sdk.utils.resolve import _get_org_id
|
|
4
5
|
|
|
5
6
|
if TYPE_CHECKING:
|
|
6
7
|
from lightning_sdk.job.base import MachineDict
|
|
@@ -52,7 +53,10 @@ class Work:
|
|
|
52
53
|
|
|
53
54
|
@property
|
|
54
55
|
def machine(self) -> Union["Machine", str]:
|
|
55
|
-
return self._job_api.get_machine_from_work(
|
|
56
|
+
return self._job_api.get_machine_from_work(
|
|
57
|
+
self._guaranteed_work,
|
|
58
|
+
org_id=_get_org_id(self._teamspace),
|
|
59
|
+
)
|
|
56
60
|
|
|
57
61
|
@property
|
|
58
62
|
def artifact_path(self) -> Optional[str]:
|
|
@@ -243,6 +243,7 @@ from lightning_sdk.lightning_cloud.openapi.models.user_id_affiliatelinks_body im
|
|
|
243
243
|
from lightning_sdk.lightning_cloud.openapi.models.user_id_membershiprolebindings_body import UserIdMembershiprolebindingsBody
|
|
244
244
|
from lightning_sdk.lightning_cloud.openapi.models.user_id_membershiprolebindings_body1 import UserIdMembershiprolebindingsBody1
|
|
245
245
|
from lightning_sdk.lightning_cloud.openapi.models.user_id_upgradetrigger_body import UserIdUpgradetriggerBody
|
|
246
|
+
from lightning_sdk.lightning_cloud.openapi.models.user_user_id_body import UserUserIdBody
|
|
246
247
|
from lightning_sdk.lightning_cloud.openapi.models.v1_aws_cluster_credentials import V1AWSClusterCredentials
|
|
247
248
|
from lightning_sdk.lightning_cloud.openapi.models.v1_aws_direct_v1 import V1AWSDirectV1
|
|
248
249
|
from lightning_sdk.lightning_cloud.openapi.models.v1_aws_direct_v1_status import V1AWSDirectV1Status
|
|
@@ -325,6 +326,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_version import
|
|
|
325
326
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_version_publication import V1CloudSpaceVersionPublication
|
|
326
327
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloudflare_v1 import V1CloudflareV1
|
|
327
328
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloudy_expert import V1CloudyExpert
|
|
329
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_cloudy_settings import V1CloudySettings
|
|
328
330
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_accelerator import V1ClusterAccelerator
|
|
329
331
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_availability import V1ClusterAvailability
|
|
330
332
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_capacity_reservation import V1ClusterCapacityReservation
|
|
@@ -533,6 +535,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_filesystem_work import V1Fi
|
|
|
533
535
|
from lightning_sdk.lightning_cloud.openapi.models.v1_find_capacity_block_offering_response import V1FindCapacityBlockOfferingResponse
|
|
534
536
|
from lightning_sdk.lightning_cloud.openapi.models.v1_flowserver import V1Flowserver
|
|
535
537
|
from lightning_sdk.lightning_cloud.openapi.models.v1_folder_index_status import V1FolderIndexStatus
|
|
538
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_function_call import V1FunctionCall
|
|
536
539
|
from lightning_sdk.lightning_cloud.openapi.models.v1_function_tool import V1FunctionTool
|
|
537
540
|
from lightning_sdk.lightning_cloud.openapi.models.v1_gcp_direct_vpc import V1GCPDirectVPC
|
|
538
541
|
from lightning_sdk.lightning_cloud.openapi.models.v1_gcs_folder_data_connection import V1GCSFolderDataConnection
|
|
@@ -555,6 +558,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_instance_sy
|
|
|
555
558
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_size_response import V1GetCloudSpaceSizeResponse
|
|
556
559
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cluster_accelerator_demand_response import V1GetClusterAcceleratorDemandResponse
|
|
557
560
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cluster_credentials_response import V1GetClusterCredentialsResponse
|
|
561
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cluster_health_response import V1GetClusterHealthResponse
|
|
558
562
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_deployment_routing_telemetry_aggregated_response import V1GetDeploymentRoutingTelemetryAggregatedResponse
|
|
559
563
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_deployment_routing_telemetry_content_response import V1GetDeploymentRoutingTelemetryContentResponse
|
|
560
564
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_deployment_routing_telemetry_response import V1GetDeploymentRoutingTelemetryResponse
|
|
@@ -729,7 +733,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_list_project_memberships_re
|
|
|
729
733
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_project_roles_response import V1ListProjectRolesResponse
|
|
730
734
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_published_cloud_spaces_response import V1ListPublishedCloudSpacesResponse
|
|
731
735
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_published_deployment_templates_response import V1ListPublishedDeploymentTemplatesResponse
|
|
732
|
-
from lightning_sdk.lightning_cloud.openapi.models.
|
|
736
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_list_published_managed_endpoints_response import V1ListPublishedManagedEndpointsResponse
|
|
733
737
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_quests_response import V1ListQuestsResponse
|
|
734
738
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_slurm_cluster_users_response import V1ListSLURMClusterUsersResponse
|
|
735
739
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_ssh_public_keys_response import V1ListSSHPublicKeysResponse
|
|
@@ -930,6 +934,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_telemetry import V1Telemetr
|
|
|
930
934
|
from lightning_sdk.lightning_cloud.openapi.models.v1_timestamp_code_telemetry import V1TimestampCodeTelemetry
|
|
931
935
|
from lightning_sdk.lightning_cloud.openapi.models.v1_token_usage import V1TokenUsage
|
|
932
936
|
from lightning_sdk.lightning_cloud.openapi.models.v1_tool import V1Tool
|
|
937
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_tool_call import V1ToolCall
|
|
933
938
|
from lightning_sdk.lightning_cloud.openapi.models.v1_transaction import V1Transaction
|
|
934
939
|
from lightning_sdk.lightning_cloud.openapi.models.v1_transfer_cloud_space_response import V1TransferCloudSpaceResponse
|
|
935
940
|
from lightning_sdk.lightning_cloud.openapi.models.v1_transfer_org_balance_response import V1TransferOrgBalanceResponse
|