lightning-sdk 2025.8.6rc2__py3-none-any.whl → 2025.11.5__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.
Files changed (330) hide show
  1. lightning_sdk/__init__.py +13 -7
  2. lightning_sdk/__version__.py +3 -0
  3. lightning_sdk/agents.py +2 -1
  4. lightning_sdk/ai_hub.py +2 -1
  5. lightning_sdk/api/__init__.py +2 -0
  6. lightning_sdk/api/base_studio_api.py +13 -9
  7. lightning_sdk/api/cloud_account_api.py +31 -10
  8. lightning_sdk/api/deployment_api.py +27 -1
  9. lightning_sdk/api/job_api.py +16 -12
  10. lightning_sdk/api/license_api.py +26 -59
  11. lightning_sdk/api/llm_api.py +25 -2
  12. lightning_sdk/api/mmt_api.py +5 -2
  13. lightning_sdk/api/studio_api.py +252 -55
  14. lightning_sdk/api/teamspace_api.py +106 -30
  15. lightning_sdk/api/user_api.py +56 -2
  16. lightning_sdk/api/utils.py +108 -18
  17. lightning_sdk/base_studio.py +51 -27
  18. lightning_sdk/cli/__init__.py +1 -0
  19. lightning_sdk/cli/base_studio/__init__.py +10 -0
  20. lightning_sdk/cli/base_studio/list.py +43 -0
  21. lightning_sdk/cli/config/__init__.py +14 -0
  22. lightning_sdk/cli/config/get.py +57 -0
  23. lightning_sdk/cli/config/set.py +92 -0
  24. lightning_sdk/cli/config/show.py +9 -0
  25. lightning_sdk/cli/entrypoint.py +71 -71
  26. lightning_sdk/cli/groups.py +56 -0
  27. lightning_sdk/cli/job/__init__.py +7 -0
  28. lightning_sdk/cli/{clusters_menu.py → legacy/clusters_menu.py} +9 -6
  29. lightning_sdk/cli/{configure.py → legacy/configure.py} +2 -2
  30. lightning_sdk/cli/{connect.py → legacy/connect.py} +2 -2
  31. lightning_sdk/cli/{create.py → legacy/create.py} +12 -14
  32. lightning_sdk/cli/{delete.py → legacy/delete.py} +5 -5
  33. lightning_sdk/cli/legacy/deploy/__init__.py +0 -0
  34. lightning_sdk/cli/{deploy → legacy/deploy}/_auth.py +5 -6
  35. lightning_sdk/cli/{deploy → legacy/deploy}/devbox.py +8 -2
  36. lightning_sdk/cli/{deploy → legacy/deploy}/serve.py +19 -8
  37. lightning_sdk/cli/{download.py → legacy/download.py} +14 -15
  38. lightning_sdk/cli/legacy/entrypoint.py +110 -0
  39. lightning_sdk/cli/{generate.py → legacy/generate.py} +1 -1
  40. lightning_sdk/cli/{inspection.py → legacy/inspection.py} +1 -1
  41. lightning_sdk/cli/{job_and_mmt_action.py → legacy/job_and_mmt_action.py} +6 -6
  42. lightning_sdk/cli/{jobs_menu.py → legacy/jobs_menu.py} +1 -1
  43. lightning_sdk/cli/{list.py → legacy/list.py} +12 -13
  44. lightning_sdk/cli/{mmts_menu.py → legacy/mmts_menu.py} +1 -1
  45. lightning_sdk/cli/{open.py → legacy/open.py} +4 -4
  46. lightning_sdk/cli/{start.py → legacy/start.py} +1 -0
  47. lightning_sdk/cli/{stop.py → legacy/stop.py} +1 -1
  48. lightning_sdk/cli/{switch.py → legacy/switch.py} +1 -0
  49. lightning_sdk/cli/{teamspace_menu.py → legacy/teamspace_menu.py} +1 -1
  50. lightning_sdk/cli/{upload.py → legacy/upload.py} +7 -8
  51. lightning_sdk/cli/license/__init__.py +14 -0
  52. lightning_sdk/cli/license/get.py +15 -0
  53. lightning_sdk/cli/license/list.py +45 -0
  54. lightning_sdk/cli/license/set.py +13 -0
  55. lightning_sdk/cli/mmt/__init__.py +7 -0
  56. lightning_sdk/cli/studio/__init__.py +24 -0
  57. lightning_sdk/cli/studio/connect.py +139 -0
  58. lightning_sdk/cli/studio/create.py +96 -0
  59. lightning_sdk/cli/studio/delete.py +49 -0
  60. lightning_sdk/cli/studio/list.py +85 -0
  61. lightning_sdk/cli/studio/ssh.py +64 -0
  62. lightning_sdk/cli/studio/start.py +115 -0
  63. lightning_sdk/cli/studio/stop.py +45 -0
  64. lightning_sdk/cli/studio/switch.py +66 -0
  65. lightning_sdk/cli/utils/__init__.py +7 -0
  66. lightning_sdk/cli/utils/cloud_account_map.py +10 -0
  67. lightning_sdk/cli/utils/get_base_studio.py +24 -0
  68. lightning_sdk/cli/utils/handle_machine_and_gpus_args.py +69 -0
  69. lightning_sdk/cli/utils/logging.py +122 -0
  70. lightning_sdk/cli/utils/owner_selection.py +110 -0
  71. lightning_sdk/cli/utils/resolve.py +28 -0
  72. lightning_sdk/cli/utils/richt_print.py +35 -0
  73. lightning_sdk/cli/utils/save_to_config.py +27 -0
  74. lightning_sdk/cli/utils/ssh_connection.py +59 -0
  75. lightning_sdk/cli/utils/studio_selection.py +113 -0
  76. lightning_sdk/cli/utils/teamspace_selection.py +125 -0
  77. lightning_sdk/cli/vm/__init__.py +20 -0
  78. lightning_sdk/cli/vm/create.py +33 -0
  79. lightning_sdk/cli/vm/delete.py +25 -0
  80. lightning_sdk/cli/vm/list.py +30 -0
  81. lightning_sdk/cli/vm/ssh.py +31 -0
  82. lightning_sdk/cli/vm/start.py +60 -0
  83. lightning_sdk/cli/vm/stop.py +25 -0
  84. lightning_sdk/cli/vm/switch.py +38 -0
  85. lightning_sdk/constants.py +1 -0
  86. lightning_sdk/deployment/__init__.py +2 -0
  87. lightning_sdk/deployment/deployment.py +17 -2
  88. lightning_sdk/helpers.py +56 -37
  89. lightning_sdk/job/base.py +21 -6
  90. lightning_sdk/job/job.py +13 -0
  91. lightning_sdk/job/v1.py +11 -0
  92. lightning_sdk/job/v2.py +12 -0
  93. lightning_sdk/lightning_cloud/login.py +320 -10
  94. lightning_sdk/lightning_cloud/openapi/__init__.py +113 -3
  95. lightning_sdk/lightning_cloud/openapi/api/__init__.py +3 -0
  96. lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +713 -75
  97. lightning_sdk/lightning_cloud/openapi/api/auth_service_api.py +376 -0
  98. lightning_sdk/lightning_cloud/openapi/api/billing_service_api.py +191 -1
  99. lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +5 -1
  100. lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +420 -0
  101. lightning_sdk/lightning_cloud/openapi/api/cloudy_service_api.py +0 -97
  102. lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +420 -0
  103. lightning_sdk/lightning_cloud/openapi/api/data_connection_service_api.py +101 -0
  104. lightning_sdk/lightning_cloud/openapi/api/incidents_service_api.py +1058 -0
  105. lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +121 -0
  106. lightning_sdk/lightning_cloud/openapi/api/k8_s_cluster_service_api.py +1742 -94
  107. lightning_sdk/lightning_cloud/openapi/api/markets_service_api.py +145 -0
  108. lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +4 -4
  109. lightning_sdk/lightning_cloud/openapi/api/product_license_service_api.py +108 -108
  110. lightning_sdk/lightning_cloud/openapi/api/projects_service_api.py +105 -0
  111. lightning_sdk/lightning_cloud/openapi/api/schedules_service_api.py +347 -0
  112. lightning_sdk/lightning_cloud/openapi/api/sdk_command_history_service_api.py +141 -0
  113. lightning_sdk/lightning_cloud/openapi/api/storage_service_api.py +761 -1
  114. lightning_sdk/lightning_cloud/openapi/configuration.py +3 -19
  115. lightning_sdk/lightning_cloud/openapi/models/__init__.py +110 -3
  116. lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +15 -15
  117. lightning_sdk/lightning_cloud/openapi/models/cloudspace_id_visibility_body.py +27 -1
  118. lightning_sdk/lightning_cloud/openapi/models/cluster_id_kubernetestemplates_body.py +201 -0
  119. lightning_sdk/lightning_cloud/openapi/models/cluster_id_metrics_body.py +131 -1
  120. lightning_sdk/lightning_cloud/openapi/models/create.py +27 -1
  121. lightning_sdk/lightning_cloud/openapi/models/create_machine_request_represents_the_request_to_create_a_machine.py +461 -0
  122. lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +27 -1
  123. lightning_sdk/lightning_cloud/openapi/models/externalv1_cloud_space_instance_status.py +105 -1
  124. lightning_sdk/lightning_cloud/openapi/models/externalv1_user_status.py +27 -1
  125. lightning_sdk/lightning_cloud/openapi/models/id_codeconfig_body.py +3 -81
  126. lightning_sdk/lightning_cloud/openapi/models/id_fork_body1.py +27 -1
  127. lightning_sdk/lightning_cloud/openapi/models/id_render_body.py +123 -0
  128. lightning_sdk/lightning_cloud/openapi/models/id_sleepconfig_body.py +175 -0
  129. lightning_sdk/lightning_cloud/openapi/models/id_transfer_body.py +53 -1
  130. lightning_sdk/lightning_cloud/openapi/models/incident_id_messages_body.py +123 -0
  131. lightning_sdk/lightning_cloud/openapi/models/incidents_id_body.py +279 -0
  132. lightning_sdk/lightning_cloud/openapi/models/job_id_reportroutingtelemetry_body.py +123 -0
  133. lightning_sdk/lightning_cloud/openapi/models/kubernetestemplates_id_body.py +201 -0
  134. lightning_sdk/lightning_cloud/openapi/models/license_key_validate_body.py +123 -0
  135. lightning_sdk/lightning_cloud/openapi/models/message_id_actions_body.py +201 -0
  136. lightning_sdk/lightning_cloud/openapi/models/messages_message_id_body.py +123 -0
  137. lightning_sdk/lightning_cloud/openapi/models/metricsstream_create_body.py +27 -1
  138. lightning_sdk/lightning_cloud/openapi/models/models_model_id_body.py +109 -31
  139. lightning_sdk/lightning_cloud/openapi/models/models_model_id_body1.py +149 -0
  140. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +53 -1
  141. lightning_sdk/lightning_cloud/openapi/models/project_id_storage_body.py +27 -1
  142. lightning_sdk/lightning_cloud/openapi/models/project_id_storagetransfers_body.py +175 -0
  143. lightning_sdk/lightning_cloud/openapi/models/project_tab_management_messages.py +123 -0
  144. lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +79 -1
  145. lightning_sdk/lightning_cloud/openapi/models/storage_complete_body.py +41 -15
  146. lightning_sdk/lightning_cloud/openapi/models/storagetransfers_validate_body.py +149 -0
  147. lightning_sdk/lightning_cloud/openapi/models/update1.py +27 -1
  148. lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body1.py +27 -1
  149. lightning_sdk/lightning_cloud/openapi/models/user_id_affiliatelinks_body.py +107 -3
  150. lightning_sdk/lightning_cloud/openapi/models/v1_abort_storage_transfer_response.py +97 -0
  151. lightning_sdk/lightning_cloud/openapi/models/v1_aggregated_pod_metrics.py +799 -0
  152. lightning_sdk/lightning_cloud/openapi/models/v1_ai_pod_v1.py +53 -1
  153. lightning_sdk/lightning_cloud/openapi/models/v1_assistant_session_daily_aggregated.py +27 -1
  154. lightning_sdk/lightning_cloud/openapi/models/v1_billing_tier.py +0 -1
  155. lightning_sdk/lightning_cloud/openapi/models/v1_cancel_running_cloud_space_instance_transfer_response.py +97 -0
  156. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +2 -0
  157. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template_config.py +27 -1
  158. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_specialized_view.py +1 -0
  159. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_transfer_metadata.py +147 -17
  160. lightning_sdk/lightning_cloud/openapi/models/v1_cloudflare_v1.py +3 -29
  161. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_accelerator.py +53 -1
  162. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_capacity_reservation.py +27 -1
  163. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_metrics.py +1527 -0
  164. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
  165. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +53 -1
  166. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_type.py +1 -0
  167. lightning_sdk/lightning_cloud/openapi/models/v1_container_metrics.py +21 -21
  168. lightning_sdk/lightning_cloud/openapi/models/v1_create_incident_request.py +305 -0
  169. lightning_sdk/lightning_cloud/openapi/models/v1_create_license_request.py +175 -0
  170. lightning_sdk/lightning_cloud/openapi/models/v1_create_machine_response.py +123 -0
  171. lightning_sdk/lightning_cloud/openapi/models/v1_create_model_metrics_response.py +97 -0
  172. lightning_sdk/lightning_cloud/openapi/models/v1_create_project_request.py +79 -1
  173. lightning_sdk/lightning_cloud/openapi/models/v1_create_sdk_command_history_request.py +253 -0
  174. lightning_sdk/lightning_cloud/openapi/models/v1_create_sdk_command_history_response.py +97 -0
  175. lightning_sdk/lightning_cloud/openapi/models/v1_daily_model_metrics.py +149 -0
  176. lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +53 -1
  177. lightning_sdk/lightning_cloud/openapi/models/v1_delete_incident_message_response.py +97 -0
  178. lightning_sdk/lightning_cloud/openapi/models/v1_delete_incident_response.py +97 -0
  179. lightning_sdk/lightning_cloud/openapi/models/v1_delete_kubernetes_template_response.py +97 -0
  180. lightning_sdk/lightning_cloud/openapi/models/v1_delete_license_response.py +97 -0
  181. lightning_sdk/lightning_cloud/openapi/models/v1_delete_machine_response.py +97 -0
  182. lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +27 -1
  183. lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster_spec.py +53 -1
  184. lightning_sdk/lightning_cloud/openapi/models/v1_external_search_user.py +27 -1
  185. lightning_sdk/lightning_cloud/openapi/models/v1_filestore_data_connection.py +27 -1
  186. lightning_sdk/lightning_cloud/openapi/models/v1_filesystem_metric.py +201 -0
  187. lightning_sdk/lightning_cloud/openapi/models/v1_firewall_rule.py +175 -0
  188. lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_required_balance_status_response.py +149 -0
  189. lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_transfer_estimate_response.py +149 -0
  190. lightning_sdk/lightning_cloud/openapi/models/v1_get_latest_model_metrics_response.py +123 -0
  191. lightning_sdk/lightning_cloud/openapi/models/v1_get_machine_response.py +123 -0
  192. lightning_sdk/lightning_cloud/openapi/models/v1_get_market_pricing_response.py +201 -0
  193. lightning_sdk/lightning_cloud/openapi/models/v1_get_model_metrics_response.py +123 -0
  194. lightning_sdk/lightning_cloud/openapi/models/v1_get_temp_bucket_credentials_response.py +201 -0
  195. lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +41 -15
  196. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +27 -1
  197. lightning_sdk/lightning_cloud/openapi/models/v1_group_node_metrics.py +1215 -0
  198. lightning_sdk/lightning_cloud/openapi/models/v1_group_pod_metrics.py +1241 -0
  199. lightning_sdk/lightning_cloud/openapi/models/v1_guest_login_request.py +177 -0
  200. lightning_sdk/lightning_cloud/openapi/models/v1_guest_login_response.py +149 -0
  201. lightning_sdk/lightning_cloud/openapi/models/v1_guest_user.py +201 -0
  202. lightning_sdk/lightning_cloud/openapi/models/v1_incident.py +565 -0
  203. lightning_sdk/lightning_cloud/openapi/models/v1_incident_detail.py +149 -0
  204. lightning_sdk/lightning_cloud/openapi/models/v1_incident_event.py +591 -0
  205. lightning_sdk/lightning_cloud/openapi/models/v1_incident_message.py +253 -0
  206. lightning_sdk/lightning_cloud/openapi/models/v1_incident_severity.py +105 -0
  207. lightning_sdk/lightning_cloud/openapi/models/v1_incident_type.py +108 -0
  208. lightning_sdk/lightning_cloud/openapi/models/v1_job.py +53 -1
  209. lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +27 -1
  210. lightning_sdk/lightning_cloud/openapi/models/v1_k8s_incident_indexes.py +149 -0
  211. lightning_sdk/lightning_cloud/openapi/models/v1_kai_scheduler_queue_metrics.py +627 -0
  212. lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_aws_config.py +279 -0
  213. lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_direct_settings_v1.py +253 -0
  214. lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_direct_v1.py +107 -3
  215. lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_template.py +357 -0
  216. lightning_sdk/lightning_cloud/openapi/models/v1_kubernetes_template_property.py +227 -0
  217. lightning_sdk/lightning_cloud/openapi/models/v1_lambda_labs_direct_v1.py +29 -3
  218. lightning_sdk/lightning_cloud/openapi/models/v1_license.py +227 -0
  219. lightning_sdk/lightning_cloud/openapi/models/v1_lightning_elastic_cluster_v1.py +97 -0
  220. lightning_sdk/lightning_cloud/openapi/models/v1_list_aggregated_pod_metrics_response.py +123 -0
  221. lightning_sdk/lightning_cloud/openapi/models/v1_list_cluster_metric_timestamps_response.py +123 -0
  222. lightning_sdk/lightning_cloud/openapi/models/v1_list_cluster_metrics_response.py +123 -0
  223. lightning_sdk/lightning_cloud/openapi/models/v1_list_cluster_namespace_metrics_response.py +123 -0
  224. lightning_sdk/lightning_cloud/openapi/models/v1_list_cluster_namespace_user_metrics_response.py +123 -0
  225. lightning_sdk/lightning_cloud/openapi/models/v1_list_conversation_message_actions_response.py +123 -0
  226. lightning_sdk/lightning_cloud/openapi/models/v1_list_filesystem_metrics_response.py +123 -0
  227. lightning_sdk/lightning_cloud/openapi/models/v1_list_group_pod_metrics_response.py +123 -0
  228. lightning_sdk/lightning_cloud/openapi/models/v1_list_incident_events_response.py +123 -0
  229. lightning_sdk/lightning_cloud/openapi/models/v1_list_incident_messages_response.py +149 -0
  230. lightning_sdk/lightning_cloud/openapi/models/v1_list_incidents_response.py +149 -0
  231. lightning_sdk/lightning_cloud/openapi/models/v1_list_kai_scheduler_queues_metrics_response.py +123 -0
  232. lightning_sdk/lightning_cloud/openapi/models/v1_list_kubernetes_templates_response.py +123 -0
  233. lightning_sdk/lightning_cloud/openapi/models/{v1_list_product_licenses_response.py → v1_list_license_response.py} +16 -16
  234. lightning_sdk/lightning_cloud/openapi/models/v1_list_machines_response.py +149 -0
  235. lightning_sdk/lightning_cloud/openapi/models/v1_list_platform_notifications_response.py +123 -0
  236. lightning_sdk/lightning_cloud/openapi/models/v1_list_schedule_runs_response.py +123 -0
  237. lightning_sdk/lightning_cloud/openapi/models/v1_list_storage_transfers_response.py +123 -0
  238. lightning_sdk/lightning_cloud/openapi/models/v1_lustre_data_connection.py +149 -0
  239. lightning_sdk/lightning_cloud/openapi/models/v1_machine.py +617 -0
  240. lightning_sdk/lightning_cloud/openapi/models/v1_machine_direct_v1.py +123 -0
  241. lightning_sdk/lightning_cloud/openapi/models/v1_magic_link_login_request.py +1 -27
  242. lightning_sdk/lightning_cloud/openapi/models/v1_magic_link_login_response.py +27 -1
  243. lightning_sdk/lightning_cloud/openapi/models/v1_managed_model.py +29 -3
  244. lightning_sdk/lightning_cloud/openapi/models/v1_market_price.py +149 -0
  245. lightning_sdk/lightning_cloud/openapi/models/v1_membership.py +53 -1
  246. lightning_sdk/lightning_cloud/openapi/models/v1_message_action.py +279 -0
  247. lightning_sdk/lightning_cloud/openapi/models/v1_metrics_stream.py +27 -1
  248. lightning_sdk/lightning_cloud/openapi/models/v1_model_metrics.py +175 -0
  249. lightning_sdk/lightning_cloud/openapi/models/v1_namespace_metrics.py +591 -0
  250. lightning_sdk/lightning_cloud/openapi/models/v1_namespace_user_metrics.py +435 -0
  251. lightning_sdk/lightning_cloud/openapi/models/v1_nebius_direct_v1.py +29 -3
  252. lightning_sdk/lightning_cloud/openapi/models/v1_node_metrics.py +361 -23
  253. lightning_sdk/lightning_cloud/openapi/models/v1_notification_type.py +1 -0
  254. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +53 -1
  255. lightning_sdk/lightning_cloud/openapi/models/v1_pause_storage_transfer_response.py +97 -0
  256. lightning_sdk/lightning_cloud/openapi/models/v1_platform_notification.py +279 -0
  257. lightning_sdk/lightning_cloud/openapi/models/v1_pod_metrics.py +335 -23
  258. lightning_sdk/lightning_cloud/openapi/models/v1_project.py +27 -1
  259. lightning_sdk/lightning_cloud/openapi/models/v1_project_cluster_binding.py +27 -1
  260. lightning_sdk/lightning_cloud/openapi/models/v1_project_membership.py +53 -1
  261. lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +53 -1
  262. lightning_sdk/lightning_cloud/openapi/models/v1_project_tab.py +149 -0
  263. lightning_sdk/lightning_cloud/openapi/models/{v1_product_license_check_response.py → v1_purchase_annual_upsell_request.py} +23 -23
  264. lightning_sdk/lightning_cloud/openapi/models/v1_purchase_annual_upsell_response.py +123 -0
  265. lightning_sdk/lightning_cloud/openapi/models/v1_quote_annual_upsell_response.py +227 -0
  266. lightning_sdk/lightning_cloud/openapi/models/v1_render_kubernetes_template_response.py +123 -0
  267. lightning_sdk/lightning_cloud/openapi/models/v1_report_deployment_routing_telemetry_response.py +97 -0
  268. lightning_sdk/lightning_cloud/openapi/models/v1_required_balance_reason.py +107 -0
  269. lightning_sdk/lightning_cloud/openapi/models/v1_reset_api_key_request.py +97 -0
  270. lightning_sdk/lightning_cloud/openapi/models/v1_reset_api_key_response.py +123 -0
  271. lightning_sdk/lightning_cloud/openapi/models/v1_resource_visibility.py +1 -27
  272. lightning_sdk/lightning_cloud/openapi/models/v1_resume_storage_transfer_response.py +97 -0
  273. lightning_sdk/lightning_cloud/openapi/models/v1_routing_telemetry.py +79 -1
  274. lightning_sdk/lightning_cloud/openapi/models/v1_rule_resource.py +1 -0
  275. lightning_sdk/lightning_cloud/openapi/models/v1_schedule_run.py +357 -0
  276. lightning_sdk/lightning_cloud/openapi/models/v1_sdk_command_history_severity.py +104 -0
  277. lightning_sdk/lightning_cloud/openapi/models/v1_sdk_command_history_type.py +104 -0
  278. lightning_sdk/lightning_cloud/openapi/models/v1_secret_type.py +1 -0
  279. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_type.py +1 -0
  280. lightning_sdk/lightning_cloud/openapi/models/v1_slack_notifier.py +201 -0
  281. lightning_sdk/lightning_cloud/openapi/models/v1_slack_notifier_type.py +105 -0
  282. lightning_sdk/lightning_cloud/openapi/models/v1_storage_asset.py +133 -3
  283. lightning_sdk/lightning_cloud/openapi/models/v1_storage_transfer.py +435 -0
  284. lightning_sdk/lightning_cloud/openapi/models/v1_storage_transfer_status.py +108 -0
  285. lightning_sdk/lightning_cloud/openapi/models/v1_token_login_request.py +123 -0
  286. lightning_sdk/lightning_cloud/openapi/models/v1_token_login_response.py +123 -0
  287. lightning_sdk/lightning_cloud/openapi/models/v1_token_owner_type.py +104 -0
  288. lightning_sdk/lightning_cloud/openapi/models/v1_update_cloud_space_instance_config_request.py +3 -81
  289. lightning_sdk/lightning_cloud/openapi/models/v1_update_project_tab_order_response.py +123 -0
  290. lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +41 -15
  291. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +444 -600
  292. lightning_sdk/lightning_cloud/openapi/models/v1_validate_license_response.py +123 -0
  293. lightning_sdk/lightning_cloud/openapi/models/v1_validate_storage_transfer_response.py +123 -0
  294. lightning_sdk/lightning_cloud/openapi/models/v1_voltage_park_direct_v1.py +29 -3
  295. lightning_sdk/lightning_cloud/rest_client.py +48 -45
  296. lightning_sdk/lightning_cloud/utils/data_connection.py +51 -1
  297. lightning_sdk/llm/llm.py +175 -56
  298. lightning_sdk/llm/public_assistants.py +44 -7
  299. lightning_sdk/machine.py +21 -2
  300. lightning_sdk/mmt/base.py +7 -0
  301. lightning_sdk/mmt/mmt.py +11 -3
  302. lightning_sdk/mmt/v1.py +3 -1
  303. lightning_sdk/mmt/v2.py +4 -0
  304. lightning_sdk/owner.py +2 -1
  305. lightning_sdk/pipeline/steps.py +6 -0
  306. lightning_sdk/plugin.py +6 -1
  307. lightning_sdk/studio.py +294 -53
  308. lightning_sdk/teamspace.py +167 -7
  309. lightning_sdk/user.py +19 -1
  310. lightning_sdk/utils/config.py +179 -0
  311. lightning_sdk/utils/license.py +13 -0
  312. lightning_sdk/utils/logging.py +79 -0
  313. lightning_sdk/utils/names.py +1179 -0
  314. lightning_sdk/utils/progress.py +283 -0
  315. lightning_sdk/utils/resolve.py +82 -7
  316. {lightning_sdk-2025.8.6rc2.dist-info → lightning_sdk-2025.11.5.dist-info}/METADATA +2 -1
  317. {lightning_sdk-2025.8.6rc2.dist-info → lightning_sdk-2025.11.5.dist-info}/RECORD +328 -169
  318. {lightning_sdk-2025.8.6rc2.dist-info → lightning_sdk-2025.11.5.dist-info}/entry_points.txt +1 -0
  319. lightning_sdk/lightning_cloud/openapi/models/v1_product_license.py +0 -435
  320. lightning_sdk/services/license.py +0 -363
  321. /lightning_sdk/cli/{deploy → legacy}/__init__.py +0 -0
  322. /lightning_sdk/cli/{ai_hub.py → legacy/ai_hub.py} +0 -0
  323. /lightning_sdk/cli/{docker_cli.py → legacy/docker_cli.py} +0 -0
  324. /lightning_sdk/cli/{exceptions.py → legacy/exceptions.py} +0 -0
  325. /lightning_sdk/cli/{run.py → legacy/run.py} +0 -0
  326. /lightning_sdk/cli/{studios_menu.py → legacy/studios_menu.py} +0 -0
  327. /lightning_sdk/cli/{coloring.py → utils/coloring.py} +0 -0
  328. {lightning_sdk-2025.8.6rc2.dist-info → lightning_sdk-2025.11.5.dist-info}/LICENSE +0 -0
  329. {lightning_sdk-2025.8.6rc2.dist-info → lightning_sdk-2025.11.5.dist-info}/WHEEL +0 -0
  330. {lightning_sdk-2025.8.6rc2.dist-info → lightning_sdk-2025.11.5.dist-info}/top_level.txt +0 -0
lightning_sdk/plugin.py CHANGED
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, Generator, Optional, Protocol, Union, run
9
9
  from lightning_sdk.job import Job
10
10
  from lightning_sdk.machine import Machine
11
11
  from lightning_sdk.studio import Studio
12
+ from lightning_sdk.utils.logging import TrackCallsABCMeta
12
13
  from lightning_sdk.utils.resolve import (
13
14
  _LIGHTNING_SERVICE_EXECUTION_ID_KEY,
14
15
  _resolve_deprecated_cloud_compute,
@@ -22,7 +23,7 @@ if TYPE_CHECKING:
22
23
  _logger = _setup_logger(__name__)
23
24
 
24
25
 
25
- class _Plugin(ABC):
26
+ class _Plugin(ABC, metaclass=TrackCallsABCMeta):
26
27
  """Abstract Plugin class defining the API.
27
28
 
28
29
  Args:
@@ -128,6 +129,7 @@ class JobsPlugin(_Plugin):
128
129
  machine: Machine = Machine.CPU,
129
130
  cloud_compute: Optional[Machine] = None,
130
131
  interruptible: bool = False,
132
+ reuse_snapshot: bool = True,
131
133
  ) -> Job:
132
134
  """Launches an asynchronous job.
133
135
 
@@ -137,6 +139,8 @@ class JobsPlugin(_Plugin):
137
139
  machine: The machine to run the job on.
138
140
  interruptible: Whether to run the job on an interruptible machine.
139
141
  These are cheaper but can be preempted at any time.
142
+ reuse_snapshot: Whether the job should reuse a Studio snapshot when multiple jobs for the same Studio are
143
+ submitted. Turning this off may result in longer job startup times. Defaults to True.
140
144
  """
141
145
  if not name:
142
146
  name = _run_name("job")
@@ -151,6 +155,7 @@ class JobsPlugin(_Plugin):
151
155
  teamspace=self._studio.teamspace,
152
156
  cloud_account=self._studio.cloud_account,
153
157
  interruptible=interruptible,
158
+ reuse_snapshot=reuse_snapshot,
154
159
  )
155
160
 
156
161
 
lightning_sdk/studio.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import glob
2
2
  import os
3
+ import threading
3
4
  import warnings
4
5
  from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Tuple, Union
5
6
 
@@ -7,13 +8,17 @@ from tqdm.auto import tqdm
7
8
 
8
9
  from lightning_sdk.api.cloud_account_api import CloudAccountApi
9
10
  from lightning_sdk.api.studio_api import StudioApi
11
+ from lightning_sdk.base_studio import BaseStudio
10
12
  from lightning_sdk.constants import _LIGHTNING_DEBUG
11
- from lightning_sdk.machine import CloudProvider, Machine
13
+ from lightning_sdk.lightning_cloud.openapi import V1ClusterType
14
+ from lightning_sdk.machine import DEFAULT_MACHINE, CloudProvider, Machine
12
15
  from lightning_sdk.organization import Organization
13
16
  from lightning_sdk.owner import Owner
14
17
  from lightning_sdk.status import Status
15
18
  from lightning_sdk.teamspace import Teamspace
16
19
  from lightning_sdk.user import User
20
+ from lightning_sdk.utils.logging import TrackCallsMeta
21
+ from lightning_sdk.utils.names import random_unique_name
17
22
  from lightning_sdk.utils.resolve import (
18
23
  _get_org_id,
19
24
  _resolve_deprecated_cluster,
@@ -30,7 +35,7 @@ if TYPE_CHECKING:
30
35
  _logger = _setup_logger(__name__)
31
36
 
32
37
 
33
- class Studio:
38
+ class Studio(metaclass=TrackCallsMeta):
34
39
  """A single Lightning AI Studio.
35
40
 
36
41
  Allows to fully control a studio, including retrieving the status, running commands
@@ -45,9 +50,11 @@ class Studio:
45
50
  Doesn't matter when the studio already exists.
46
51
  cloud_account_provider: The provider to select the cloud-account from.
47
52
  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.
53
+ If not specified, falls back to the teamspace default cloud account.
49
54
  create_ok: whether the studio will be created if it does not yet exist. Defaults to True
50
55
  provider: the provider of the machine, the studio should be created on.
56
+ studio_type: Type of studio to create. Only effective during initial creation;
57
+ ignored for existing studios.
51
58
 
52
59
  Note:
53
60
  Since a teamspace can either be owned by an org or by a user directly,
@@ -56,7 +63,11 @@ class Studio:
56
63
  """
57
64
 
58
65
  # skips init of studio, only set when using this as a shell for names, ids etc.
59
- _skip_init = False
66
+ _skip_init = threading.local()
67
+ _skip_setup = threading.local()
68
+
69
+ # whether to show progress bars during operations
70
+ show_progress = False
60
71
 
61
72
  def __init__(
62
73
  self,
@@ -71,56 +82,117 @@ class Studio:
71
82
  source: Optional[str] = None,
72
83
  disable_secrets: bool = False,
73
84
  provider: Optional[Union[CloudProvider, str]] = None, # deprecated in favor of cloud_provider
85
+ studio_type: Optional[str] = None, # for base studio templates
74
86
  ) -> None:
75
87
  self._studio_api = StudioApi()
76
88
  self._cloud_account_api = CloudAccountApi()
77
89
 
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")
90
+ self._prevent_refetch = False
91
+ self._teamspace = None
92
+
93
+ # don't resolve anything if we're skipping init
94
+ if not getattr(self._skip_init, "value", False):
95
+ _teamspace = _resolve_teamspace(teamspace=teamspace, org=org, user=user)
96
+ if _teamspace is None:
97
+ raise ValueError("Couldn't resolve teamspace from the provided name, org, or user")
81
98
 
82
- self._teamspace = _teamspace
83
- self._cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
99
+ self._teamspace = _teamspace
84
100
 
85
- self._setup_done = False
101
+ self._setup_done = getattr(self._skip_setup, "value", False)
86
102
  self._disable_secrets = disable_secrets
87
103
 
88
104
  self._plugins = {}
105
+ self._studio = None
106
+
107
+ # Check to see if we're inside a studio
108
+ current_studio = None
109
+ studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
110
+ if studio_id is not None and self._teamspace is not None:
111
+ # We're inside a studio, get it by ID
112
+ current_studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
113
+
114
+ if cloud_account or not cloud_provider:
115
+ cloud_account = _resolve_deprecated_cluster(
116
+ cloud_account, cluster, current_studio.cluster_id if current_studio else None
117
+ )
118
+ cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
119
+ else:
120
+ cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
89
121
 
90
- cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
91
- cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
122
+ cls_name = self._cls_name
92
123
 
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
- )
124
+ # if we're skipping init, we don't need to resolve the cloud account as then we're not creating a studio
125
+ if self._teamspace is not None:
126
+ _cloud_account = self._cloud_account_api.resolve_cloud_account(
127
+ self._teamspace.id,
128
+ cloud_account=cloud_account,
129
+ cloud_provider=cloud_provider,
130
+ default_cloud_account=self._teamspace.default_cloud_account,
131
+ )
99
132
 
100
- if name is None:
101
- studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
102
- if studio_id is None:
103
- raise ValueError("Cannot autodetect Studio. Either use the SDK from within a Studio or pass a name!")
104
- self._studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
133
+ self._studio_type = None
134
+ if studio_type:
135
+ self._base_studio = BaseStudio(teamspace=self._teamspace)
136
+ self._available_base_studios = self._base_studio.list()
137
+ for bst in self._available_base_studios:
138
+ if (
139
+ bst.id == studio_type
140
+ or bst.name == studio_type
141
+ or bst.name.lower().replace(" ", "-") == studio_type
142
+ ):
143
+ self._studio_type = bst.id
144
+
145
+ if not self._studio_type:
146
+ raise ValueError(
147
+ f"Could not find studio type with ID or name '{studio_type}'. "
148
+ f"Available studio types: "
149
+ f"{[bst.name.lower().replace(' ', '-') for bst in self._available_base_studios]}"
150
+ )
105
151
  else:
152
+ if current_studio:
153
+ self._studio_type = current_studio.environment_template_id
154
+
155
+ # Resolve studio name if not provided: explicit → env (LIGHTNING_CLOUD_SPACE_ID) → config defaults
156
+ if name is None and not getattr(self._skip_init, "value", False):
157
+ if current_studio:
158
+ name = current_studio.name
159
+ else:
160
+ # Try config defaults
161
+ from lightning_sdk.utils.config import Config, DefaultConfigKeys
162
+
163
+ config = Config()
164
+ name = config.get_value(DefaultConfigKeys.studio)
165
+ if name is None and not create_ok:
166
+ raise ValueError(
167
+ f"Cannot autodetect {cls_name}. Either use the SDK from within a {cls_name} or pass a name!"
168
+ )
169
+
170
+ if self._studio is None and not getattr(self._skip_init, "value", False):
171
+ # If we have a name (explicit or from config), get studio by name
106
172
  try:
173
+ if name is None:
174
+ # if we don't have a name, raise an error to get
175
+ # to the exception path and optionally create a studio
176
+ raise ValueError(
177
+ f"Cannot autodetect {cls_name}. Either use the SDK from within a {cls_name} or pass a name!"
178
+ )
107
179
  self._studio = self._studio_api.get_studio(name, self._teamspace.id)
108
180
  except ValueError as e:
109
181
  if create_ok:
182
+ name = name or random_unique_name()
110
183
  self._studio = self._studio_api.create_studio(
111
184
  name,
112
185
  self._teamspace.id,
113
- cloud_account=self._cloud_account,
186
+ cloud_account=_cloud_account,
114
187
  source=source,
115
188
  disable_secrets=self._disable_secrets,
189
+ cloud_space_environment_template_id=self._studio_type,
116
190
  )
117
191
  else:
118
- raise ValueError(f"Studio {name} does not exist.") from e
119
-
120
- self._cloud_account = self._studio.cluster_id
192
+ raise e
121
193
 
122
194
  if (
123
- not self._skip_init
195
+ not getattr(self._skip_init, "value", False)
124
196
  and _internal_status_to_external_status(
125
197
  self._studio_api._get_studio_instance_status_from_object(self._studio)
126
198
  )
@@ -183,6 +255,14 @@ class Studio:
183
255
  _get_org_id(self._teamspace),
184
256
  )
185
257
 
258
+ @property
259
+ def public_ip(self) -> Optional[str]:
260
+ """Returns the public IP address of the machine the Studio is running on."""
261
+ return self._studio_api.get_public_ip(
262
+ self._studio.id,
263
+ self._teamspace.id,
264
+ )
265
+
186
266
  @property
187
267
  def interruptible(self) -> bool:
188
268
  """Returns whether the Studio is running on a interruptible instance."""
@@ -194,7 +274,9 @@ class Studio:
194
274
  @property
195
275
  def cluster(self) -> str:
196
276
  """Returns the cluster the Studio is running on."""
197
- warnings.warn("Studio.cluster is deprecated. Use Studio.cloud_account instead", DeprecationWarning)
277
+ warnings.warn(
278
+ f"{self._cls_name}.cluster is deprecated. Use {self._cls_name}.cloud_account instead", DeprecationWarning
279
+ )
198
280
  return self.cloud_account
199
281
 
200
282
  @property
@@ -203,7 +285,7 @@ class Studio:
203
285
 
204
286
  def start(
205
287
  self,
206
- machine: Union[Machine, str] = Machine.CPU,
288
+ machine: Optional[Union[Machine, str]] = None,
207
289
  interruptible: Optional[bool] = None,
208
290
  max_runtime: Optional[int] = None,
209
291
  ) -> None:
@@ -218,6 +300,22 @@ class Studio:
218
300
  Defaults to 3h
219
301
 
220
302
  """
303
+ # Check to see if we're inside a studio and if its running
304
+ current_studio_machine = None
305
+ studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
306
+ if studio_id is not None:
307
+ # We're inside a studio, get the machine if it is running
308
+ current_studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
309
+ current_status = self._studio_api._get_studio_instance_status_from_object(current_studio)
310
+
311
+ if current_status and _internal_status_to_external_status(current_status) == Status.Running:
312
+ current_studio_machine = self._studio_api.get_machine(
313
+ current_studio.id,
314
+ self._teamspace.id,
315
+ current_studio.cluster_id,
316
+ _get_org_id(self._teamspace),
317
+ )
318
+
221
319
  status = self.status
222
320
 
223
321
  if interruptible is None:
@@ -227,23 +325,53 @@ class Studio:
227
325
  else:
228
326
  interruptible = self.teamspace.start_studios_on_interruptible
229
327
 
230
- if status == Status.Running:
328
+ new_machine = DEFAULT_MACHINE
329
+ if machine is not None:
231
330
  new_machine = machine
232
- if not isinstance(machine, Machine):
233
- new_machine = Machine.from_str(machine)
331
+ elif current_studio_machine is not None:
332
+ new_machine = current_studio_machine
333
+
334
+ if not isinstance(new_machine, Machine):
335
+ new_machine = Machine.from_str(new_machine)
336
+
337
+ if status == Status.Running:
234
338
  if new_machine != self.machine:
235
339
  raise RuntimeError(
236
- f"Requested to start studio on {new_machine}, but studio is already running on {self.machine}."
340
+ f"Requested to start {self._cls_name} on {new_machine}, "
341
+ "but {self._cls_name} is already running on {self.machine}."
237
342
  " Consider switching instead!"
238
343
  )
239
- _logger.info(f"Studio {self.name} is already running")
344
+ _logger.info(f"{self._cls_name} {self.name} is already running")
240
345
  return
241
346
 
242
347
  if status != Status.Stopped:
243
- raise RuntimeError(f"Cannot start a studio that is not stopped. Studio {self.name} is {status}.")
244
- self._studio_api.start_studio(
245
- self._studio.id, self._teamspace.id, machine, interruptible=interruptible, max_runtime=max_runtime
246
- )
348
+ raise RuntimeError(
349
+ f"Cannot start a {self._cls_name} that is not stopped. {self._cls_name} {self.name} is {status}."
350
+ )
351
+
352
+ # Show progress bar during startup
353
+ if self.show_progress:
354
+ from lightning_sdk.utils.progress import StudioProgressTracker
355
+
356
+ with StudioProgressTracker("start", show_progress=True) as progress:
357
+ # Start the studio without blocking
358
+ self._studio_api.start_studio_async(
359
+ self._studio.id,
360
+ self._teamspace.id,
361
+ new_machine,
362
+ interruptible=interruptible,
363
+ max_runtime=max_runtime,
364
+ )
365
+
366
+ # Track progress through completion
367
+ progress.track_startup_phases(
368
+ lambda: self._studio_api.get_studio_status(self._studio.id, self._teamspace.id)
369
+ )
370
+ else:
371
+ # Use the blocking version if no progress is needed
372
+ self._studio_api.start_studio(
373
+ self._studio.id, self._teamspace.id, new_machine, interruptible=interruptible, max_runtime=max_runtime
374
+ )
247
375
 
248
376
  self._setup()
249
377
 
@@ -258,13 +386,20 @@ class Studio:
258
386
  """Deletes the current Studio."""
259
387
  self._studio_api.delete_studio(self._studio.id, self._teamspace.id)
260
388
 
261
- def duplicate(self, target_teamspace: Optional[Union["Teamspace", str]] = None) -> "Studio":
389
+ def duplicate(
390
+ self,
391
+ target_teamspace: Optional[Union["Teamspace", str]] = None,
392
+ machine: Machine = Machine.CPU,
393
+ name: Optional[str] = None,
394
+ ) -> "Studio":
262
395
  """Duplicates the existing Studio.
263
396
 
264
397
  Args:
265
398
  target_teamspace: the teamspace to duplicate the studio to.
266
399
  Must have the same owner as the source teamspace.
267
400
  If not provided, defaults to current teamspace.
401
+ machine: the machine to start the duplicated studio on.
402
+ Defaults to CPU
268
403
  """
269
404
  if target_teamspace is None:
270
405
  target_teamspace_id = self._teamspace.id
@@ -284,16 +419,23 @@ class Studio:
284
419
  target_teamspace_id = target_teamspace.id
285
420
 
286
421
  kwargs = self._studio_api.duplicate_studio(
287
- studio_id=self._studio.id, teamspace_id=self._teamspace.id, target_teamspace_id=target_teamspace_id
422
+ studio_id=self._studio.id,
423
+ teamspace_id=self._teamspace.id,
424
+ target_teamspace_id=target_teamspace_id,
425
+ machine=machine,
426
+ new_name=name,
288
427
  )
289
428
  return Studio(**kwargs)
290
429
 
291
- def switch_machine(self, machine: Union[Machine, str], interruptible: bool = False) -> None:
430
+ def switch_machine(
431
+ self, machine: Union[Machine, str], interruptible: bool = False, cloud_provider: Optional[CloudProvider] = None
432
+ ) -> None:
292
433
  """Switches machine to the provided machine type/.
293
434
 
294
435
  Args:
295
436
  machine: the new machine type to switch to
296
437
  interruptible: determines whether to switch to an interruptible instance
438
+ cloud_provider: the cloud provider to switch to, has no effect if the Studio is not on Lightning Cloud
297
439
 
298
440
  Note:
299
441
  this call is blocking until the new machine is provisioned
@@ -302,12 +444,49 @@ class Studio:
302
444
  status = self.status
303
445
  if status != Status.Running:
304
446
  raise RuntimeError(
305
- f"Cannot switch machine on a studio that is not running. Studio {self.name} is {status}."
447
+ f"Cannot switch machine on a {self._cls_name} that is not running. "
448
+ "{self._cls_name} {self.name} is {status}."
306
449
  )
307
- self._studio_api.switch_studio_machine(
308
- self._studio.id, self._teamspace.id, machine, interruptible=interruptible
450
+
451
+ current_cloud = self._cloud_account_api.get_cloud_account_non_org(
452
+ self._teamspace.id,
453
+ self._studio.cluster_id,
309
454
  )
310
455
 
456
+ cloud_account = ""
457
+ if cloud_provider is not None and current_cloud.spec.cluster_type == V1ClusterType.GLOBAL:
458
+ cloud_account = self._cloud_account_api.resolve_cloud_account(
459
+ self._teamspace.id,
460
+ cloud_account=None,
461
+ cloud_provider=cloud_provider,
462
+ default_cloud_account=None,
463
+ )
464
+
465
+ if self.show_progress:
466
+ from lightning_sdk.utils.progress import StudioProgressTracker
467
+
468
+ with StudioProgressTracker("switch", show_progress=True) as progress:
469
+ # Update progress before starting the switch
470
+ progress.update_progress(5, "Initiating machine switch...")
471
+
472
+ # Start the switch operation with progress tracking
473
+ self._studio_api.switch_studio_machine_with_progress(
474
+ self._studio.id,
475
+ self._teamspace.id,
476
+ machine,
477
+ interruptible=interruptible,
478
+ progress=progress,
479
+ cloud_account=cloud_account,
480
+ )
481
+ else:
482
+ self._studio_api.switch_studio_machine(
483
+ self._studio.id, self._teamspace.id, machine, interruptible=interruptible, cloud_account=cloud_account
484
+ )
485
+
486
+ if self._studio and cloud_account:
487
+ # TODO: get this from the API
488
+ self._studio.cluster_id = cloud_account
489
+
311
490
  def run_and_detach(self, *commands: str, timeout: float = 10, check_interval: float = 1) -> str:
312
491
  """Runs given commands on the Studio and returns immediately.
313
492
 
@@ -324,7 +503,10 @@ class Studio:
324
503
  print(f"Running {commands=}")
325
504
  status = self.status
326
505
  if status != Status.Running:
327
- raise RuntimeError(f"Cannot run a command in a studio that is not running. Studio {self.name} is {status}.")
506
+ raise RuntimeError(
507
+ f"Cannot run a command in a {self._cls_name} that is not running. "
508
+ "{self._cls_name} {self.name} is {status}."
509
+ )
328
510
 
329
511
  iter_output = self._studio_api.run_studio_commands_and_yield(
330
512
  self._studio.id, self._teamspace.id, *commands, timeout=timeout, check_interval=check_interval
@@ -350,7 +532,10 @@ class Studio:
350
532
 
351
533
  status = self.status
352
534
  if status != Status.Running:
353
- raise RuntimeError(f"Cannot run a command in a studio that is not running. Studio {self.name} is {status}.")
535
+ raise RuntimeError(
536
+ f"Cannot run a command in a {self._cls_name} that is not running. "
537
+ "{self._cls_name} {self.name} is {status}."
538
+ )
354
539
  output, exit_code = self._studio_api.run_studio_commands(self._studio.id, self._teamspace.id, *commands)
355
540
  output = output.strip()
356
541
 
@@ -446,6 +631,7 @@ class Studio:
446
631
  command: str,
447
632
  env: Optional[Dict[str, str]] = None,
448
633
  interruptible: bool = False,
634
+ reuse_snapshot: bool = True,
449
635
  ) -> "Job":
450
636
  """Run async workloads using the compute environment from your studio.
451
637
 
@@ -455,6 +641,8 @@ class Studio:
455
641
  command: The command to run inside your job.
456
642
  env: Environment variables to set inside the job.
457
643
  interruptible: Whether the job should run on interruptible instances. They are cheaper but can be preempted.
644
+ reuse_snapshot: Whether the job should reuse a Studio snapshot when multiple jobs for the same Studio are
645
+ submitted. Turning this off may result in longer job startup times. Defaults to True.
458
646
  """
459
647
  from lightning_sdk.job import Job
460
648
 
@@ -468,6 +656,7 @@ class Studio:
468
656
  cloud_account=self.cloud_account,
469
657
  env=env,
470
658
  interruptible=interruptible,
659
+ reuse_snapshot=reuse_snapshot,
471
660
  )
472
661
 
473
662
  def run_mmt(
@@ -512,6 +701,14 @@ class Studio:
512
701
  self._assistant_id = assistant.id
513
702
  _logger.info(assistant_info)
514
703
 
704
+ def rename(self, new_name: str) -> None:
705
+ """Renames the current Studio to the provided new name."""
706
+ if new_name == self._studio.name:
707
+ return
708
+
709
+ self._studio_api._update_cloudspace(self._studio, self._teamspace.id, "display_name", new_name)
710
+ self._update_studio_reference()
711
+
515
712
  @property
516
713
  def auto_sleep(self) -> bool:
517
714
  """Returns if a Studio has auto-sleep enabled."""
@@ -520,8 +717,8 @@ class Studio:
520
717
  @auto_sleep.setter
521
718
  def auto_sleep(self, value: bool) -> None:
522
719
  if not value and self.machine == Machine.CPU:
523
- warnings.warn("Disabling auto-sleep will convert the Studio from free to paid!")
524
- self._studio_api.update_autoshutdown(self._studio.id, self._teamspace.id, enabled=value, studio=self._studio)
720
+ warnings.warn(f"Disabling auto-sleep will convert the {self._cls_name} from free to paid!")
721
+ self._studio_api.update_autoshutdown(self._studio.id, self._teamspace.id, enabled=value)
525
722
  self._update_studio_reference()
526
723
 
527
724
  @property
@@ -531,10 +728,8 @@ class Studio:
531
728
 
532
729
  @auto_sleep_time.setter
533
730
  def auto_sleep_time(self, value: int) -> None:
534
- warnings.warn("Setting auto-sleep time will convert the Studio from free to paid!")
535
- self._studio_api.update_autoshutdown(
536
- self._studio.id, self._teamspace.id, idle_shutdown_seconds=value, studio=self._studio
537
- )
731
+ warnings.warn(f"Setting auto-sleep time will convert the {self._cls_name} from free to paid!")
732
+ self._studio_api.update_autoshutdown(self._studio.id, self._teamspace.id, idle_shutdown_seconds=value)
538
733
  self._update_studio_reference()
539
734
 
540
735
  @property
@@ -557,6 +752,22 @@ class Studio:
557
752
  warnings.warn("auto_shutdown_time is deprecated. Use auto_sleep_time instead", DeprecationWarning)
558
753
  self.auto_sleep_time = value
559
754
 
755
+ @property
756
+ def env(self) -> Dict[str, str]:
757
+ self._update_studio_reference()
758
+ return self._studio_api.get_env(self._studio)
759
+
760
+ def set_env(self, new_env: Dict[str, str], partial: bool = True) -> None:
761
+ """Set the environment variables for the Studio.
762
+
763
+ Args:
764
+ new_env: The new environment variables to set.
765
+ partial: Whether to only set the environment variables that are provided.
766
+ If False, existing environment variables that are not in new_env will be removed.
767
+ If True, existing environment variables that are not in new_env will be kept.
768
+ """
769
+ self._studio_api.set_env(self._studio, self._teamspace.id, new_env, partial=partial)
770
+
560
771
  @property
561
772
  def available_plugins(self) -> Mapping[str, str]:
562
773
  """All available plugins to install in the current Studio."""
@@ -648,6 +859,36 @@ class Studio:
648
859
  def _update_studio_reference(self) -> None:
649
860
  self._studio = self._studio_api.get_studio_by_id(studio_id=self._studio.id, teamspace_id=self._teamspace.id)
650
861
 
862
+ @property
863
+ def _cls_name(self) -> str:
864
+ return self.__class__.__qualname__
865
+
866
+
867
+ class VM(Studio):
868
+ """A single Lightning AI VM.
869
+
870
+ Allows to fully control a vm, including retrieving the status, running commands
871
+ and switching machine types.
872
+
873
+ Args:
874
+ name: the name of the vm
875
+ teamspace: the name of the teamspace the vm is contained by
876
+ org: the name of the organization owning the :param`teamspace` in case it is owned by an org
877
+ user: the name of the user owning the :param`teamspace` in case it is owned directly by a user instead of an org
878
+ cloud_account: the name of the cloud account, the vm should be created on.
879
+ Doesn't matter when the vm already exists.
880
+ cloud_account_provider: The provider to select the cloud-account from.
881
+ If set, must be in agreement with the provider from the cloud_account (if specified).
882
+ If not specified, falls backto the teamspace default cloud account.
883
+ create_ok: whether the vm will be created if it does not yet exist. Defaults to True
884
+ provider: the provider of the machine, the vm should be created on.
885
+
886
+ Note:
887
+ Since a teamspace can either be owned by an org or by a user directly,
888
+ only one of the arguments can be provided.
889
+
890
+ """
891
+
651
892
 
652
893
  def _internal_status_to_external_status(internal_status: str) -> Status:
653
894
  """Converts internal status strings from HTTP requests to external enums."""