lightning-sdk 2025.8.18.post0__py3-none-any.whl → 2025.8.21__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/llm_api.py +6 -2
- lightning_sdk/api/studio_api.py +113 -36
- lightning_sdk/api/utils.py +108 -18
- lightning_sdk/cli/legacy/create.py +9 -11
- lightning_sdk/cli/legacy/start.py +1 -0
- lightning_sdk/cli/legacy/switch.py +1 -0
- lightning_sdk/cli/studio/start.py +1 -0
- lightning_sdk/cli/studio/switch.py +1 -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 +113 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +2 -0
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +15 -15
- lightning_sdk/lightning_cloud/openapi/models/id_codeconfig_body.py +3 -81
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/project_id_storage_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/storage_complete_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body1.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_metrics.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_list_aggregated_pod_metrics_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_node_metrics.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_pod_metrics.py +157 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project_cluster_binding.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_quote_annual_upsell_response.py +201 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_cloud_space_instance_config_request.py +3 -81
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +1 -131
- lightning_sdk/llm/llm.py +2 -2
- lightning_sdk/studio.py +39 -6
- lightning_sdk/utils/progress.py +284 -0
- {lightning_sdk-2025.8.18.post0.dist-info → lightning_sdk-2025.8.21.dist-info}/METADATA +1 -1
- {lightning_sdk-2025.8.18.post0.dist-info → lightning_sdk-2025.8.21.dist-info}/RECORD +37 -34
- {lightning_sdk-2025.8.18.post0.dist-info → lightning_sdk-2025.8.21.dist-info}/LICENSE +0 -0
- {lightning_sdk-2025.8.18.post0.dist-info → lightning_sdk-2025.8.21.dist-info}/WHEEL +0 -0
- {lightning_sdk-2025.8.18.post0.dist-info → lightning_sdk-2025.8.21.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-2025.8.18.post0.dist-info → lightning_sdk-2025.8.21.dist-info}/top_level.txt +0 -0
|
@@ -41,7 +41,6 @@ class V1UserFeatures(object):
|
|
|
41
41
|
and the value is json key in definition.
|
|
42
42
|
"""
|
|
43
43
|
swagger_types = {
|
|
44
|
-
'add_data_v2': 'bool',
|
|
45
44
|
'affiliate_links': 'bool',
|
|
46
45
|
'agents_v2': 'bool',
|
|
47
46
|
'ai_hub_monetization': 'bool',
|
|
@@ -75,13 +74,11 @@ class V1UserFeatures(object):
|
|
|
75
74
|
'f241': 'bool',
|
|
76
75
|
'f242': 'bool',
|
|
77
76
|
'f243': 'bool',
|
|
78
|
-
'f244': 'bool',
|
|
79
77
|
'f245': 'bool',
|
|
80
78
|
'fair_share': 'bool',
|
|
81
79
|
'featured_studios_admin': 'bool',
|
|
82
80
|
'gcp_overprovisioning': 'bool',
|
|
83
81
|
'gcs_connections_optimized': 'bool',
|
|
84
|
-
'gcs_folders': 'bool',
|
|
85
82
|
'instant_capacity_reservation': 'bool',
|
|
86
83
|
'job_artifacts_v2': 'bool',
|
|
87
84
|
'kubernetes_cluster_ui': 'bool',
|
|
@@ -109,7 +106,6 @@ class V1UserFeatures(object):
|
|
|
109
106
|
'product_license': 'bool',
|
|
110
107
|
'project_selector': 'bool',
|
|
111
108
|
'publish_pipelines': 'bool',
|
|
112
|
-
'r2_data_connections': 'bool',
|
|
113
109
|
'reserved_machines_tab': 'bool',
|
|
114
110
|
'restartable_jobs': 'bool',
|
|
115
111
|
'runnable_public_studio_page': 'bool',
|
|
@@ -123,7 +119,6 @@ class V1UserFeatures(object):
|
|
|
123
119
|
'studio_sharing_v2': 'bool',
|
|
124
120
|
'studio_version_visibility': 'bool',
|
|
125
121
|
'trainium2': 'bool',
|
|
126
|
-
'use_internal_data_connection_mounts': 'bool',
|
|
127
122
|
'use_rclone_mounts_only': 'bool',
|
|
128
123
|
'vultr': 'bool',
|
|
129
124
|
'weka': 'bool',
|
|
@@ -131,7 +126,6 @@ class V1UserFeatures(object):
|
|
|
131
126
|
}
|
|
132
127
|
|
|
133
128
|
attribute_map = {
|
|
134
|
-
'add_data_v2': 'addDataV2',
|
|
135
129
|
'affiliate_links': 'affiliateLinks',
|
|
136
130
|
'agents_v2': 'agentsV2',
|
|
137
131
|
'ai_hub_monetization': 'aiHubMonetization',
|
|
@@ -165,13 +159,11 @@ class V1UserFeatures(object):
|
|
|
165
159
|
'f241': 'f241',
|
|
166
160
|
'f242': 'f242',
|
|
167
161
|
'f243': 'f243',
|
|
168
|
-
'f244': 'f244',
|
|
169
162
|
'f245': 'f245',
|
|
170
163
|
'fair_share': 'fairShare',
|
|
171
164
|
'featured_studios_admin': 'featuredStudiosAdmin',
|
|
172
165
|
'gcp_overprovisioning': 'gcpOverprovisioning',
|
|
173
166
|
'gcs_connections_optimized': 'gcsConnectionsOptimized',
|
|
174
|
-
'gcs_folders': 'gcsFolders',
|
|
175
167
|
'instant_capacity_reservation': 'instantCapacityReservation',
|
|
176
168
|
'job_artifacts_v2': 'jobArtifactsV2',
|
|
177
169
|
'kubernetes_cluster_ui': 'kubernetesClusterUi',
|
|
@@ -199,7 +191,6 @@ class V1UserFeatures(object):
|
|
|
199
191
|
'product_license': 'productLicense',
|
|
200
192
|
'project_selector': 'projectSelector',
|
|
201
193
|
'publish_pipelines': 'publishPipelines',
|
|
202
|
-
'r2_data_connections': 'r2DataConnections',
|
|
203
194
|
'reserved_machines_tab': 'reservedMachinesTab',
|
|
204
195
|
'restartable_jobs': 'restartableJobs',
|
|
205
196
|
'runnable_public_studio_page': 'runnablePublicStudioPage',
|
|
@@ -213,16 +204,14 @@ class V1UserFeatures(object):
|
|
|
213
204
|
'studio_sharing_v2': 'studioSharingV2',
|
|
214
205
|
'studio_version_visibility': 'studioVersionVisibility',
|
|
215
206
|
'trainium2': 'trainium2',
|
|
216
|
-
'use_internal_data_connection_mounts': 'useInternalDataConnectionMounts',
|
|
217
207
|
'use_rclone_mounts_only': 'useRcloneMountsOnly',
|
|
218
208
|
'vultr': 'vultr',
|
|
219
209
|
'weka': 'weka',
|
|
220
210
|
'writable_s3_connections': 'writableS3Connections'
|
|
221
211
|
}
|
|
222
212
|
|
|
223
|
-
def __init__(self,
|
|
213
|
+
def __init__(self, affiliate_links: 'bool' =None, agents_v2: 'bool' =None, ai_hub_monetization: 'bool' =None, auto_fast_load: 'bool' =None, auto_join_orgs: 'bool' =None, b2c_experience: 'bool' =None, byo_machine_type: 'bool' =None, cap_add: 'list[str]' =None, cap_drop: 'list[str]' =None, capacity_reservation_byoc: 'bool' =None, capacity_reservation_dry_run: 'bool' =None, chat_models: 'bool' =None, cloudspace_schedules: 'bool' =None, code_tab: 'bool' =None, collab_screen_sharing: 'bool' =None, control_center_monitoring: 'bool' =None, cost_attribution_settings: 'bool' =None, custom_app_domain: 'bool' =None, datasets: 'bool' =None, default_one_cluster: 'bool' =None, deployment_persistent_disk: 'bool' =None, drive_v2: 'bool' =None, enterprise_compute_admin: 'bool' =None, f227: 'bool' =None, f234: 'bool' =None, f236: 'bool' =None, f237: 'bool' =None, f238: 'bool' =None, f239: 'bool' =None, f240: 'bool' =None, f241: 'bool' =None, f242: 'bool' =None, f243: 'bool' =None, f245: 'bool' =None, fair_share: 'bool' =None, featured_studios_admin: 'bool' =None, gcp_overprovisioning: 'bool' =None, gcs_connections_optimized: 'bool' =None, instant_capacity_reservation: 'bool' =None, job_artifacts_v2: 'bool' =None, kubernetes_cluster_ui: 'bool' =None, kubernetes_clusters: 'bool' =None, landing_studios: 'bool' =None, lit_logger: 'bool' =None, marketplace: 'bool' =None, mmt_fault_tolerance: 'bool' =None, mmt_strategy_selector: 'bool' =None, model_api_dashboard: 'bool' =None, multiple_studio_versions: 'bool' =None, nerf_fs_nonpaying: 'bool' =None, onboarding_v2: 'bool' =None, org_level_member_permissions: 'bool' =None, org_usage_limits: 'bool' =None, persistent_disk: 'bool' =None, plugin_distributed: 'bool' =None, plugin_inference: 'bool' =None, plugin_label_studio: 'bool' =None, plugin_langflow: 'bool' =None, plugin_python_profiler: 'bool' =None, plugin_sweeps: 'bool' =None, pricing_updates: 'bool' =None, product_generator: 'bool' =None, product_license: 'bool' =None, project_selector: 'bool' =None, publish_pipelines: 'bool' =None, reserved_machines_tab: 'bool' =None, restartable_jobs: 'bool' =None, runnable_public_studio_page: 'bool' =None, security_docs: 'bool' =None, show_dev_admin: 'bool' =None, single_wallet: 'bool' =None, slurm: 'bool' =None, specialised_studios: 'bool' =None, storage_overuse_deletion: 'bool' =None, studio_config: 'bool' =None, studio_sharing_v2: 'bool' =None, studio_version_visibility: 'bool' =None, trainium2: 'bool' =None, use_rclone_mounts_only: 'bool' =None, vultr: 'bool' =None, weka: 'bool' =None, writable_s3_connections: 'bool' =None): # noqa: E501
|
|
224
214
|
"""V1UserFeatures - a model defined in Swagger""" # noqa: E501
|
|
225
|
-
self._add_data_v2 = None
|
|
226
215
|
self._affiliate_links = None
|
|
227
216
|
self._agents_v2 = None
|
|
228
217
|
self._ai_hub_monetization = None
|
|
@@ -256,13 +245,11 @@ class V1UserFeatures(object):
|
|
|
256
245
|
self._f241 = None
|
|
257
246
|
self._f242 = None
|
|
258
247
|
self._f243 = None
|
|
259
|
-
self._f244 = None
|
|
260
248
|
self._f245 = None
|
|
261
249
|
self._fair_share = None
|
|
262
250
|
self._featured_studios_admin = None
|
|
263
251
|
self._gcp_overprovisioning = None
|
|
264
252
|
self._gcs_connections_optimized = None
|
|
265
|
-
self._gcs_folders = None
|
|
266
253
|
self._instant_capacity_reservation = None
|
|
267
254
|
self._job_artifacts_v2 = None
|
|
268
255
|
self._kubernetes_cluster_ui = None
|
|
@@ -290,7 +277,6 @@ class V1UserFeatures(object):
|
|
|
290
277
|
self._product_license = None
|
|
291
278
|
self._project_selector = None
|
|
292
279
|
self._publish_pipelines = None
|
|
293
|
-
self._r2_data_connections = None
|
|
294
280
|
self._reserved_machines_tab = None
|
|
295
281
|
self._restartable_jobs = None
|
|
296
282
|
self._runnable_public_studio_page = None
|
|
@@ -304,14 +290,11 @@ class V1UserFeatures(object):
|
|
|
304
290
|
self._studio_sharing_v2 = None
|
|
305
291
|
self._studio_version_visibility = None
|
|
306
292
|
self._trainium2 = None
|
|
307
|
-
self._use_internal_data_connection_mounts = None
|
|
308
293
|
self._use_rclone_mounts_only = None
|
|
309
294
|
self._vultr = None
|
|
310
295
|
self._weka = None
|
|
311
296
|
self._writable_s3_connections = None
|
|
312
297
|
self.discriminator = None
|
|
313
|
-
if add_data_v2 is not None:
|
|
314
|
-
self.add_data_v2 = add_data_v2
|
|
315
298
|
if affiliate_links is not None:
|
|
316
299
|
self.affiliate_links = affiliate_links
|
|
317
300
|
if agents_v2 is not None:
|
|
@@ -378,8 +361,6 @@ class V1UserFeatures(object):
|
|
|
378
361
|
self.f242 = f242
|
|
379
362
|
if f243 is not None:
|
|
380
363
|
self.f243 = f243
|
|
381
|
-
if f244 is not None:
|
|
382
|
-
self.f244 = f244
|
|
383
364
|
if f245 is not None:
|
|
384
365
|
self.f245 = f245
|
|
385
366
|
if fair_share is not None:
|
|
@@ -390,8 +371,6 @@ class V1UserFeatures(object):
|
|
|
390
371
|
self.gcp_overprovisioning = gcp_overprovisioning
|
|
391
372
|
if gcs_connections_optimized is not None:
|
|
392
373
|
self.gcs_connections_optimized = gcs_connections_optimized
|
|
393
|
-
if gcs_folders is not None:
|
|
394
|
-
self.gcs_folders = gcs_folders
|
|
395
374
|
if instant_capacity_reservation is not None:
|
|
396
375
|
self.instant_capacity_reservation = instant_capacity_reservation
|
|
397
376
|
if job_artifacts_v2 is not None:
|
|
@@ -446,8 +425,6 @@ class V1UserFeatures(object):
|
|
|
446
425
|
self.project_selector = project_selector
|
|
447
426
|
if publish_pipelines is not None:
|
|
448
427
|
self.publish_pipelines = publish_pipelines
|
|
449
|
-
if r2_data_connections is not None:
|
|
450
|
-
self.r2_data_connections = r2_data_connections
|
|
451
428
|
if reserved_machines_tab is not None:
|
|
452
429
|
self.reserved_machines_tab = reserved_machines_tab
|
|
453
430
|
if restartable_jobs is not None:
|
|
@@ -474,8 +451,6 @@ class V1UserFeatures(object):
|
|
|
474
451
|
self.studio_version_visibility = studio_version_visibility
|
|
475
452
|
if trainium2 is not None:
|
|
476
453
|
self.trainium2 = trainium2
|
|
477
|
-
if use_internal_data_connection_mounts is not None:
|
|
478
|
-
self.use_internal_data_connection_mounts = use_internal_data_connection_mounts
|
|
479
454
|
if use_rclone_mounts_only is not None:
|
|
480
455
|
self.use_rclone_mounts_only = use_rclone_mounts_only
|
|
481
456
|
if vultr is not None:
|
|
@@ -485,27 +460,6 @@ class V1UserFeatures(object):
|
|
|
485
460
|
if writable_s3_connections is not None:
|
|
486
461
|
self.writable_s3_connections = writable_s3_connections
|
|
487
462
|
|
|
488
|
-
@property
|
|
489
|
-
def add_data_v2(self) -> 'bool':
|
|
490
|
-
"""Gets the add_data_v2 of this V1UserFeatures. # noqa: E501
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
:return: The add_data_v2 of this V1UserFeatures. # noqa: E501
|
|
494
|
-
:rtype: bool
|
|
495
|
-
"""
|
|
496
|
-
return self._add_data_v2
|
|
497
|
-
|
|
498
|
-
@add_data_v2.setter
|
|
499
|
-
def add_data_v2(self, add_data_v2: 'bool'):
|
|
500
|
-
"""Sets the add_data_v2 of this V1UserFeatures.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
:param add_data_v2: The add_data_v2 of this V1UserFeatures. # noqa: E501
|
|
504
|
-
:type: bool
|
|
505
|
-
"""
|
|
506
|
-
|
|
507
|
-
self._add_data_v2 = add_data_v2
|
|
508
|
-
|
|
509
463
|
@property
|
|
510
464
|
def affiliate_links(self) -> 'bool':
|
|
511
465
|
"""Gets the affiliate_links of this V1UserFeatures. # noqa: E501
|
|
@@ -1199,27 +1153,6 @@ class V1UserFeatures(object):
|
|
|
1199
1153
|
|
|
1200
1154
|
self._f243 = f243
|
|
1201
1155
|
|
|
1202
|
-
@property
|
|
1203
|
-
def f244(self) -> 'bool':
|
|
1204
|
-
"""Gets the f244 of this V1UserFeatures. # noqa: E501
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
:return: The f244 of this V1UserFeatures. # noqa: E501
|
|
1208
|
-
:rtype: bool
|
|
1209
|
-
"""
|
|
1210
|
-
return self._f244
|
|
1211
|
-
|
|
1212
|
-
@f244.setter
|
|
1213
|
-
def f244(self, f244: 'bool'):
|
|
1214
|
-
"""Sets the f244 of this V1UserFeatures.
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
:param f244: The f244 of this V1UserFeatures. # noqa: E501
|
|
1218
|
-
:type: bool
|
|
1219
|
-
"""
|
|
1220
|
-
|
|
1221
|
-
self._f244 = f244
|
|
1222
|
-
|
|
1223
1156
|
@property
|
|
1224
1157
|
def f245(self) -> 'bool':
|
|
1225
1158
|
"""Gets the f245 of this V1UserFeatures. # noqa: E501
|
|
@@ -1325,27 +1258,6 @@ class V1UserFeatures(object):
|
|
|
1325
1258
|
|
|
1326
1259
|
self._gcs_connections_optimized = gcs_connections_optimized
|
|
1327
1260
|
|
|
1328
|
-
@property
|
|
1329
|
-
def gcs_folders(self) -> 'bool':
|
|
1330
|
-
"""Gets the gcs_folders of this V1UserFeatures. # noqa: E501
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
:return: The gcs_folders of this V1UserFeatures. # noqa: E501
|
|
1334
|
-
:rtype: bool
|
|
1335
|
-
"""
|
|
1336
|
-
return self._gcs_folders
|
|
1337
|
-
|
|
1338
|
-
@gcs_folders.setter
|
|
1339
|
-
def gcs_folders(self, gcs_folders: 'bool'):
|
|
1340
|
-
"""Sets the gcs_folders of this V1UserFeatures.
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
:param gcs_folders: The gcs_folders of this V1UserFeatures. # noqa: E501
|
|
1344
|
-
:type: bool
|
|
1345
|
-
"""
|
|
1346
|
-
|
|
1347
|
-
self._gcs_folders = gcs_folders
|
|
1348
|
-
|
|
1349
1261
|
@property
|
|
1350
1262
|
def instant_capacity_reservation(self) -> 'bool':
|
|
1351
1263
|
"""Gets the instant_capacity_reservation of this V1UserFeatures. # noqa: E501
|
|
@@ -1913,27 +1825,6 @@ class V1UserFeatures(object):
|
|
|
1913
1825
|
|
|
1914
1826
|
self._publish_pipelines = publish_pipelines
|
|
1915
1827
|
|
|
1916
|
-
@property
|
|
1917
|
-
def r2_data_connections(self) -> 'bool':
|
|
1918
|
-
"""Gets the r2_data_connections of this V1UserFeatures. # noqa: E501
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
:return: The r2_data_connections of this V1UserFeatures. # noqa: E501
|
|
1922
|
-
:rtype: bool
|
|
1923
|
-
"""
|
|
1924
|
-
return self._r2_data_connections
|
|
1925
|
-
|
|
1926
|
-
@r2_data_connections.setter
|
|
1927
|
-
def r2_data_connections(self, r2_data_connections: 'bool'):
|
|
1928
|
-
"""Sets the r2_data_connections of this V1UserFeatures.
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
:param r2_data_connections: The r2_data_connections of this V1UserFeatures. # noqa: E501
|
|
1932
|
-
:type: bool
|
|
1933
|
-
"""
|
|
1934
|
-
|
|
1935
|
-
self._r2_data_connections = r2_data_connections
|
|
1936
|
-
|
|
1937
1828
|
@property
|
|
1938
1829
|
def reserved_machines_tab(self) -> 'bool':
|
|
1939
1830
|
"""Gets the reserved_machines_tab of this V1UserFeatures. # noqa: E501
|
|
@@ -2207,27 +2098,6 @@ class V1UserFeatures(object):
|
|
|
2207
2098
|
|
|
2208
2099
|
self._trainium2 = trainium2
|
|
2209
2100
|
|
|
2210
|
-
@property
|
|
2211
|
-
def use_internal_data_connection_mounts(self) -> 'bool':
|
|
2212
|
-
"""Gets the use_internal_data_connection_mounts of this V1UserFeatures. # noqa: E501
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
:return: The use_internal_data_connection_mounts of this V1UserFeatures. # noqa: E501
|
|
2216
|
-
:rtype: bool
|
|
2217
|
-
"""
|
|
2218
|
-
return self._use_internal_data_connection_mounts
|
|
2219
|
-
|
|
2220
|
-
@use_internal_data_connection_mounts.setter
|
|
2221
|
-
def use_internal_data_connection_mounts(self, use_internal_data_connection_mounts: 'bool'):
|
|
2222
|
-
"""Sets the use_internal_data_connection_mounts of this V1UserFeatures.
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
:param use_internal_data_connection_mounts: The use_internal_data_connection_mounts of this V1UserFeatures. # noqa: E501
|
|
2226
|
-
:type: bool
|
|
2227
|
-
"""
|
|
2228
|
-
|
|
2229
|
-
self._use_internal_data_connection_mounts = use_internal_data_connection_mounts
|
|
2230
|
-
|
|
2231
2101
|
@property
|
|
2232
2102
|
def use_rclone_mounts_only(self) -> 'bool':
|
|
2233
2103
|
"""Gets the use_rclone_mounts_only of this V1UserFeatures. # noqa: E501
|
lightning_sdk/llm/llm.py
CHANGED
|
@@ -285,7 +285,7 @@ class LLM:
|
|
|
285
285
|
self,
|
|
286
286
|
prompt: str,
|
|
287
287
|
system_prompt: Optional[str] = None,
|
|
288
|
-
max_completion_tokens: Optional[int] =
|
|
288
|
+
max_completion_tokens: Optional[int] = None,
|
|
289
289
|
images: Optional[Union[List[str], str]] = None,
|
|
290
290
|
conversation: Optional[str] = None,
|
|
291
291
|
metadata: Optional[Dict[str, str]] = None,
|
|
@@ -319,7 +319,7 @@ class LLM:
|
|
|
319
319
|
self,
|
|
320
320
|
prompt: str,
|
|
321
321
|
system_prompt: Optional[str] = None,
|
|
322
|
-
max_completion_tokens: Optional[int] =
|
|
322
|
+
max_completion_tokens: Optional[int] = None,
|
|
323
323
|
images: Optional[Union[List[str], str]] = None,
|
|
324
324
|
conversation: Optional[str] = None,
|
|
325
325
|
metadata: Optional[Dict[str, str]] = None,
|
lightning_sdk/studio.py
CHANGED
|
@@ -59,6 +59,9 @@ class Studio:
|
|
|
59
59
|
_skip_init = False
|
|
60
60
|
_skip_setup = False
|
|
61
61
|
|
|
62
|
+
# whether to show progress bars during operations
|
|
63
|
+
show_progress = False
|
|
64
|
+
|
|
62
65
|
def __init__(
|
|
63
66
|
self,
|
|
64
67
|
name: Optional[str] = None,
|
|
@@ -254,9 +257,26 @@ class Studio:
|
|
|
254
257
|
|
|
255
258
|
if status != Status.Stopped:
|
|
256
259
|
raise RuntimeError(f"Cannot start a studio that is not stopped. Studio {self.name} is {status}.")
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
|
|
261
|
+
# Show progress bar during startup
|
|
262
|
+
if self.show_progress:
|
|
263
|
+
from lightning_sdk.utils.progress import StudioProgressTracker
|
|
264
|
+
|
|
265
|
+
with StudioProgressTracker("start", show_progress=True) as progress:
|
|
266
|
+
# Start the studio without blocking
|
|
267
|
+
self._studio_api.start_studio_async(
|
|
268
|
+
self._studio.id, self._teamspace.id, machine, interruptible=interruptible, max_runtime=max_runtime
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Track progress through completion
|
|
272
|
+
progress.track_startup_phases(
|
|
273
|
+
lambda: self._studio_api.get_studio_status(self._studio.id, self._teamspace.id)
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
# Use the blocking version if no progress is needed
|
|
277
|
+
self._studio_api.start_studio(
|
|
278
|
+
self._studio.id, self._teamspace.id, machine, interruptible=interruptible, max_runtime=max_runtime
|
|
279
|
+
)
|
|
260
280
|
|
|
261
281
|
self._setup()
|
|
262
282
|
|
|
@@ -324,9 +344,22 @@ class Studio:
|
|
|
324
344
|
raise RuntimeError(
|
|
325
345
|
f"Cannot switch machine on a studio that is not running. Studio {self.name} is {status}."
|
|
326
346
|
)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
347
|
+
|
|
348
|
+
if self.show_progress:
|
|
349
|
+
from lightning_sdk.utils.progress import StudioProgressTracker
|
|
350
|
+
|
|
351
|
+
with StudioProgressTracker("switch", show_progress=True) as progress:
|
|
352
|
+
# Update progress before starting the switch
|
|
353
|
+
progress.update_progress(5, "Initiating machine switch...")
|
|
354
|
+
|
|
355
|
+
# Start the switch operation with progress tracking
|
|
356
|
+
self._studio_api.switch_studio_machine_with_progress(
|
|
357
|
+
self._studio.id, self._teamspace.id, machine, interruptible=interruptible, progress=progress
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
self._studio_api.switch_studio_machine(
|
|
361
|
+
self._studio.id, self._teamspace.id, machine, interruptible=interruptible
|
|
362
|
+
)
|
|
330
363
|
|
|
331
364
|
def run_and_detach(self, *commands: str, timeout: float = 10, check_interval: float = 1) -> str:
|
|
332
365
|
"""Runs given commands on the Studio and returns immediately.
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Studio startup/switch progress bar utilities."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import types
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Callable, List, Optional, Type, Union
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskID, TextColumn, TimeElapsedColumn
|
|
10
|
+
|
|
11
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_accelerator import V1ClusterAccelerator
|
|
12
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_instance_status_response import (
|
|
13
|
+
V1GetCloudSpaceInstanceStatusResponse,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StartupPhase(Enum):
|
|
18
|
+
"""Studio startup phase messages."""
|
|
19
|
+
|
|
20
|
+
ALLOCATING_MACHINE = "Allocating machine from cloud provider..."
|
|
21
|
+
STUDIO_STARTING = "Studio is starting up..."
|
|
22
|
+
SETTING_UP_ENVIRONMENT = "Setting up Studio environment..."
|
|
23
|
+
RESTORING_STATE = "Restoring Studio state..."
|
|
24
|
+
FINALIZING_SETUP = "Finalizing Studio setup..."
|
|
25
|
+
COMPLETED = "Studio started successfully"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_switching_progress_message(percentage: int, is_base_studio: bool, is_new_cloud_space: bool) -> str:
|
|
29
|
+
"""Get progress message for switching studios."""
|
|
30
|
+
percentage = max(0, min(100, round(percentage)))
|
|
31
|
+
|
|
32
|
+
if percentage > 98:
|
|
33
|
+
message = "Done"
|
|
34
|
+
elif percentage > 80:
|
|
35
|
+
if is_new_cloud_space:
|
|
36
|
+
message = "Setting up Base Studio..." if is_base_studio else "Preparing Studio..."
|
|
37
|
+
else:
|
|
38
|
+
message = "Restoring Studio..."
|
|
39
|
+
elif percentage > 60:
|
|
40
|
+
message = "Setting up machine from the cloud provider"
|
|
41
|
+
else:
|
|
42
|
+
message = "Allocating machine from the cloud provider"
|
|
43
|
+
|
|
44
|
+
return f"({percentage}%) {message}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def estimated_studio_ready_in_seconds(
|
|
48
|
+
cloud_space: Any, cloud_space_instance_status: Any, accelerators: Optional[List[V1ClusterAccelerator]] = None
|
|
49
|
+
) -> int:
|
|
50
|
+
"""Calculate estimated seconds until studio is ready."""
|
|
51
|
+
# Default estimate
|
|
52
|
+
return 120
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def progress_bar_growth(default_timeout: int, counter: float) -> int:
|
|
56
|
+
"""Calculate progress bar growth based on timeout and counter."""
|
|
57
|
+
if default_timeout <= 0:
|
|
58
|
+
return 100
|
|
59
|
+
|
|
60
|
+
value = ((default_timeout - counter) / default_timeout) * 100
|
|
61
|
+
if value > 100:
|
|
62
|
+
value = 100
|
|
63
|
+
if value < 0:
|
|
64
|
+
value = 0
|
|
65
|
+
return int(value)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class StudioProgressTracker:
|
|
69
|
+
"""Tracks and displays progress for studio startup/switching operations."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, operation_type: str = "start", show_progress: bool = True, check_interval: float = 1.0) -> None:
|
|
72
|
+
"""Initialize progress tracker.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
operation_type: Type of operation ('start' or 'switch')
|
|
76
|
+
show_progress: Whether to display progress bar
|
|
77
|
+
check_interval: Seconds between status checks
|
|
78
|
+
"""
|
|
79
|
+
self.operation_type = operation_type
|
|
80
|
+
self.show_progress = show_progress
|
|
81
|
+
self.check_interval = check_interval
|
|
82
|
+
self.progress: Optional[Progress] = None
|
|
83
|
+
self.task_id: Optional[TaskID] = None
|
|
84
|
+
self.console = Console()
|
|
85
|
+
self._last_message = ""
|
|
86
|
+
|
|
87
|
+
def __enter__(self) -> "StudioProgressTracker":
|
|
88
|
+
"""Enter context manager."""
|
|
89
|
+
if self.show_progress:
|
|
90
|
+
self.progress = Progress(
|
|
91
|
+
SpinnerColumn(),
|
|
92
|
+
TextColumn("[progress.description]{task.description}"),
|
|
93
|
+
BarColumn(),
|
|
94
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
95
|
+
TimeElapsedColumn(),
|
|
96
|
+
console=self.console,
|
|
97
|
+
transient=False,
|
|
98
|
+
)
|
|
99
|
+
self.progress.start()
|
|
100
|
+
self.task_id = self.progress.add_task(f"{self.operation_type.capitalize()}ing Studio...", total=100)
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def __exit__(
|
|
104
|
+
self,
|
|
105
|
+
exc_type: Union[Type[BaseException], None],
|
|
106
|
+
exc_val: Union[BaseException, None],
|
|
107
|
+
exc_tb: Union[types.TracebackType, None],
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Exit context manager."""
|
|
110
|
+
if self.progress:
|
|
111
|
+
self.progress.stop()
|
|
112
|
+
|
|
113
|
+
def update_progress(
|
|
114
|
+
self, percentage: int, message: str = "", is_base_studio: bool = False, is_new_cloud_space: bool = False
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Update progress bar with current percentage and message."""
|
|
117
|
+
if not self.progress or self.task_id is None:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
if self.operation_type == "switch":
|
|
121
|
+
display_message = get_switching_progress_message(percentage, is_base_studio, is_new_cloud_space)
|
|
122
|
+
else:
|
|
123
|
+
display_message = message or f"{self.operation_type.capitalize()}ing Studio..."
|
|
124
|
+
|
|
125
|
+
# Update description if message changed
|
|
126
|
+
if display_message != self._last_message:
|
|
127
|
+
self.progress.update(self.task_id, description=display_message)
|
|
128
|
+
self._last_message = display_message
|
|
129
|
+
|
|
130
|
+
# Never show 100% until truly complete
|
|
131
|
+
completed = min(percentage, 98) if percentage < 100 else 100
|
|
132
|
+
self.progress.update(self.task_id, completed=completed)
|
|
133
|
+
|
|
134
|
+
# Force console refresh to ensure progress is visible
|
|
135
|
+
self.progress.refresh()
|
|
136
|
+
|
|
137
|
+
def complete(self, success_message: str = "") -> None:
|
|
138
|
+
"""Mark operation as complete."""
|
|
139
|
+
if self.progress and self.task_id is not None:
|
|
140
|
+
self.progress.update(self.task_id, completed=100, description=success_message or "Done")
|
|
141
|
+
|
|
142
|
+
def track_startup_phases(
|
|
143
|
+
self,
|
|
144
|
+
status_getter: Callable[[], V1GetCloudSpaceInstanceStatusResponse],
|
|
145
|
+
accelerators: Optional[List[V1ClusterAccelerator]] = None,
|
|
146
|
+
timeout: int = 600,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Track startup phases and update progress accordingly."""
|
|
149
|
+
start_time = time.time()
|
|
150
|
+
last_progress = 5
|
|
151
|
+
phase_start_times = {}
|
|
152
|
+
last_message_time = 0
|
|
153
|
+
message_stability_delay = 3.0 # Seconds to wait before changing message
|
|
154
|
+
|
|
155
|
+
# Show initial progress immediately
|
|
156
|
+
self.update_progress(5, StartupPhase.ALLOCATING_MACHINE.value)
|
|
157
|
+
|
|
158
|
+
while True:
|
|
159
|
+
try:
|
|
160
|
+
status = status_getter()
|
|
161
|
+
elapsed = time.time() - start_time
|
|
162
|
+
|
|
163
|
+
# Default fallback progress based on time
|
|
164
|
+
time_based_progress = min(95, int((elapsed / timeout) * 100))
|
|
165
|
+
current_progress = max(last_progress, time_based_progress)
|
|
166
|
+
current_message = StartupPhase.ALLOCATING_MACHINE.value
|
|
167
|
+
|
|
168
|
+
# Check if we have detailed status information
|
|
169
|
+
if hasattr(status, "in_use") and status.in_use:
|
|
170
|
+
in_use = status.in_use
|
|
171
|
+
|
|
172
|
+
# Check startup status for detailed phases
|
|
173
|
+
if hasattr(in_use, "startup_status") and in_use.startup_status:
|
|
174
|
+
startup_status = in_use.startup_status
|
|
175
|
+
|
|
176
|
+
# Check completion first
|
|
177
|
+
if (
|
|
178
|
+
hasattr(startup_status, "top_up_restore_finished")
|
|
179
|
+
and startup_status.top_up_restore_finished
|
|
180
|
+
):
|
|
181
|
+
self.complete(StartupPhase.COMPLETED.value)
|
|
182
|
+
break
|
|
183
|
+
|
|
184
|
+
# Check other phases in descending priority
|
|
185
|
+
if (
|
|
186
|
+
hasattr(startup_status, "initial_restore_finished")
|
|
187
|
+
and startup_status.initial_restore_finished
|
|
188
|
+
):
|
|
189
|
+
current_progress = max(current_progress, 85)
|
|
190
|
+
current_message = StartupPhase.FINALIZING_SETUP.value
|
|
191
|
+
elif hasattr(startup_status, "container_ready") and startup_status.container_ready:
|
|
192
|
+
current_progress = max(current_progress, 70)
|
|
193
|
+
current_message = StartupPhase.RESTORING_STATE.value
|
|
194
|
+
elif hasattr(startup_status, "machine_ready") and startup_status.machine_ready:
|
|
195
|
+
current_progress = max(current_progress, 60)
|
|
196
|
+
current_message = StartupPhase.SETTING_UP_ENVIRONMENT.value
|
|
197
|
+
|
|
198
|
+
# Check general phase information
|
|
199
|
+
if hasattr(in_use, "phase") and in_use.phase:
|
|
200
|
+
phase = in_use.phase
|
|
201
|
+
|
|
202
|
+
if phase == "CLOUD_SPACE_INSTANCE_STATE_RUNNING":
|
|
203
|
+
current_progress = max(current_progress, 80)
|
|
204
|
+
current_message = StartupPhase.STUDIO_STARTING.value
|
|
205
|
+
elif phase == "CLOUD_SPACE_INSTANCE_STATE_PENDING":
|
|
206
|
+
# Track time in pending phase for smoother progress
|
|
207
|
+
if "pending" not in phase_start_times:
|
|
208
|
+
phase_start_times["pending"] = time.time()
|
|
209
|
+
|
|
210
|
+
pending_elapsed = time.time() - phase_start_times["pending"]
|
|
211
|
+
# Progress more smoothly through pending phase (10-60%)
|
|
212
|
+
pending_progress = 10 + min(50, int((pending_elapsed / 60) * 50))
|
|
213
|
+
current_progress = max(current_progress, pending_progress)
|
|
214
|
+
current_message = StartupPhase.ALLOCATING_MACHINE.value
|
|
215
|
+
|
|
216
|
+
# Check for requested machine status (pre-allocation)
|
|
217
|
+
elif hasattr(status, "requested") and status.requested:
|
|
218
|
+
if "allocation" not in phase_start_times:
|
|
219
|
+
phase_start_times["allocation"] = time.time()
|
|
220
|
+
|
|
221
|
+
allocation_elapsed = time.time() - phase_start_times["allocation"]
|
|
222
|
+
# Progress through allocation phase (5-30%)
|
|
223
|
+
allocation_progress = 5 + min(25, int((allocation_elapsed / 30) * 25))
|
|
224
|
+
current_progress = max(current_progress, allocation_progress)
|
|
225
|
+
current_message = StartupPhase.ALLOCATING_MACHINE.value
|
|
226
|
+
|
|
227
|
+
# Ensure progress never decreases and moves smoothly
|
|
228
|
+
if current_progress > last_progress:
|
|
229
|
+
# Smooth progress increases - don't jump too much at once
|
|
230
|
+
max_increment = 3 if current_progress - last_progress > 10 else current_progress - last_progress
|
|
231
|
+
current_progress = last_progress + max_increment
|
|
232
|
+
|
|
233
|
+
current_progress = min(98, current_progress) # Never show 100% until truly complete
|
|
234
|
+
|
|
235
|
+
# Only update message if enough time has passed since last message change
|
|
236
|
+
# or if this is the first message
|
|
237
|
+
current_time = time.time()
|
|
238
|
+
should_update_message = current_message != self._last_message and (
|
|
239
|
+
current_time - last_message_time >= message_stability_delay or last_message_time == 0
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if should_update_message:
|
|
243
|
+
self.update_progress(current_progress, current_message)
|
|
244
|
+
last_message_time = current_time
|
|
245
|
+
else:
|
|
246
|
+
# Update progress but keep existing message
|
|
247
|
+
if self.progress and self.task_id is not None:
|
|
248
|
+
self.progress.update(self.task_id, completed=current_progress)
|
|
249
|
+
self.progress.refresh()
|
|
250
|
+
|
|
251
|
+
last_progress = current_progress
|
|
252
|
+
|
|
253
|
+
# Break if we timeout
|
|
254
|
+
if elapsed > timeout:
|
|
255
|
+
self.complete("Studio start may still be in progress...")
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
except Exception:
|
|
259
|
+
# Continue on API errors but still update progress
|
|
260
|
+
elapsed = time.time() - start_time
|
|
261
|
+
fallback_progress = min(95, max(last_progress, int((elapsed / timeout) * 100)))
|
|
262
|
+
|
|
263
|
+
# Only update message if enough time has passed
|
|
264
|
+
current_time = time.time()
|
|
265
|
+
should_update_message = StartupPhase.ALLOCATING_MACHINE.value != self._last_message and (
|
|
266
|
+
current_time - last_message_time >= message_stability_delay or last_message_time == 0
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if should_update_message:
|
|
270
|
+
self.update_progress(fallback_progress, StartupPhase.ALLOCATING_MACHINE.value)
|
|
271
|
+
last_message_time = current_time
|
|
272
|
+
else:
|
|
273
|
+
# Update progress but keep existing message
|
|
274
|
+
if self.progress and self.task_id is not None:
|
|
275
|
+
self.progress.update(self.task_id, completed=fallback_progress)
|
|
276
|
+
self.progress.refresh()
|
|
277
|
+
|
|
278
|
+
last_progress = fallback_progress
|
|
279
|
+
|
|
280
|
+
if elapsed > timeout:
|
|
281
|
+
self.complete("Studio start may still be in progress...")
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
time.sleep(self.check_interval)
|