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
@@ -1,8 +1,7 @@
1
1
  import json
2
2
  import os
3
- import tempfile
4
3
  import time
5
- import zipfile
4
+ from pathlib import Path
6
5
  from threading import Event, Thread
7
6
  from typing import Any, Dict, Generator, List, Mapping, Optional, Tuple, Union
8
7
 
@@ -12,6 +11,7 @@ from tqdm import tqdm
12
11
 
13
12
  from lightning_sdk.api.utils import (
14
13
  _create_app,
14
+ _download_teamspace_files,
15
15
  _DummyBody,
16
16
  _DummyResponse,
17
17
  _FileUploader,
@@ -25,10 +25,12 @@ from lightning_sdk.constants import _LIGHTNING_DEBUG
25
25
  from lightning_sdk.lightning_cloud.login import Auth
26
26
  from lightning_sdk.lightning_cloud.openapi import (
27
27
  CloudspaceIdRunsBody,
28
+ CloudspacesIdBody,
28
29
  Externalv1LightningappInstance,
29
30
  IdCodeconfigBody,
30
31
  IdExecuteBody1,
31
32
  IdForkBody1,
33
+ IdSleepconfigBody,
32
34
  IdStartBody,
33
35
  ProjectIdCloudspacesBody,
34
36
  V1Assistant,
@@ -39,6 +41,7 @@ from lightning_sdk.lightning_cloud.openapi import (
39
41
  V1CloudSpaceState,
40
42
  V1ClusterAccelerator,
41
43
  V1EndpointType,
44
+ V1EnvVar,
42
45
  V1GetCloudSpaceInstanceStatusResponse,
43
46
  V1GetLongRunningCommandInCloudSpaceResponse,
44
47
  V1LoginRequest,
@@ -204,6 +207,32 @@ class StudioApi:
204
207
  instance_id = code_status.in_use.cloud_space_instance_id
205
208
  print(f"Studio started | {teamspace_id=} {studio_id=} {instance_id=}")
206
209
 
210
+ def start_studio_async(
211
+ self,
212
+ studio_id: str,
213
+ teamspace_id: str,
214
+ machine: Union[Machine, str],
215
+ interruptible: bool = False,
216
+ max_runtime: Optional[int] = None,
217
+ ) -> None:
218
+ """Start an existing Studio without blocking."""
219
+ # need to go via kwargs for typing compatibility since autogenerated apis accept None but aren't typed with None
220
+ optional_kwargs_compute_body = {}
221
+
222
+ if max_runtime is not None:
223
+ optional_kwargs_compute_body["requested_run_duration_seconds"] = str(max_runtime)
224
+ self._client.cloud_space_service_start_cloud_space_instance(
225
+ IdStartBody(
226
+ compute_config=V1UserRequestedComputeConfig(
227
+ name=_machine_to_compute_name(machine),
228
+ spot=interruptible,
229
+ **optional_kwargs_compute_body,
230
+ )
231
+ ),
232
+ teamspace_id,
233
+ studio_id,
234
+ )
235
+
207
236
  def stop_studio(self, studio_id: str, teamspace_id: str) -> None:
208
237
  """Stop an existing Studio."""
209
238
  self.stop_keeping_alive(teamspace_id=teamspace_id, studio_id=studio_id)
@@ -232,12 +261,19 @@ class StudioApi:
232
261
  return getattr(getattr(studio.code_status, "in_use", None), "phase", None)
233
262
 
234
263
  def _request_switch(
235
- self, studio_id: str, teamspace_id: str, machine: Union[Machine, str], interruptible: bool
264
+ self,
265
+ studio_id: str,
266
+ teamspace_id: str,
267
+ machine: Union[Machine, str],
268
+ interruptible: bool,
269
+ cloud_account: Optional[str],
236
270
  ) -> None:
237
271
  """Switches given Studio to a new machine type."""
238
272
  compute_name = _machine_to_compute_name(machine)
239
273
  # TODO: UI sends disk size here, maybe we need to also?
240
274
  body = IdCodeconfigBody(compute_config=V1UserRequestedComputeConfig(name=compute_name, spot=interruptible))
275
+ if cloud_account:
276
+ body.compute_config.cluster_override = cloud_account
241
277
  self._client.cloud_space_service_update_cloud_space_instance_config(
242
278
  id=studio_id,
243
279
  project_id=teamspace_id,
@@ -245,29 +281,136 @@ class StudioApi:
245
281
  )
246
282
 
247
283
  def switch_studio_machine(
248
- self, studio_id: str, teamspace_id: str, machine: Union[Machine, str], interruptible: bool
284
+ self,
285
+ studio_id: str,
286
+ teamspace_id: str,
287
+ machine: Union[Machine, str],
288
+ interruptible: bool,
289
+ cloud_account: Optional[str],
249
290
  ) -> None:
250
291
  """Switches given Studio to a new machine type."""
251
292
  self._request_switch(
252
- studio_id=studio_id, teamspace_id=teamspace_id, machine=machine, interruptible=interruptible
293
+ studio_id=studio_id,
294
+ teamspace_id=teamspace_id,
295
+ machine=machine,
296
+ interruptible=interruptible,
297
+ cloud_account=cloud_account,
298
+ )
299
+
300
+ # Wait until it's time to switch
301
+ requested_was_found = False
302
+ startup_status = None
303
+ while True:
304
+ status = self.get_studio_status(studio_id, teamspace_id)
305
+ requested_machine = status.requested
306
+
307
+ if requested_machine is not None:
308
+ requested_was_found = True
309
+ startup_status = requested_machine.startup_status
310
+
311
+ # if the requested machine was found in the past, use the in_use status instead.
312
+ # it might be that it either was cancelled or it actually is ready.
313
+ # Either way, since we're actually blocking below for the in use startup status
314
+ # it's safe to switch at this point
315
+ elif requested_was_found:
316
+ in_use_machine = status.in_use
317
+ if in_use_machine is not None:
318
+ startup_status = in_use_machine.startup_status
319
+
320
+ if startup_status and startup_status.initial_restore_finished:
321
+ break
322
+ time.sleep(1)
323
+
324
+ self._client.cloud_space_service_switch_cloud_space_instance(teamspace_id, studio_id)
325
+
326
+ # Wait until the new machine is ready to use
327
+ while True:
328
+ in_use = self.get_studio_status(studio_id, teamspace_id).in_use
329
+ if in_use is None:
330
+ continue
331
+ startup_status = in_use.startup_status
332
+ if startup_status and startup_status.top_up_restore_finished:
333
+ break
334
+ time.sleep(1)
335
+
336
+ def switch_studio_machine_with_progress(
337
+ self,
338
+ studio_id: str,
339
+ teamspace_id: str,
340
+ machine: Union[Machine, str],
341
+ interruptible: bool,
342
+ progress: Any, # StudioProgressTracker - avoid circular import
343
+ cloud_account: Optional[str],
344
+ ) -> None:
345
+ """Switches given Studio to a new machine type with progress tracking."""
346
+ progress.update_progress(10, "Requesting machine switch...")
347
+
348
+ self._request_switch(
349
+ studio_id=studio_id,
350
+ teamspace_id=teamspace_id,
351
+ machine=machine,
352
+ interruptible=interruptible,
353
+ cloud_account=cloud_account,
253
354
  )
254
355
 
356
+ progress.update_progress(20, "Waiting for machine allocation...")
357
+
255
358
  # Wait until it's time to switch
359
+ requested_was_found = False
360
+ startup_status = None
361
+ base_progress = 20
362
+ max_wait_progress = 60
363
+ wait_counter = 0
364
+
256
365
  while True:
257
- startup_status = self.get_studio_status(studio_id, teamspace_id).requested.startup_status
366
+ status = self.get_studio_status(studio_id, teamspace_id)
367
+ requested_machine = status.requested
368
+
369
+ if requested_machine is not None:
370
+ requested_was_found = True
371
+ startup_status = requested_machine.startup_status
372
+
373
+ # if the requested machine was found in the past, use the in_use status instead.
374
+ # it might be that it either was cancelled or it actually is ready.
375
+ # Either way, since we're actually blocking below for the in use startup status
376
+ # it's safe to switch at this point
377
+ elif requested_was_found:
378
+ in_use_machine = status.in_use
379
+ if in_use_machine is not None:
380
+ startup_status = in_use_machine.startup_status
381
+
258
382
  if startup_status and startup_status.initial_restore_finished:
259
383
  break
384
+
385
+ # Update progress gradually while waiting
386
+ wait_counter += 1
387
+ current_progress = min(base_progress + (wait_counter * 2), max_wait_progress)
388
+ progress.update_progress(current_progress, "Allocating new machine...")
260
389
  time.sleep(1)
261
390
 
391
+ progress.update_progress(70, "Starting machine switch...")
262
392
  self._client.cloud_space_service_switch_cloud_space_instance(teamspace_id, studio_id)
263
393
 
394
+ progress.update_progress(80, "Configuring new machine...")
395
+
264
396
  # Wait until the new machine is ready to use
397
+ switch_counter = 0
265
398
  while True:
266
- startup_status = self.get_studio_status(studio_id, teamspace_id).in_use.startup_status
399
+ in_use = self.get_studio_status(studio_id, teamspace_id).in_use
400
+ if in_use is None:
401
+ continue
402
+ startup_status = in_use.startup_status
267
403
  if startup_status and startup_status.top_up_restore_finished:
268
404
  break
405
+
406
+ # Update progress while waiting for machine to be ready
407
+ switch_counter += 1
408
+ current_progress = min(80 + switch_counter, 95)
409
+ progress.update_progress(current_progress, "Finalizing machine setup...")
269
410
  time.sleep(1)
270
411
 
412
+ progress.complete("Machine switch completed successfully")
413
+
271
414
  def get_machine(self, studio_id: str, teamspace_id: str, cloud_account_id: str, org_id: str) -> Machine:
272
415
  """Get the current machine type the given Studio is running on."""
273
416
  response: V1CloudSpaceInstanceConfig = self._client.cloud_space_service_get_cloud_space_instance_config(
@@ -283,7 +426,7 @@ class StudioApi:
283
426
  accelerator.slug_multi_cloud,
284
427
  accelerator.instance_id,
285
428
  ):
286
- return Machine.from_str(accelerator.slug_multi_cloud)
429
+ return Machine._from_accelerator(accelerator)
287
430
 
288
431
  return Machine.from_str(response.compute_config.name)
289
432
 
@@ -295,6 +438,14 @@ class StudioApi:
295
438
 
296
439
  return response.compute_config.spot
297
440
 
441
+ def get_public_ip(self, studio_id: str, teamspace_id: str) -> Optional[str]:
442
+ """Get the public IP address of the Studio."""
443
+ internal_status = self.get_studio_status(studio_id=studio_id, teamspace_id=teamspace_id).in_use
444
+ if internal_status is None:
445
+ return None
446
+
447
+ return internal_status.public_ip_address
448
+
298
449
  def _get_machines_for_cloud_account(
299
450
  self, teamspace_id: str, cloud_account_id: str, org_id: str
300
451
  ) -> List[V1ClusterAccelerator]:
@@ -423,25 +574,28 @@ class StudioApi:
423
574
  self,
424
575
  studio_id: str,
425
576
  teamspace_id: str,
426
- studio: V1CloudSpace,
427
577
  enabled: Optional[bool] = None,
428
- idle_shutdown_seconds: int = 0,
429
- ) -> None:
430
- """Update the autoshutdown time of the given Studio."""
431
- if enabled is None:
432
- enabled = not studio.code_config.disable_auto_shutdown
433
- body = IdCodeconfigBody(
434
- disable_auto_shutdown=not enabled,
578
+ idle_shutdown_seconds: Optional[int] = None,
579
+ ) -> V1CloudSpaceInstanceConfig:
580
+ """Update the autoshutdown time and behaviour of the given Studio."""
581
+ body = IdSleepconfigBody(
582
+ disable_auto_shutdown=not enabled if enabled is not None else None,
435
583
  idle_shutdown_seconds=idle_shutdown_seconds,
436
- compute_config=studio.code_config.compute_config,
437
584
  )
438
- self._client.cloud_space_service_update_cloud_space_instance_config(
585
+ return self._client.cloud_space_service_update_cloud_space_sleep_config(
439
586
  id=studio_id,
440
587
  project_id=teamspace_id,
441
588
  body=body,
442
589
  )
443
590
 
444
- def duplicate_studio(self, studio_id: str, teamspace_id: str, target_teamspace_id: str) -> Dict[str, Any]:
591
+ def duplicate_studio(
592
+ self,
593
+ studio_id: str,
594
+ teamspace_id: str,
595
+ target_teamspace_id: str,
596
+ machine: Machine = Machine.CPU,
597
+ new_name: Optional[str] = None,
598
+ ) -> Dict[str, Any]:
445
599
  """Duplicates the given Studio from a given Teamspace into a given target Teamspace."""
446
600
  target_teamspace = self._client.projects_service_get_project(target_teamspace_id)
447
601
  init_kwargs = {}
@@ -455,7 +609,7 @@ class StudioApi:
455
609
  init_kwargs["org"] = OrgApi()._get_org_by_id(target_teamspace.owner_id).name
456
610
 
457
611
  new_cloudspace = self._client.cloud_space_service_fork_cloud_space(
458
- IdForkBody1(target_project_id=target_teamspace_id), project_id=teamspace_id, id=studio_id
612
+ IdForkBody1(target_project_id=target_teamspace_id, new_name=new_name), project_id=teamspace_id, id=studio_id
459
613
  )
460
614
 
461
615
  while self.get_studio_by_id(new_cloudspace.id, target_teamspace_id).state != V1CloudSpaceState.READY:
@@ -464,7 +618,7 @@ class StudioApi:
464
618
  init_kwargs["name"] = new_cloudspace.name
465
619
  init_kwargs["teamspace"] = target_teamspace.name
466
620
 
467
- self.start_studio(new_cloudspace.id, target_teamspace_id, Machine.CPU, False, None)
621
+ self.start_studio(new_cloudspace.id, target_teamspace_id, machine, False, None)
468
622
  return init_kwargs
469
623
 
470
624
  def delete_studio(self, studio_id: str, teamspace_id: str) -> None:
@@ -550,46 +704,24 @@ class StudioApi:
550
704
  progress_bar: bool = True,
551
705
  ) -> None:
552
706
  """Downloads a given folder from a Studio to a target location."""
553
- # TODO: Update this endpoint to permit basic auth
707
+ # TODO: implement resumable downloads
554
708
  auth = Auth()
555
709
  auth.authenticate()
556
- token = self._client.auth_service_login(V1LoginRequest(auth.api_key)).token
557
710
 
558
- query_params = {
559
- "clusterId": cloud_account,
560
- "prefix": _sanitize_studio_remote_path(path, studio_id),
561
- "token": token,
562
- }
711
+ prefix = _sanitize_studio_remote_path(path, studio_id)
712
+ # ensure we only download as a directory and not the entire prefix
713
+ if prefix.endswith("/") is False:
714
+ prefix = prefix + "/"
563
715
 
564
- r = requests.get(
565
- f"{self._client.api_client.configuration.host}/v1/projects/{teamspace_id}/artifacts/download",
566
- params=query_params,
567
- stream=True,
716
+ _download_teamspace_files(
717
+ client=self._client,
718
+ teamspace_id=teamspace_id,
719
+ cluster_id=cloud_account,
720
+ prefix=prefix,
721
+ download_dir=Path(target_path),
722
+ progress_bar=progress_bar,
568
723
  )
569
724
 
570
- if progress_bar:
571
- pbar = tqdm(
572
- desc=f"Downloading {os.path.split(path)[1]}",
573
- unit="B",
574
- unit_scale=True,
575
- unit_divisor=1000,
576
- )
577
-
578
- pbar_update = pbar.update
579
- else:
580
- pbar_update = lambda x: None
581
-
582
- if target_path:
583
- os.makedirs(target_path, exist_ok=True)
584
-
585
- with tempfile.TemporaryFile() as f:
586
- for chunk in r.iter_content(chunk_size=4096 * 8):
587
- f.write(chunk)
588
- pbar_update(len(chunk))
589
-
590
- with zipfile.ZipFile(f) as z:
591
- z.extractall(target_path)
592
-
593
725
  def install_plugin(self, studio_id: str, teamspace_id: str, plugin_name: str) -> str:
594
726
  """Installs the given plugin."""
595
727
  resp: V1Plugin = self._client.cloud_space_service_install_plugin(
@@ -844,3 +976,68 @@ class StudioApi:
844
976
  plugin_type=plugin_type,
845
977
  **other_arguments,
846
978
  )
979
+
980
+ def _update_cloudspace(self, studio: V1CloudSpace, teamspace_id: str, key: str, value: Any) -> None:
981
+ body = CloudspacesIdBody(
982
+ code_url=studio.code_url,
983
+ data_connection_mounts=studio.data_connection_mounts,
984
+ description=studio.description,
985
+ display_name=studio.display_name,
986
+ env=studio.env,
987
+ featured=studio.featured,
988
+ hide_files=studio.hide_files,
989
+ is_cloudspace_private=studio.is_cloudspace_private,
990
+ is_code_private=studio.is_code_private,
991
+ is_favorite=studio.is_favorite,
992
+ is_published=studio.is_published,
993
+ license=studio.license,
994
+ license_url=studio.license_url,
995
+ message=studio.message,
996
+ multi_user_edit=studio.multi_user_edit,
997
+ operating_cost=studio.operating_cost,
998
+ paper_authors=studio.paper_authors,
999
+ paper_org=studio.paper_org,
1000
+ paper_org_avatar_url=studio.paper_org_avatar_url,
1001
+ paper_url=studio.paper_url,
1002
+ switch_to_default_machine_on_idle=studio.switch_to_default_machine_on_idle,
1003
+ tags=studio.tags,
1004
+ thumbnail_file_type=studio.thumbnail_file_type,
1005
+ user_metadata=studio.user_metadata,
1006
+ )
1007
+
1008
+ setattr(body, key, value)
1009
+
1010
+ self._client.cloud_space_service_update_cloud_space(
1011
+ id=studio.id,
1012
+ project_id=teamspace_id,
1013
+ body=body,
1014
+ )
1015
+
1016
+ def set_env(
1017
+ self,
1018
+ studio: V1CloudSpace,
1019
+ teamspace_id: str,
1020
+ new_env: Dict[str, str],
1021
+ partial: bool = True,
1022
+ ) -> None:
1023
+ """Set the environment variables for the Studio.
1024
+
1025
+ Args:
1026
+ new_env: The new environment variables to set.
1027
+ partial: Whether to only set the environment variables that are provided.
1028
+ If False, existing environment variables that are not in new_env will be removed.
1029
+ If True, existing environment variables that are not in new_env will be kept.
1030
+ """
1031
+ updated_env_dict = {}
1032
+ if partial:
1033
+ updated_env_dict = {env.name: env.value for env in studio.env}
1034
+ updated_env_dict.update(new_env)
1035
+ else:
1036
+ updated_env_dict = new_env
1037
+
1038
+ updated_env = [V1EnvVar(name=key, value=value) for key, value in updated_env_dict.items()]
1039
+
1040
+ self._update_cloudspace(studio, teamspace_id, "env", updated_env)
1041
+
1042
+ def get_env(self, studio: V1CloudSpace) -> Dict[str, str]:
1043
+ return {env.name: env.value for env in studio.env}
@@ -1,6 +1,5 @@
1
1
  import os
2
- import tempfile
3
- import zipfile
2
+ import re
4
3
  from pathlib import Path
5
4
  from typing import Dict, List, Optional, Tuple
6
5
 
@@ -9,6 +8,7 @@ from tqdm.auto import tqdm
9
8
 
10
9
  from lightning_sdk.api.utils import (
11
10
  _download_model_files,
11
+ _download_teamspace_files,
12
12
  _DummyBody,
13
13
  _FileUploader,
14
14
  _get_model_version,
@@ -17,15 +17,21 @@ from lightning_sdk.api.utils import (
17
17
  )
18
18
  from lightning_sdk.lightning_cloud.login import Auth
19
19
  from lightning_sdk.lightning_cloud.openapi import (
20
+ Create,
20
21
  Externalv1LightningappInstance,
21
22
  ModelIdVersionsBody,
22
23
  ModelsStoreApi,
23
24
  ProjectIdAgentsBody,
24
25
  ProjectIdModelsBody,
26
+ ProjectIdSecretsBody,
27
+ SecretsIdBody,
25
28
  V1Assistant,
26
29
  V1CloudSpace,
27
30
  V1ClusterAccelerator,
31
+ V1EfsConfig,
28
32
  V1Endpoint,
33
+ V1ExternalCluster,
34
+ V1GCSFolderDataConnection,
29
35
  V1Job,
30
36
  V1LoginRequest,
31
37
  V1Model,
@@ -34,6 +40,10 @@ from lightning_sdk.lightning_cloud.openapi import (
34
40
  V1Project,
35
41
  V1ProjectClusterBinding,
36
42
  V1PromptSuggestion,
43
+ V1R2DataConnection,
44
+ V1S3FolderDataConnection,
45
+ V1Secret,
46
+ V1SecretType,
37
47
  V1UpstreamOpenAI,
38
48
  )
39
49
  from lightning_sdk.lightning_cloud.rest_client import LightningClient
@@ -63,7 +73,7 @@ class TeamspaceApi:
63
73
  def _get_teamspace_by_id(self, teamspace_id: str) -> V1Project:
64
74
  return self._client.projects_service_get_project(teamspace_id)
65
75
 
66
- def list_teamspaces(self, owner_id: str, name: Optional[str] = None) -> Optional[V1Project]:
76
+ def list_teamspaces(self, owner_id: str, name: Optional[str] = None) -> Optional[List[V1Project]]:
67
77
  """Lists teamspaces from owner.
68
78
 
69
79
  If name is passed only teamspaces matching that name will be returned
@@ -386,6 +396,8 @@ class TeamspaceApi:
386
396
  params=query_params,
387
397
  stream=True,
388
398
  )
399
+ if r.status_code == 404:
400
+ raise FileNotFoundError(f"File {path} not found")
389
401
  total_length = int(r.headers.get("content-length"))
390
402
 
391
403
  if progress_bar:
@@ -421,39 +433,103 @@ class TeamspaceApi:
421
433
  # TODO: Update this endpoint to permit basic auth
422
434
  auth = Auth()
423
435
  auth.authenticate()
424
- token = self._client.auth_service_login(V1LoginRequest(auth.api_key)).token
425
436
 
426
- query_params = {
427
- "clusterId": cloud_account,
428
- "prefix": _resolve_teamspace_remote_path(path),
429
- "token": token,
430
- }
437
+ prefix = _resolve_teamspace_remote_path(path)
431
438
 
432
- r = requests.get(
433
- f"{self._client.api_client.configuration.host}/v1/projects/{teamspace_id}/artifacts/download",
434
- params=query_params,
435
- stream=True,
439
+ # ensure we only download as a directory and not the entire prefix
440
+ if prefix.endswith("/") is False:
441
+ prefix = prefix + "/"
442
+
443
+ _download_teamspace_files(
444
+ client=self._client,
445
+ teamspace_id=teamspace_id,
446
+ cluster_id=cloud_account,
447
+ prefix=prefix,
448
+ download_dir=Path(target_path),
449
+ progress_bar=progress_bar,
436
450
  )
437
451
 
438
- if progress_bar:
439
- pbar = tqdm(
440
- desc=f"Downloading {os.path.split(path)[1]}",
441
- unit="B",
442
- unit_scale=True,
443
- unit_divisor=1000,
444
- )
452
+ def get_secrets(self, teamspace_id: str) -> Dict[str, str]:
453
+ """Get all secrets for a teamspace."""
454
+ secrets = self._get_secrets(teamspace_id)
455
+ # this returns encrypted values for security. It doesn't make sense to show them,
456
+ # so we just return a placeholder
457
+ # not a security issue to replace in the client as we get the encrypted values from the server.
458
+ return {secret.name: "***REDACTED***" for secret in secrets if secret.type == V1SecretType.UNSPECIFIED}
445
459
 
446
- pbar_update = pbar.update
460
+ def set_secret(self, teamspace_id: str, key: str, value: str) -> None:
461
+ """Set a secret for a teamspace.
462
+
463
+ This will replace the existing secret if it exists and create a new one if it doesn't.
464
+ """
465
+ secrets = self._get_secrets(teamspace_id)
466
+ for secret in secrets:
467
+ if secret.name == key:
468
+ return self._update_secret(teamspace_id, secret.id, value)
469
+ return self._create_secret(teamspace_id, key, value)
470
+
471
+ def _get_secrets(self, teamspace_id: str) -> List[V1Secret]:
472
+ return self._client.secret_service_list_secrets(project_id=teamspace_id).secrets
473
+
474
+ def _update_secret(self, teamspace_id: str, secret_id: str, value: str) -> None:
475
+ self._client.secret_service_update_secret(
476
+ body=SecretsIdBody(value=value),
477
+ project_id=teamspace_id,
478
+ id=secret_id,
479
+ )
480
+
481
+ def _create_secret(
482
+ self,
483
+ teamspace_id: str,
484
+ key: str,
485
+ value: str,
486
+ ) -> None:
487
+ self._client.secret_service_create_secret(
488
+ body=ProjectIdSecretsBody(name=key, value=value, type=V1SecretType.UNSPECIFIED), project_id=teamspace_id
489
+ )
490
+
491
+ def verify_secret_name(self, name: str) -> bool:
492
+ """Verify if a secret name is valid.
493
+
494
+ A valid secret name starts with a letter or underscore, followed by letters, digits, or underscores.
495
+ """
496
+ pattern = r"^[A-Za-z_][A-Za-z0-9_]*$"
497
+ return re.match(pattern, name) is not None
498
+
499
+ def new_folder(self, teamspace_id: str, name: str, cluster: Optional[V1ExternalCluster]) -> None:
500
+ create_request = Create(
501
+ name=name,
502
+ create_resources=True,
503
+ force=True,
504
+ writable=True,
505
+ )
506
+
507
+ if cluster is None:
508
+ create_request.r2 = V1R2DataConnection(name=name)
447
509
  else:
448
- pbar_update = lambda x: None
510
+ create_request.cluster_id = cluster.id
511
+ create_request.access_cluster_ids = [cluster.id]
449
512
 
450
- if target_path:
451
- os.makedirs(target_path, exist_ok=True)
513
+ if cluster.spec.aws_v1:
514
+ create_request.s3_folder = V1S3FolderDataConnection()
515
+ elif cluster.spec.google_cloud_v1:
516
+ create_request.gcs_folder = V1GCSFolderDataConnection()
452
517
 
453
- with tempfile.TemporaryFile() as f:
454
- for chunk in r.iter_content(chunk_size=4096 * 8):
455
- f.write(chunk)
456
- pbar_update(len(chunk))
518
+ self._client.data_connection_service_create_data_connection(create_request, teamspace_id)
519
+
520
+ def new_connection(
521
+ self, teamspace_id: str, name: str, source: str, cluster: V1ExternalCluster, writable: bool, region: str
522
+ ) -> None:
523
+ create_request = Create(
524
+ name=name,
525
+ create_resources=False,
526
+ force=True,
527
+ writable=writable,
528
+ cluster_id=cluster.id,
529
+ access_cluster_ids=[cluster.id],
530
+ )
531
+
532
+ # TODO: Add support for other connection types
533
+ create_request.efs = V1EfsConfig(file_system_id=source, region=region)
457
534
 
458
- with zipfile.ZipFile(f) as z:
459
- z.extractall(target_path)
535
+ self._client.data_connection_service_create_data_connection(create_request, teamspace_id)