lightning-sdk 2025.10.14__py3-none-any.whl → 2025.10.23__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 (76) hide show
  1. lightning_sdk/__init__.py +6 -3
  2. lightning_sdk/api/base_studio_api.py +13 -9
  3. lightning_sdk/api/job_api.py +4 -1
  4. lightning_sdk/api/license_api.py +26 -59
  5. lightning_sdk/api/studio_api.py +7 -2
  6. lightning_sdk/base_studio.py +30 -17
  7. lightning_sdk/cli/base_studio/list.py +1 -3
  8. lightning_sdk/cli/entrypoint.py +11 -34
  9. lightning_sdk/cli/groups.py +7 -0
  10. lightning_sdk/cli/license/__init__.py +14 -0
  11. lightning_sdk/cli/license/get.py +15 -0
  12. lightning_sdk/cli/license/list.py +45 -0
  13. lightning_sdk/cli/license/set.py +13 -0
  14. lightning_sdk/cli/studio/connect.py +42 -92
  15. lightning_sdk/cli/studio/create.py +23 -1
  16. lightning_sdk/cli/studio/start.py +12 -2
  17. lightning_sdk/cli/utils/get_base_studio.py +24 -0
  18. lightning_sdk/cli/utils/handle_machine_and_gpus_args.py +69 -0
  19. lightning_sdk/cli/utils/logging.py +121 -0
  20. lightning_sdk/cli/utils/ssh_connection.py +1 -1
  21. lightning_sdk/constants.py +1 -0
  22. lightning_sdk/helpers.py +53 -34
  23. lightning_sdk/job/base.py +7 -0
  24. lightning_sdk/job/job.py +8 -0
  25. lightning_sdk/job/v1.py +3 -0
  26. lightning_sdk/job/v2.py +4 -0
  27. lightning_sdk/lightning_cloud/login.py +260 -10
  28. lightning_sdk/lightning_cloud/openapi/__init__.py +16 -3
  29. lightning_sdk/lightning_cloud/openapi/api/auth_service_api.py +279 -0
  30. lightning_sdk/lightning_cloud/openapi/api/k8_s_cluster_service_api.py +117 -0
  31. lightning_sdk/lightning_cloud/openapi/api/product_license_service_api.py +108 -108
  32. lightning_sdk/lightning_cloud/openapi/models/__init__.py +16 -3
  33. lightning_sdk/lightning_cloud/openapi/models/create_machine_request_represents_the_request_to_create_a_machine.py +27 -1
  34. lightning_sdk/lightning_cloud/openapi/models/externalv1_cloud_space_instance_status.py +27 -1
  35. lightning_sdk/lightning_cloud/openapi/models/id_fork_body1.py +27 -1
  36. lightning_sdk/lightning_cloud/openapi/models/license_key_validate_body.py +123 -0
  37. lightning_sdk/lightning_cloud/openapi/models/update1.py +27 -1
  38. lightning_sdk/lightning_cloud/openapi/models/v1_create_license_request.py +175 -0
  39. lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
  40. lightning_sdk/lightning_cloud/openapi/models/v1_delete_license_response.py +97 -0
  41. lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster_spec.py +27 -1
  42. lightning_sdk/lightning_cloud/openapi/models/v1_external_search_user.py +27 -1
  43. lightning_sdk/lightning_cloud/openapi/models/v1_filesystem_metric.py +201 -0
  44. lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_transfer_estimate_response.py +29 -3
  45. lightning_sdk/lightning_cloud/openapi/models/v1_incident.py +27 -1
  46. lightning_sdk/lightning_cloud/openapi/models/v1_incident_detail.py +149 -0
  47. lightning_sdk/lightning_cloud/openapi/models/v1_incident_event.py +27 -1
  48. lightning_sdk/lightning_cloud/openapi/models/v1_license.py +227 -0
  49. lightning_sdk/lightning_cloud/openapi/models/v1_list_filesystem_metrics_response.py +123 -0
  50. lightning_sdk/lightning_cloud/openapi/models/{v1_list_product_licenses_response.py → v1_list_license_response.py} +16 -16
  51. lightning_sdk/lightning_cloud/openapi/models/v1_list_platform_notifications_response.py +123 -0
  52. lightning_sdk/lightning_cloud/openapi/models/v1_machine.py +27 -1
  53. lightning_sdk/lightning_cloud/openapi/models/v1_platform_notification.py +279 -0
  54. lightning_sdk/lightning_cloud/openapi/models/v1_reset_api_key_request.py +97 -0
  55. lightning_sdk/lightning_cloud/openapi/models/v1_reset_api_key_response.py +123 -0
  56. lightning_sdk/lightning_cloud/openapi/models/v1_slack_notifier.py +53 -1
  57. lightning_sdk/lightning_cloud/openapi/models/v1_token_login_request.py +123 -0
  58. lightning_sdk/lightning_cloud/openapi/models/v1_token_login_response.py +123 -0
  59. lightning_sdk/lightning_cloud/openapi/models/v1_token_owner_type.py +104 -0
  60. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +139 -191
  61. lightning_sdk/lightning_cloud/openapi/models/{v1_product_license_check_response.py → v1_validate_license_response.py} +21 -21
  62. lightning_sdk/lightning_cloud/rest_client.py +48 -45
  63. lightning_sdk/machine.py +5 -0
  64. lightning_sdk/pipeline/steps.py +1 -0
  65. lightning_sdk/studio.py +55 -13
  66. lightning_sdk/utils/config.py +18 -3
  67. lightning_sdk/utils/license.py +13 -0
  68. lightning_sdk/utils/resolve.py +6 -1
  69. {lightning_sdk-2025.10.14.dist-info → lightning_sdk-2025.10.23.dist-info}/METADATA +1 -1
  70. {lightning_sdk-2025.10.14.dist-info → lightning_sdk-2025.10.23.dist-info}/RECORD +74 -54
  71. lightning_sdk/lightning_cloud/openapi/models/v1_product_license.py +0 -435
  72. lightning_sdk/services/license.py +0 -363
  73. {lightning_sdk-2025.10.14.dist-info → lightning_sdk-2025.10.23.dist-info}/LICENSE +0 -0
  74. {lightning_sdk-2025.10.14.dist-info → lightning_sdk-2025.10.23.dist-info}/WHEEL +0 -0
  75. {lightning_sdk-2025.10.14.dist-info → lightning_sdk-2025.10.23.dist-info}/entry_points.txt +0 -0
  76. {lightning_sdk-2025.10.14.dist-info → lightning_sdk-2025.10.23.dist-info}/top_level.txt +0 -0
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
28
28
  from datetime import datetime
29
29
  from lightning_sdk.lightning_cloud.openapi.models import *
30
30
 
31
- class V1ProductLicenseCheckResponse(object):
31
+ class V1ValidateLicenseResponse(object):
32
32
  """NOTE: This class is auto generated by the swagger code generator program.
33
33
 
34
34
  Do not edit the class manually.
@@ -41,40 +41,40 @@ class V1ProductLicenseCheckResponse(object):
41
41
  and the value is json key in definition.
42
42
  """
43
43
  swagger_types = {
44
- 'valid': 'bool'
44
+ 'is_valid': 'bool'
45
45
  }
46
46
 
47
47
  attribute_map = {
48
- 'valid': 'valid'
48
+ 'is_valid': 'isValid'
49
49
  }
50
50
 
51
- def __init__(self, valid: 'bool' =None): # noqa: E501
52
- """V1ProductLicenseCheckResponse - a model defined in Swagger""" # noqa: E501
53
- self._valid = None
51
+ def __init__(self, is_valid: 'bool' =None): # noqa: E501
52
+ """V1ValidateLicenseResponse - a model defined in Swagger""" # noqa: E501
53
+ self._is_valid = None
54
54
  self.discriminator = None
55
- if valid is not None:
56
- self.valid = valid
55
+ if is_valid is not None:
56
+ self.is_valid = is_valid
57
57
 
58
58
  @property
59
- def valid(self) -> 'bool':
60
- """Gets the valid of this V1ProductLicenseCheckResponse. # noqa: E501
59
+ def is_valid(self) -> 'bool':
60
+ """Gets the is_valid of this V1ValidateLicenseResponse. # noqa: E501
61
61
 
62
62
 
63
- :return: The valid of this V1ProductLicenseCheckResponse. # noqa: E501
63
+ :return: The is_valid of this V1ValidateLicenseResponse. # noqa: E501
64
64
  :rtype: bool
65
65
  """
66
- return self._valid
66
+ return self._is_valid
67
67
 
68
- @valid.setter
69
- def valid(self, valid: 'bool'):
70
- """Sets the valid of this V1ProductLicenseCheckResponse.
68
+ @is_valid.setter
69
+ def is_valid(self, is_valid: 'bool'):
70
+ """Sets the is_valid of this V1ValidateLicenseResponse.
71
71
 
72
72
 
73
- :param valid: The valid of this V1ProductLicenseCheckResponse. # noqa: E501
73
+ :param is_valid: The is_valid of this V1ValidateLicenseResponse. # noqa: E501
74
74
  :type: bool
75
75
  """
76
76
 
77
- self._valid = valid
77
+ self._is_valid = is_valid
78
78
 
79
79
  def to_dict(self) -> dict:
80
80
  """Returns the model properties as a dict"""
@@ -97,7 +97,7 @@ class V1ProductLicenseCheckResponse(object):
97
97
  ))
98
98
  else:
99
99
  result[attr] = value
100
- if issubclass(V1ProductLicenseCheckResponse, dict):
100
+ if issubclass(V1ValidateLicenseResponse, dict):
101
101
  for key, value in self.items():
102
102
  result[key] = value
103
103
 
@@ -111,13 +111,13 @@ class V1ProductLicenseCheckResponse(object):
111
111
  """For `print` and `pprint`"""
112
112
  return self.to_str()
113
113
 
114
- def __eq__(self, other: 'V1ProductLicenseCheckResponse') -> bool:
114
+ def __eq__(self, other: 'V1ValidateLicenseResponse') -> bool:
115
115
  """Returns true if both objects are equal"""
116
- if not isinstance(other, V1ProductLicenseCheckResponse):
116
+ if not isinstance(other, V1ValidateLicenseResponse):
117
117
  return False
118
118
 
119
119
  return self.__dict__ == other.__dict__
120
120
 
121
- def __ne__(self, other: 'V1ProductLicenseCheckResponse') -> bool:
121
+ def __ne__(self, other: 'V1ValidateLicenseResponse') -> bool:
122
122
  """Returns true if both objects are not equal"""
123
123
  return not self == other
@@ -2,41 +2,43 @@ import functools
2
2
  import logging
3
3
  import time
4
4
  from functools import wraps
5
- from typing import Callable, Optional, Any
5
+ from typing import Any, Callable, Optional
6
6
 
7
7
  import urllib3
8
+
8
9
  from lightning_sdk.lightning_cloud import env
9
10
  from lightning_sdk.lightning_cloud.login import Auth
10
11
  from lightning_sdk.lightning_cloud.openapi import (
11
12
  ApiClient,
13
+ AssistantsServiceApi,
12
14
  AuthServiceApi,
15
+ BillingServiceApi,
16
+ CloudSpaceEnvironmentTemplateServiceApi,
13
17
  CloudSpaceServiceApi,
14
18
  ClusterServiceApi,
15
19
  Configuration,
16
20
  DataConnectionServiceApi,
21
+ DatasetServiceApi,
22
+ DeploymentTemplatesServiceApi,
23
+ EndpointServiceApi,
24
+ JobsServiceApi,
17
25
  LightningappInstanceServiceApi,
18
26
  LightningappV2ServiceApi,
19
27
  LightningworkServiceApi,
20
- ProjectsServiceApi,
21
- SecretServiceApi,
22
- SSHPublicKeyServiceApi,
23
- DatasetServiceApi,
24
- OrganizationsServiceApi,
25
- UserServiceApi,
26
- BillingServiceApi,
27
- EndpointServiceApi,
28
- SlurmJobsUserServiceApi,
29
28
  LitLoggerServiceApi,
30
- JobsServiceApi,
31
- AssistantsServiceApi,
32
- StorageServiceApi,
33
- DeploymentTemplatesServiceApi,
34
- ModelsStoreApi,
35
29
  LitRegistryServiceApi,
30
+ ModelsStoreApi,
31
+ OrganizationsServiceApi,
36
32
  PipelinesServiceApi,
37
- SchedulesServiceApi,
38
33
  ProductLicenseServiceApi,
39
- CloudSpaceEnvironmentTemplateServiceApi
34
+ ProjectsServiceApi,
35
+ SchedulesServiceApi,
36
+ SDKCommandHistoryServiceApi,
37
+ SecretServiceApi,
38
+ SlurmJobsUserServiceApi,
39
+ SSHPublicKeyServiceApi,
40
+ StorageServiceApi,
41
+ UserServiceApi,
40
42
  )
41
43
  from lightning_sdk.lightning_cloud.openapi.rest import ApiException
42
44
  from lightning_sdk.lightning_cloud.source_code.logs_socket_api import LightningLogsSocketAPI
@@ -76,34 +78,35 @@ def create_swagger_client(check_context: bool = True, with_auth: bool = True):
76
78
 
77
79
 
78
80
  class GridRestClient(
79
- LightningLogsSocketAPI,
80
- LightningappInstanceServiceApi,
81
- LightningappV2ServiceApi,
82
- AuthServiceApi,
83
- CloudSpaceServiceApi,
84
- ClusterServiceApi,
85
- ProjectsServiceApi,
86
- LightningworkServiceApi,
87
- SecretServiceApi,
88
- SSHPublicKeyServiceApi,
89
- DataConnectionServiceApi,
90
- DatasetServiceApi,
91
- OrganizationsServiceApi,
92
- UserServiceApi,
93
- BillingServiceApi,
94
- EndpointServiceApi,
95
- SlurmJobsUserServiceApi,
96
- LitLoggerServiceApi,
97
- JobsServiceApi,
98
- AssistantsServiceApi,
99
- StorageServiceApi,
100
- DeploymentTemplatesServiceApi,
101
- ModelsStoreApi,
102
- LitRegistryServiceApi,
103
- PipelinesServiceApi,
104
- SchedulesServiceApi,
105
- ProductLicenseServiceApi,
106
- CloudSpaceEnvironmentTemplateServiceApi
81
+ LightningLogsSocketAPI,
82
+ LightningappInstanceServiceApi,
83
+ LightningappV2ServiceApi,
84
+ AuthServiceApi,
85
+ CloudSpaceServiceApi,
86
+ ClusterServiceApi,
87
+ ProjectsServiceApi,
88
+ LightningworkServiceApi,
89
+ SecretServiceApi,
90
+ SSHPublicKeyServiceApi,
91
+ DataConnectionServiceApi,
92
+ DatasetServiceApi,
93
+ OrganizationsServiceApi,
94
+ UserServiceApi,
95
+ BillingServiceApi,
96
+ EndpointServiceApi,
97
+ SlurmJobsUserServiceApi,
98
+ LitLoggerServiceApi,
99
+ JobsServiceApi,
100
+ AssistantsServiceApi,
101
+ StorageServiceApi,
102
+ DeploymentTemplatesServiceApi,
103
+ ModelsStoreApi,
104
+ LitRegistryServiceApi,
105
+ PipelinesServiceApi,
106
+ SchedulesServiceApi,
107
+ ProductLicenseServiceApi,
108
+ CloudSpaceEnvironmentTemplateServiceApi,
109
+ SDKCommandHistoryServiceApi,
107
110
  ):
108
111
 
109
112
  def __init__(self, api_client: Optional[ApiClient] = None, with_auth: bool = True):
lightning_sdk/machine.py CHANGED
@@ -34,6 +34,7 @@ class Machine:
34
34
 
35
35
  # supported GPU types
36
36
  # supported T4 variations
37
+ T4_SMALL: ClassVar["Machine"]
37
38
  T4: ClassVar["Machine"]
38
39
  T4_X_2: ClassVar["Machine"]
39
40
  T4_X_4: ClassVar["Machine"]
@@ -162,6 +163,7 @@ Machine.DATA_PREP_ULTRA = Machine(
162
163
 
163
164
  # GPU machines
164
165
  # available T4 machines
166
+ Machine.T4_SMALL = Machine(name="T4_SMALL", slug="lit-t4-1-small", family="T4", accelerator_count=1)
165
167
  Machine.T4 = Machine(name="T4", slug="lit-t4-1", family="T4", accelerator_count=1)
166
168
  Machine.T4_X_2 = Machine(name="T4_X_2", slug="lit-t4-2", family="T4", accelerator_count=2)
167
169
  Machine.T4_X_4 = Machine(name="T4_X_4", slug="lit-t4-4", family="T4", accelerator_count=4)
@@ -216,3 +218,6 @@ Machine.H200 = Machine(name="H200", slug="lit-h200x-1", family="H200", accelerat
216
218
  Machine.H200_X_8 = Machine(name="H200_X_8", slug="lit-h200x-8", family="H200", accelerator_count=8)
217
219
  # available B200 machines
218
220
  Machine.B200_X_8 = Machine(name="B200_X_8", slug="lit-b200x-8", family="B200", accelerator_count=8)
221
+
222
+
223
+ DEFAULT_MACHINE = Machine.CPU.name
@@ -239,6 +239,7 @@ class JobStep:
239
239
  artifacts_remote=None,
240
240
  max_runtime=self.max_runtime,
241
241
  machine_image_version=machine_image_version,
242
+ reuse_snapshot=True,
242
243
  )
243
244
 
244
245
  return V1PipelineStep(
lightning_sdk/studio.py CHANGED
@@ -11,7 +11,7 @@ from lightning_sdk.api.studio_api import StudioApi
11
11
  from lightning_sdk.base_studio import BaseStudio
12
12
  from lightning_sdk.constants import _LIGHTNING_DEBUG
13
13
  from lightning_sdk.lightning_cloud.openapi import V1ClusterType
14
- from lightning_sdk.machine import CloudProvider, Machine
14
+ from lightning_sdk.machine import DEFAULT_MACHINE, CloudProvider, Machine
15
15
  from lightning_sdk.organization import Organization
16
16
  from lightning_sdk.owner import Owner
17
17
  from lightning_sdk.status import Status
@@ -103,7 +103,16 @@ class Studio:
103
103
  self._plugins = {}
104
104
  self._studio = None
105
105
 
106
- cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
106
+ # Check to see if we're inside a studio
107
+ current_studio = None
108
+ studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
109
+ if studio_id is not None:
110
+ # We're inside a studio, get it by ID
111
+ current_studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
112
+
113
+ cloud_account = _resolve_deprecated_cluster(
114
+ cloud_account, cluster, current_studio.cluster_id if current_studio else None
115
+ )
107
116
  cloud_provider = _resolve_deprecated_provider(cloud_provider, provider)
108
117
 
109
118
  cls_name = self._cls_name
@@ -119,7 +128,7 @@ class Studio:
119
128
 
120
129
  self._studio_type = None
121
130
  if studio_type:
122
- self._base_studio = BaseStudio()
131
+ self._base_studio = BaseStudio(teamspace=self._teamspace)
123
132
  self._available_base_studios = self._base_studio.list()
124
133
  for bst in self._available_base_studios:
125
134
  if (
@@ -135,14 +144,14 @@ class Studio:
135
144
  f"Available studio types: "
136
145
  f"{[bst.name.lower().replace(' ', '-') for bst in self._available_base_studios]}"
137
146
  )
147
+ else:
148
+ if current_studio:
149
+ self._studio_type = current_studio.environment_template_id
138
150
 
139
151
  # Resolve studio name if not provided: explicit → env (LIGHTNING_CLOUD_SPACE_ID) → config defaults
140
152
  if name is None and not getattr(self._skip_init, "value", False):
141
- studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
142
- if studio_id is not None:
143
- # We're inside a studio, get it by ID
144
- self._studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
145
- name = self._studio.name
153
+ if current_studio:
154
+ name = current_studio.name
146
155
  else:
147
156
  # Try config defaults
148
157
  from lightning_sdk.utils.config import Config, DefaultConfigKeys
@@ -272,7 +281,7 @@ class Studio:
272
281
 
273
282
  def start(
274
283
  self,
275
- machine: Union[Machine, str] = Machine.CPU,
284
+ machine: Optional[Union[Machine, str]] = None,
276
285
  interruptible: Optional[bool] = None,
277
286
  max_runtime: Optional[int] = None,
278
287
  ) -> None:
@@ -287,6 +296,22 @@ class Studio:
287
296
  Defaults to 3h
288
297
 
289
298
  """
299
+ # Check to see if we're inside a studio and if its running
300
+ current_studio_machine = None
301
+ studio_id = os.environ.get("LIGHTNING_CLOUD_SPACE_ID", None)
302
+ if studio_id is not None:
303
+ # We're inside a studio, get the machine if it is running
304
+ current_studio = self._studio_api.get_studio_by_id(studio_id=studio_id, teamspace_id=self._teamspace.id)
305
+ current_status = self._studio_api._get_studio_instance_status_from_object(current_studio)
306
+
307
+ if current_status and _internal_status_to_external_status(current_status) == Status.Running:
308
+ current_studio_machine = self._studio_api.get_machine(
309
+ current_studio.id,
310
+ self._teamspace.id,
311
+ current_studio.cluster_id,
312
+ _get_org_id(self._teamspace),
313
+ )
314
+
290
315
  status = self.status
291
316
 
292
317
  if interruptible is None:
@@ -296,9 +321,14 @@ class Studio:
296
321
  else:
297
322
  interruptible = self.teamspace.start_studios_on_interruptible
298
323
 
299
- new_machine = machine
300
- if not isinstance(machine, Machine):
301
- new_machine = Machine.from_str(machine)
324
+ new_machine = DEFAULT_MACHINE
325
+ if machine is not None:
326
+ new_machine = machine
327
+ elif current_studio_machine is not None:
328
+ new_machine = current_studio_machine
329
+
330
+ if not isinstance(new_machine, Machine):
331
+ new_machine = Machine.from_str(new_machine)
302
332
 
303
333
  if status == Status.Running:
304
334
  if new_machine != self.machine:
@@ -353,7 +383,10 @@ class Studio:
353
383
  self._studio_api.delete_studio(self._studio.id, self._teamspace.id)
354
384
 
355
385
  def duplicate(
356
- self, target_teamspace: Optional[Union["Teamspace", str]] = None, machine: Machine = Machine.CPU
386
+ self,
387
+ target_teamspace: Optional[Union["Teamspace", str]] = None,
388
+ machine: Machine = Machine.CPU,
389
+ name: Optional[str] = None,
357
390
  ) -> "Studio":
358
391
  """Duplicates the existing Studio.
359
392
 
@@ -386,6 +419,7 @@ class Studio:
386
419
  teamspace_id=self._teamspace.id,
387
420
  target_teamspace_id=target_teamspace_id,
388
421
  machine=machine,
422
+ new_name=name,
389
423
  )
390
424
  return Studio(**kwargs)
391
425
 
@@ -659,6 +693,14 @@ class Studio:
659
693
  self._assistant_id = assistant.id
660
694
  _logger.info(assistant_info)
661
695
 
696
+ def rename(self, new_name: str) -> None:
697
+ """Renames the current Studio to the provided new name."""
698
+ if new_name == self._studio.name:
699
+ return
700
+
701
+ self._studio_api._update_cloudspace(self._studio, self._teamspace.id, "display_name", new_name)
702
+ self._update_studio_reference()
703
+
662
704
  @property
663
705
  def auto_sleep(self) -> bool:
664
706
  """Returns if a Studio has auto-sleep enabled."""
@@ -1,7 +1,6 @@
1
1
  import os
2
- from collections.abc import Mapping
3
2
  from dataclasses import dataclass
4
- from typing import Any, Dict, Optional, Sequence
3
+ from typing import Any, Dict, Mapping, Optional, Sequence
5
4
 
6
5
  import yaml
7
6
 
@@ -26,6 +25,8 @@ class DefaultConfigKeys:
26
25
  cloud_account: str = "cloud_account.name"
27
26
  cloud_provider: str = "cloud_provider.name"
28
27
 
28
+ license: str = "license"
29
+
29
30
 
30
31
  class ConfigProxy:
31
32
  def __init__(self, root: "Config", *path: str) -> None:
@@ -101,6 +102,20 @@ class Config:
101
102
  Returns:
102
103
  The config value if found, None otherwise
103
104
  """
105
+ return self._get_value_type(key_path, str)
106
+
107
+ def get_sub_config(self, key_path: str) -> Optional[Mapping[str, Any]]:
108
+ """Gets a subconfig from the config using dot notation.
109
+
110
+ Args:
111
+ key_path: the dot-separated path to the subconfig (e.g. "license")
112
+
113
+ Returns:
114
+ The subconfig if found, None otherwise
115
+ """
116
+ return self._get_value_type(key_path, Mapping)
117
+
118
+ def _get_value_type(self, key_path: str, subtype: type) -> Optional[Any]:
104
119
  config = self._load_config()
105
120
  if not isinstance(config, Mapping):
106
121
  return None
@@ -111,7 +126,7 @@ class Config:
111
126
  if not isinstance(curr, dict) or k not in curr:
112
127
  return None
113
128
  curr = curr[k]
114
- return curr if isinstance(curr, str) else None
129
+ return curr if isinstance(curr, subtype) else None
115
130
 
116
131
  def __getattr__(self, name: str) -> ConfigProxy:
117
132
  """Returns a proxy to the actual values to allow for nested access.
@@ -0,0 +1,13 @@
1
+ from functools import lru_cache
2
+
3
+ from lightning_sdk.api.license_api import LicenseApi
4
+
5
+
6
+ class License:
7
+ def __init__(self, license_key: str, product_name: str) -> None:
8
+ self.license_key = license_key
9
+ self.product_name = product_name
10
+
11
+ @lru_cache(maxsize=1) # noqa: B019
12
+ def validate(self) -> bool:
13
+ return LicenseApi(self.license_key).validate_license(self.license_key, self.product_name)
@@ -75,7 +75,9 @@ def _resolve_deprecated_provider(
75
75
  return cloud_provider
76
76
 
77
77
 
78
- def _resolve_deprecated_cluster(cloud_account: Optional[str], cluster: Optional[str]) -> Optional[str]:
78
+ def _resolve_deprecated_cluster(
79
+ cloud_account: Optional[str], cluster: Optional[str], current_cloud_account: Optional[str] = None
80
+ ) -> Optional[str]:
79
81
  if cluster is not None:
80
82
  if cloud_account is not None:
81
83
  raise ValueError(
@@ -96,6 +98,9 @@ def _resolve_deprecated_cluster(cloud_account: Optional[str], cluster: Optional[
96
98
  config = Config()
97
99
  cloud_account = config.get_value(DefaultConfigKeys.cloud_account)
98
100
 
101
+ if cloud_account is None:
102
+ cloud_account = current_cloud_account
103
+
99
104
  return cloud_account
100
105
 
101
106
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightning_sdk
3
- Version: 2025.10.14
3
+ Version: 2025.10.23
4
4
  Summary: SDK to develop using Lightning AI Studios
5
5
  Author-email: Lightning-AI <justus@lightning.ai>
6
6
  License: MIT License