lightning-sdk 2025.7.10__py3-none-any.whl → 2025.7.22__py3-none-any.whl

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