lightning-sdk 2025.9.11__py3-none-any.whl → 2025.9.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.
lightning_sdk/__init__.py CHANGED
@@ -10,14 +10,16 @@ from lightning_sdk.organization import Organization
10
10
  from lightning_sdk.plugin import JobsPlugin, MultiMachineTrainingPlugin, Plugin, SlurmJobsPlugin
11
11
  from lightning_sdk.status import Status
12
12
  from lightning_sdk.studio import Studio
13
- from lightning_sdk.teamspace import Teamspace
13
+ from lightning_sdk.teamspace import ConnectionType, FolderLocation, Teamspace
14
14
  from lightning_sdk.user import User
15
15
 
16
16
  __all__ = [
17
17
  "AIHub",
18
18
  "Agent",
19
19
  "CloudProvider",
20
+ "ConnectionType",
20
21
  "Deployment",
22
+ "FolderLocation",
21
23
  "Job",
22
24
  "JobsPlugin",
23
25
  "Machine",
@@ -32,6 +34,6 @@ __all__ = [
32
34
  "User",
33
35
  ]
34
36
 
35
- __version__ = "2025.09.11"
37
+ __version__ = "2025.09.23"
36
38
  _check_version_and_prompt_upgrade(__version__)
37
39
  _set_tqdm_envvars_noninteractive()
@@ -1,5 +1,6 @@
1
1
  from lightning_sdk.api.agents_api import AgentApi
2
2
  from lightning_sdk.api.ai_hub_api import AIHubApi
3
+ from lightning_sdk.api.cloud_account_api import CloudAccountApi
3
4
  from lightning_sdk.api.org_api import OrgApi
4
5
  from lightning_sdk.api.studio_api import StudioApi
5
6
  from lightning_sdk.api.teamspace_api import TeamspaceApi
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "UserApi",
13
14
  "AgentApi",
14
15
  "AIHubApi",
16
+ "CloudAccountApi",
15
17
  ]
@@ -13,6 +13,7 @@ from lightning_sdk.lightning_cloud.rest_client import LightningClient
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from lightning_sdk.machine import CloudProvider
16
+ from lightning_sdk.teamspace import ConnectionType
16
17
 
17
18
 
18
19
  class CloudAccountApi:
@@ -131,13 +132,20 @@ class CloudAccountApi:
131
132
  )
132
133
  return list(filtered_cloud_accounts)
133
134
 
134
- def get_cloud_account_provider_mapping(self, teamspace_id: str) -> Dict["CloudProvider", str]:
135
+ def get_cloud_account_provider_mapping(self, teamspace_id: str) -> Dict["CloudProvider", V1ExternalCluster]:
135
136
  """Gets the cloud account <-> provider mapping."""
136
- res = self.list_global_cloud_accounts(teamspace_id=teamspace_id)
137
- return {self._get_cloud_account_provider(cloud_account): cloud_account.id for cloud_account in res}
137
+ res = self.list_cloud_accounts(teamspace_id=teamspace_id)
138
+ cloud_accounts = {cloud_account.id: cloud_account for cloud_account in res}
139
+ providers = {cloud_account.id: self._get_cloud_account_provider(cloud_account) for cloud_account in res}
140
+
141
+ mapping = {}
142
+ for cloud_account_id, provider in providers.items():
143
+ if provider is not None:
144
+ mapping[provider] = cloud_accounts[cloud_account_id]
145
+ return mapping
138
146
 
139
147
  @staticmethod
140
- def _get_cloud_account_provider(cloud_account: Optional[V1ExternalCluster]) -> "CloudProvider":
148
+ def _get_cloud_account_provider(cloud_account: Optional[V1ExternalCluster]) -> Optional["CloudProvider"]:
141
149
  """Determines the cloud provider based on the cloud_account configuration.
142
150
 
143
151
  Args:
@@ -172,7 +180,7 @@ class CloudAccountApi:
172
180
  if cloud_account.spec.nebius_v1:
173
181
  return CloudProvider.NEBIUS
174
182
 
175
- return CloudProvider.AWS
183
+ return None
176
184
 
177
185
  def resolve_cloud_account(
178
186
  self,
@@ -201,9 +209,19 @@ class CloudAccountApi:
201
209
  if cloud_provider:
202
210
  cloud_account_mapping = self.get_cloud_account_provider_mapping(teamspace_id=teamspace_id)
203
211
  if cloud_provider and cloud_provider in cloud_account_mapping:
204
- return cloud_account_mapping[cloud_provider]
212
+ return cloud_account_mapping[cloud_provider].id
205
213
 
206
214
  if default_cloud_account:
207
215
  return default_cloud_account
208
216
 
209
217
  return None
218
+
219
+ @staticmethod
220
+ def get_cloud_provider_for_connection_type(connection_type: "ConnectionType") -> "CloudProvider":
221
+ from lightning_sdk.machine import CloudProvider
222
+ from lightning_sdk.teamspace import ConnectionType
223
+
224
+ if connection_type == ConnectionType.EFS:
225
+ return CloudProvider.AWS
226
+
227
+ raise ValueError(f"ConnectionType {ConnectionType} currently not supported!")
@@ -104,22 +104,23 @@ class JobApiV1:
104
104
  org_id=org_id,
105
105
  )
106
106
 
107
- identifier = None
107
+ identifiers = []
108
108
 
109
109
  if user_requested_compute_config and user_requested_compute_config.name:
110
- identifier = user_requested_compute_config.name
110
+ identifiers.append(user_requested_compute_config.name)
111
111
  else:
112
- identifier = spec.compute_config.instance_type
112
+ identifiers.append(spec.compute_config.instance_type)
113
113
 
114
114
  for accelerator in accelerators:
115
- if identifier in (
116
- accelerator.slug,
117
- accelerator.slug_multi_cloud,
118
- accelerator.instance_id,
119
- ):
120
- return Machine.from_str(accelerator.slug_multi_cloud)
115
+ for ident in identifiers:
116
+ if ident in (
117
+ accelerator.slug,
118
+ accelerator.slug_multi_cloud,
119
+ accelerator.instance_id,
120
+ ):
121
+ return Machine._from_accelerator(accelerator)
121
122
 
122
- return Machine.from_str(identifier)
123
+ return Machine.from_str(identifiers[0])
123
124
 
124
125
  def _get_machines_for_cloud_account(
125
126
  self, teamspace_id: str, cloud_account_id: str, org_id: str
@@ -431,7 +432,7 @@ class JobApiV2:
431
432
  if (spec.instance_name and spec.instance_name in possible_identifiers) or (
432
433
  spec.instance_type and spec.instance_type in possible_identifiers
433
434
  ):
434
- return Machine.from_str(accelerator.slug_multi_cloud)
435
+ return Machine._from_accelerator(accelerator)
435
436
 
436
437
  return Machine.from_str(spec.instance_name or spec.instance_type)
437
438
 
@@ -265,7 +265,7 @@ class MMTApiV2:
265
265
  if (spec.instance_name and spec.instance_name in possible_identifiers) or (
266
266
  spec.instance_type and spec.instance_type in possible_identifiers
267
267
  ):
268
- return Machine.from_str(accelerator.slug_multi_cloud)
268
+ return Machine._from_accelerator(accelerator)
269
269
 
270
270
  return Machine.from_str(spec.instance_name or spec.instance_type)
271
271
 
@@ -426,7 +426,7 @@ class StudioApi:
426
426
  accelerator.slug_multi_cloud,
427
427
  accelerator.instance_id,
428
428
  ):
429
- return Machine.from_str(accelerator.slug_multi_cloud)
429
+ return Machine._from_accelerator(accelerator)
430
430
 
431
431
  return Machine.from_str(response.compute_config.name)
432
432
 
@@ -17,6 +17,7 @@ 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,
@@ -27,7 +28,10 @@ from lightning_sdk.lightning_cloud.openapi import (
27
28
  V1Assistant,
28
29
  V1CloudSpace,
29
30
  V1ClusterAccelerator,
31
+ V1EfsConfig,
30
32
  V1Endpoint,
33
+ V1ExternalCluster,
34
+ V1GCSFolderDataConnection,
31
35
  V1Job,
32
36
  V1LoginRequest,
33
37
  V1Model,
@@ -36,6 +40,8 @@ from lightning_sdk.lightning_cloud.openapi import (
36
40
  V1Project,
37
41
  V1ProjectClusterBinding,
38
42
  V1PromptSuggestion,
43
+ V1R2DataConnection,
44
+ V1S3FolderDataConnection,
39
45
  V1Secret,
40
46
  V1SecretType,
41
47
  V1UpstreamOpenAI,
@@ -489,3 +495,41 @@ class TeamspaceApi:
489
495
  """
490
496
  pattern = r"^[A-Za-z_][A-Za-z0-9_]*$"
491
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)
509
+ else:
510
+ create_request.cluster_id = cluster.id
511
+ create_request.access_cluster_ids = [cluster.id]
512
+
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()
517
+
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)
534
+
535
+ self._client.data_connection_service_create_data_connection(create_request, teamspace_id)
@@ -1,5 +1,5 @@
1
1
  import re
2
- from typing import Dict, List, Union
2
+ from typing import Dict, List, Optional, Union
3
3
 
4
4
  from lightning_sdk.lightning_cloud.login import Auth
5
5
  from lightning_sdk.lightning_cloud.openapi import (
@@ -61,8 +61,14 @@ class UserApi:
61
61
  def _get_all_teamspace_memberships(
62
62
  self,
63
63
  user_id: str, # todo: this is unused, but still required
64
+ org_id: Optional[str] = None,
64
65
  ) -> List[V1Membership]:
65
- return self._client.projects_service_list_memberships(filter_by_user_id=True).memberships
66
+ kwargs: Dict[str, Union[bool, str]] = {"filter_by_user_id": True}
67
+
68
+ if org_id is not None:
69
+ kwargs["organization_id"] = org_id
70
+
71
+ return self._client.projects_service_list_memberships(**kwargs).memberships
66
72
 
67
73
  def _get_authed_user_name(self) -> str:
68
74
  """Gets the currently logged-in user."""
@@ -0,0 +1,110 @@
1
+ import os
2
+ from contextlib import suppress
3
+ from typing import Dict, List, Optional, TypedDict
4
+
5
+ import click
6
+ from simple_term_menu import TerminalMenu
7
+
8
+ from lightning_sdk.cli.legacy.exceptions import StudioCliError
9
+ from lightning_sdk.organization import Organization
10
+ from lightning_sdk.owner import Owner
11
+ from lightning_sdk.user import User
12
+ from lightning_sdk.utils.resolve import ApiException, _get_authed_user, _resolve_org, _resolve_user
13
+
14
+
15
+ class _OwnerMenuType(TypedDict):
16
+ name: str
17
+ is_org: bool
18
+
19
+
20
+ class OwnerMenu:
21
+ """This class is used to select a teamspace owner (org/user) from a list of possible owners.
22
+
23
+ It can be used to select an owner from a list of possible owners, or to resolve an owner from a name.
24
+ """
25
+
26
+ def _get_owner_from_interactive_menu(self, possible_owners: Dict[str, _OwnerMenuType]) -> _OwnerMenuType:
27
+ owner_ids = sorted(possible_owners.keys())
28
+ terminal_menu = self._prepare_terminal_menu_owners([possible_owners[k] for k in owner_ids])
29
+ terminal_menu.show()
30
+
31
+ selected_id = owner_ids[terminal_menu.chosen_menu_index]
32
+ return possible_owners[selected_id]
33
+
34
+ def _get_owner_from_name(self, owner: str, possible_owners: Dict[str, _OwnerMenuType]) -> _OwnerMenuType:
35
+ for _, ts in possible_owners.items():
36
+ if ts["name"]:
37
+ return ts
38
+
39
+ click.echo(f"Could not find Owner {owner}, please select it from the list:")
40
+ return self._get_owner_from_interactive_menu(possible_owners)
41
+
42
+ @staticmethod
43
+ def _prepare_terminal_menu_owners(
44
+ possible_owners: List[_OwnerMenuType], title: Optional[str] = None
45
+ ) -> TerminalMenu:
46
+ if title is None:
47
+ title = "Please select a Teamspace-Owner out of the following:"
48
+
49
+ return TerminalMenu(
50
+ [f"{to['name']} ({'Organization' if to['is_org'] else 'User'})" for to in possible_owners],
51
+ title=title,
52
+ clear_menu_on_exit=True,
53
+ )
54
+
55
+ @staticmethod
56
+ def _get_possible_owners(user: User) -> Dict[str, _OwnerMenuType]:
57
+ user_api = user._user_api
58
+
59
+ orgs = user_api._get_organizations_for_authed_user()
60
+ owners: Dict[str, _OwnerMenuType] = {user.id: {"name": user.name, "is_org": False}}
61
+
62
+ for org in orgs:
63
+ owners[org.id] = {"name": org.name, "is_org": True}
64
+
65
+ return owners
66
+
67
+ def __call__(self, owner: Optional[str] = None) -> Owner:
68
+ try:
69
+ # try to resolve the teamspace from the name, environment or config
70
+ resolved_owner = None
71
+ with suppress(ApiException, ValueError, RuntimeError):
72
+ resolved_owner = _resolve_org(owner)
73
+
74
+ if resolved_owner is not None:
75
+ return resolved_owner
76
+
77
+ with suppress(ApiException, ValueError, RuntimeError):
78
+ resolved_owner = _resolve_user(owner)
79
+
80
+ if resolved_owner is not None:
81
+ return resolved_owner
82
+
83
+ if os.environ.get("LIGHTNING_NON_INTERACTIVE", "0") == "1":
84
+ raise ValueError(
85
+ "Owner selection is not supported in non-interactive mode. Please provide an owner name."
86
+ )
87
+
88
+ # if the owner is not resolved, try to get the owner from the interactive menu
89
+ # this could mean that either no owner was provided or the provided owner is not valid
90
+ user = _get_authed_user()
91
+
92
+ possible_owners = self._get_possible_owners(user)
93
+ if owner is None:
94
+ owner_dict = self._get_owner_from_interactive_menu(possible_owners=possible_owners)
95
+
96
+ else:
97
+ owner_dict = self._get_owner_from_name(owner=owner, possible_owners=possible_owners)
98
+
99
+ if owner_dict.get("is_org", False):
100
+ return Organization(owner_dict.get("name", None))
101
+
102
+ return User(owner_dict.get("name", None))
103
+ except KeyboardInterrupt:
104
+ raise KeyboardInterrupt from None
105
+
106
+ except Exception as e:
107
+ raise StudioCliError(
108
+ f"Could not find the given Teamspace-Owner {owner}. "
109
+ "Please contact Lightning AI directly to resolve this issue."
110
+ ) from e
@@ -1,15 +1,15 @@
1
1
  import os
2
+ from contextlib import suppress
2
3
  from typing import Dict, List, Optional
3
4
 
4
5
  import click
5
6
  from simple_term_menu import TerminalMenu
6
7
 
7
- from lightning_sdk.api import OrgApi
8
8
  from lightning_sdk.cli.legacy.exceptions import StudioCliError
9
9
  from lightning_sdk.cli.utils.resolve import resolve_teamspace_owner_name_format
10
- from lightning_sdk.teamspace import Teamspace
11
- from lightning_sdk.user import User
12
- from lightning_sdk.utils.resolve import _get_authed_user
10
+ from lightning_sdk.teamspace import Organization, Teamspace
11
+ from lightning_sdk.user import Owner, User
12
+ from lightning_sdk.utils.resolve import ApiException, _get_authed_user, _resolve_teamspace
13
13
 
14
14
 
15
15
  class TeamspacesMenu:
@@ -18,51 +18,45 @@ class TeamspacesMenu:
18
18
  It can be used to select a teamspace from a list of possible teamspaces, or to resolve a teamspace from a name.
19
19
  """
20
20
 
21
- def _get_teamspace_from_interactive_menu(self, possible_teamspaces: Dict[str, Dict[str, str]]) -> Dict[str, str]:
21
+ def __init__(self, owner: Optional[Owner] = None) -> None:
22
+ self._owner: Owner = owner
23
+
24
+ def _get_teamspace_from_interactive_menu(self, possible_teamspaces: Dict[str, str]) -> str:
22
25
  teamspace_ids = sorted(possible_teamspaces.keys())
23
- terminal_menu = self._prepare_terminal_menu_teamspaces([possible_teamspaces[k] for k in teamspace_ids])
26
+ terminal_menu = self._prepare_terminal_menu_teamspaces(
27
+ [possible_teamspaces[k] for k in teamspace_ids], self._owner
28
+ )
24
29
  terminal_menu.show()
25
30
 
26
31
  selected_id = teamspace_ids[terminal_menu.chosen_menu_index]
27
32
  return possible_teamspaces[selected_id]
28
33
 
29
- def _get_teamspace_from_name(
30
- self, teamspace: str, possible_teamspaces: Dict[str, Dict[str, str]]
31
- ) -> Dict[str, str]:
32
- try:
33
- owner, name = teamspace.split("/", maxsplit=1)
34
- except ValueError as e:
35
- raise ValueError(
36
- f"Invalid teamspace format: '{teamspace}'. "
37
- "Teamspace should be specified as '{teamspace_owner}/{teamspace_name}' "
38
- "(e.g., 'my-org/my-teamspace')."
39
- ) from e
40
-
34
+ def _get_teamspace_from_name(self, teamspace: str, possible_teamspaces: Dict[str, str]) -> str:
41
35
  for _, ts in possible_teamspaces.items():
42
- if ts["name"] == name and (ts["user"] == owner or ts["org"] == owner):
36
+ if ts == teamspace:
43
37
  return ts
44
38
 
45
- click.echo(f"Could not find Teamspace {teamspace}, please select it from the list:")
39
+ click.echo(f"Could not find Teamspace {self._owner.name}/{teamspace}, please select it from the list:")
46
40
  return self._get_teamspace_from_interactive_menu(possible_teamspaces)
47
41
 
48
42
  @staticmethod
49
43
  def _prepare_terminal_menu_teamspaces(
50
- possible_teamspaces: List[Dict[str, str]], title: Optional[str] = None
44
+ possible_teamspaces: List[str],
45
+ owner: Owner,
46
+ title: Optional[str] = None,
51
47
  ) -> TerminalMenu:
52
48
  if title is None:
53
- title = "Please select a Teamspace out of the following:"
49
+ title = f"Please select a Teamspace (owned by {owner.name}) out of the following:"
54
50
 
55
- return TerminalMenu(
56
- [f"{t['user'] or t['org']}/{t['name']}" for t in possible_teamspaces], title=title, clear_menu_on_exit=True
57
- )
51
+ return TerminalMenu(possible_teamspaces, title=title, clear_menu_on_exit=True)
58
52
 
59
53
  @staticmethod
60
- def _get_possible_teamspaces(user: User) -> Dict[str, Dict[str, str]]:
61
- org_api = OrgApi()
54
+ def _get_possible_teamspaces(user: User, owner: Owner) -> Dict[str, str]:
62
55
  user_api = user._user_api
63
56
 
64
- user_api._get_organizations_for_authed_user()
65
- memberships = user_api._get_all_teamspace_memberships(user_id=user.id)
57
+ memberships = user_api._get_all_teamspace_memberships(
58
+ user_id=user.id, org_id=owner.id if isinstance(owner, Organization) else None
59
+ )
66
60
 
67
61
  teamspaces = {}
68
62
  # get all teamspace memberships
@@ -70,56 +64,63 @@ class TeamspacesMenu:
70
64
  teamspace_id = membership.project_id
71
65
  teamspace_name = membership.name
72
66
 
73
- # get organization if necessary
74
- if membership.owner_type == "organization":
75
- org_name = org_api._get_org_by_id(membership.owner_id).name
76
- user_name = None
77
- else:
78
- org_name = None
79
-
80
- # don't do a request if not necessary
81
- if membership.owner_id == user.id:
82
- user_name = user.name
83
- else:
84
- user_name = user_api._get_user_by_id(membership.owner_id).username
85
-
86
- teamspaces[teamspace_id] = {"user": user_name, "org": org_name, "name": teamspace_name}
67
+ if membership.owner_id == owner.id:
68
+ teamspaces[teamspace_id] = teamspace_name
87
69
 
88
70
  return teamspaces
89
71
 
90
72
  def __call__(self, teamspace: Optional[str] = None) -> Teamspace:
91
- try:
92
- # try to resolve the teamspace from the name, environment or config
93
- resolved_teamspace = resolve_teamspace_owner_name_format(teamspace)
73
+ resolved_teamspace = None
74
+ resolved_teamspace = resolve_teamspace_owner_name_format(teamspace)
75
+ if resolved_teamspace is not None:
76
+ return resolved_teamspace
94
77
 
95
- if resolved_teamspace is not None:
96
- return resolved_teamspace
78
+ if self._owner is None:
79
+ # resolve owner if required
80
+ from lightning_sdk.cli.utils.owner_selection import OwnerMenu
97
81
 
98
- if os.environ.get("LIGHTNING_NON_INTERACTIVE", "0") == "1":
99
- raise ValueError(
100
- "Teamspace selection is not supported in non-interactive mode. "
101
- "Please provide a teamspace name in the format of 'owner/teamspace'."
102
- )
82
+ menu = OwnerMenu()
83
+ self._owner = menu()
84
+
85
+ if isinstance(self._owner, Organization):
86
+ org = self._owner
87
+ user = None
88
+ elif isinstance(self._owner, User):
89
+ org = None
90
+ user = self._owner
91
+
92
+ with suppress(ApiException, ValueError, RuntimeError):
93
+ resolved_teamspace = _resolve_teamspace(teamspace=teamspace, org=org, user=user)
103
94
 
104
- # if the teamspace is not resolved, try to get the teamspace from the interactive menu
105
- # this could mean that either no teamspace was provided or the provided teamspace is not valid
106
- user = _get_authed_user()
95
+ if resolved_teamspace is not None:
96
+ return resolved_teamspace
107
97
 
108
- possible_teamspaces = self._get_possible_teamspaces(user)
98
+ if os.environ.get("LIGHTNING_NON_INTERACTIVE", "0") == "1":
99
+ raise ValueError(
100
+ "Teamspace selection is not supported in non-interactive mode. "
101
+ "Please provide a teamspace name in the format of 'owner/teamspace'."
102
+ )
103
+
104
+ # if the teamspace is not resolved, try to get the teamspace from the interactive menu
105
+ # this could mean that either no teamspace was provided or the provided teamspace is not valid
106
+ try:
107
+ auth_user = _get_authed_user()
108
+
109
+ possible_teamspaces = self._get_possible_teamspaces(auth_user, self._owner)
109
110
  if teamspace is None:
110
- teamspace = resolve_teamspace_owner_name_format(teamspace)
111
- teamspace_dict = self._get_teamspace_from_interactive_menu(possible_teamspaces=possible_teamspaces)
111
+ teamspace_name = self._get_teamspace_from_interactive_menu(possible_teamspaces=possible_teamspaces)
112
112
  else:
113
- teamspace_dict = self._get_teamspace_from_name(
113
+ teamspace_name = self._get_teamspace_from_name(
114
114
  teamspace=teamspace, possible_teamspaces=possible_teamspaces
115
115
  )
116
116
 
117
- return Teamspace(**teamspace_dict)
117
+ return Teamspace(teamspace_name, org=org, user=user)
118
+
118
119
  except KeyboardInterrupt:
119
120
  raise KeyboardInterrupt from None
120
121
 
121
122
  except Exception as e:
122
123
  raise StudioCliError(
123
- f"Could not find the given Teamspace {teamspace}. "
124
+ f"Could not find the given Teamspace owned by {self._owner.name}. "
124
125
  "Please contact Lightning AI directly to resolve this issue."
125
126
  ) from e
lightning_sdk/llm/llm.py CHANGED
@@ -368,7 +368,7 @@ class LLM:
368
368
  metadata: Optional[Dict[str, str]] = None,
369
369
  stream: bool = False,
370
370
  full_response: bool = False,
371
- reasoning_effort: Optional[Literal["low", "medium", "highc"]] = None,
371
+ reasoning_effort: Optional[Literal["none", "low", "medium", "high"]] = None,
372
372
  **kwargs: Any,
373
373
  ) -> Union[str, AsyncGenerator[str, None]]:
374
374
  conversation_id = self._conversations.get(conversation) if conversation else None
@@ -404,13 +404,13 @@ class LLM:
404
404
  stream: bool = False,
405
405
  full_response: bool = False,
406
406
  tools: Optional[List[Dict[str, Any]]] = None,
407
- reasoning_effort: Optional[Literal["low", "medium", "high"]] = None,
407
+ reasoning_effort: Optional[Literal["none", "low", "medium", "high"]] = None,
408
408
  **kwargs: Any,
409
409
  ) -> Union[
410
410
  V1ConversationResponseChunk, Generator[V1ConversationResponseChunk, None, None], str, Generator[str, None, None]
411
411
  ]:
412
- if reasoning_effort is not None and reasoning_effort not in ["low", "medium", "high"]:
413
- raise ValueError("reasoning_effort must be 'low', 'medium', 'high', or None")
412
+ if reasoning_effort is not None and reasoning_effort not in ["none", "low", "medium", "high"]:
413
+ raise ValueError("reasoning_effort must be 'none', 'low', 'medium', 'high', or None")
414
414
 
415
415
  if conversation and conversation not in self._conversations:
416
416
  self._get_conversations()
lightning_sdk/machine.py CHANGED
@@ -116,7 +116,7 @@ class Machine:
116
116
  )
117
117
  for m in possible_values:
118
118
  for machine_id in [machine, *additional_machine_ids]:
119
- if machine_id in (
119
+ if machine_id and machine_id in (
120
120
  getattr(m, "name", None),
121
121
  getattr(m, "instance_type", None),
122
122
  getattr(m, "slug", None),
@@ -127,6 +127,21 @@ class Machine:
127
127
  return cls(machine, *additional_machine_ids)
128
128
  return cls(machine, machine, machine)
129
129
 
130
+ @classmethod
131
+ def _from_accelerator(cls, accelerator: Any) -> "Machine":
132
+ if accelerator.accelerator_type == "GPU":
133
+ accelerator_resources_count = accelerator.resources.gpu
134
+ else:
135
+ accelerator_resources_count = accelerator.resources.cpu
136
+
137
+ return Machine.from_str(
138
+ accelerator.slug_multi_cloud,
139
+ accelerator.slug,
140
+ accelerator.instance_id,
141
+ accelerator.secondary_instance_id,
142
+ f"lit-{accelerator.family.lower()}-{accelerator_resources_count}",
143
+ )
144
+
130
145
 
131
146
  # CPU machines
132
147
  # default CPU machines
@@ -1,6 +1,7 @@
1
1
  import glob
2
2
  import os
3
3
  import warnings
4
+ from enum import Enum
4
5
  from pathlib import Path
5
6
  from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
6
7
 
@@ -8,9 +9,9 @@ from tqdm.auto import tqdm
8
9
 
9
10
  import lightning_sdk
10
11
  from lightning_sdk.agents import Agent
11
- from lightning_sdk.api import TeamspaceApi
12
- from lightning_sdk.lightning_cloud.openapi import V1Model, V1ModelVersionArchive, V1ProjectClusterBinding
13
- from lightning_sdk.machine import Machine
12
+ from lightning_sdk.api import CloudAccountApi, TeamspaceApi
13
+ from lightning_sdk.lightning_cloud.openapi import V1ClusterType, V1Model, V1ModelVersionArchive, V1ProjectClusterBinding
14
+ from lightning_sdk.machine import CloudProvider, Machine
14
15
  from lightning_sdk.models import UploadedModelInfo
15
16
  from lightning_sdk.organization import Organization
16
17
  from lightning_sdk.owner import Owner
@@ -30,6 +31,27 @@ if TYPE_CHECKING:
30
31
  from lightning_sdk.studio import Studio
31
32
 
32
33
 
34
+ class FolderLocation(Enum):
35
+ AWS = "AWS"
36
+ GCP = "GCP"
37
+ CLOUD_AGNOSTIC = "CLOUD_AGNOSTIC"
38
+
39
+ def __str__(self) -> str:
40
+ """Converts the FolderLocation to a str."""
41
+ return self.value
42
+
43
+
44
+ class ConnectionType(Enum):
45
+ EFS = "EFS"
46
+ S3 = "S3"
47
+ GCS = "GCS"
48
+ FILESTORE = "FILESTORE"
49
+
50
+ def __str__(self) -> str:
51
+ """Converts the FolderLocation to a str."""
52
+ return self.value
53
+
54
+
33
55
  class Teamspace:
34
56
  """A teamspace is a collection of Studios, Clusters, Members and an associated Budget.
35
57
 
@@ -54,6 +76,7 @@ class Teamspace:
54
76
  user: Optional[Union[str, User]] = None,
55
77
  ) -> None:
56
78
  self._teamspace_api = TeamspaceApi()
79
+ self._cloud_account_api = CloudAccountApi()
57
80
 
58
81
  name = _resolve_teamspace_name(name)
59
82
 
@@ -515,6 +538,96 @@ class Teamspace:
515
538
  cloud_account=self.default_cloud_account,
516
539
  )
517
540
 
541
+ def new_folder(
542
+ self, name: str, location: Optional[FolderLocation] = None, cloud_account: Optional[str] = None
543
+ ) -> None:
544
+ """Create a new folder in this Teamspace.
545
+
546
+ Args:
547
+ name: The name of the folder. Folders will be accesible under `/teamspace/folders/<name>`
548
+ location: The location of the folder. Defaults to cloud agnostic.
549
+ cloud_account: The cloud account to create the folder in. Not used for cloud agnostic folders.
550
+ """
551
+ if cloud_account is None:
552
+ cloud_account = self.default_cloud_account
553
+
554
+ cloud_accounts = self._cloud_account_api.list_cloud_accounts(self.id)
555
+ resolved_cloud_accounts = [
556
+ external_cloud for external_cloud in cloud_accounts if external_cloud.id == cloud_account
557
+ ]
558
+
559
+ if len(resolved_cloud_accounts) == 0:
560
+ raise ValueError(f"Cloud account not found: {cloud_account}")
561
+
562
+ resolved_cloud_account = resolved_cloud_accounts[0]
563
+
564
+ # if the cloud account is global, default to agnostic
565
+ if location is None and resolved_cloud_account.spec.cluster_type == V1ClusterType.GLOBAL:
566
+ location = FolderLocation.CLOUD_AGNOSTIC
567
+
568
+ # if it's global, then default to agnostic, and aws / gcp otherwise if set
569
+ if (
570
+ location is not None
571
+ and location != FolderLocation.CLOUD_AGNOSTIC
572
+ and resolved_cloud_account.spec.cluster_type == V1ClusterType.GLOBAL
573
+ ):
574
+ providers = self._cloud_account_api.get_cloud_account_provider_mapping(self.id)
575
+
576
+ if location == FolderLocation.AWS:
577
+ resolved_cloud_account = providers[CloudProvider.AWS]
578
+ elif location == FolderLocation.GCP:
579
+ resolved_cloud_account = providers[CloudProvider.GCP]
580
+
581
+ if location == FolderLocation.CLOUD_AGNOSTIC:
582
+ self._teamspace_api.new_folder(self.id, name, None)
583
+ else:
584
+ self._teamspace_api.new_folder(self.id, name, resolved_cloud_account)
585
+
586
+ return
587
+
588
+ def new_connection(
589
+ self,
590
+ name: str,
591
+ source: str,
592
+ connection_type: ConnectionType,
593
+ writable: bool = True,
594
+ cloud_account: Optional[str] = None,
595
+ region: Optional[str] = None,
596
+ ) -> None:
597
+ """Add an existing data source to this Teamspace.
598
+
599
+ Args:
600
+ name: the name under which your data will be available in this Teamspace
601
+ source: the source spec of your data. Format depends on the type of data to connect.
602
+ For EFS, this should be the filsystem id
603
+ connection_type: the kind of data to connect to this Teamspace
604
+ writable: whether to support write-back to this data source. If False, the data is connected as read-only
605
+ cloud_account: which cloud-account to connect to the data source to.
606
+ If not specified, will retrieve the cloud-account that matches the required provider type
607
+ starting with private cloud accounts and falling back to public if necessary.
608
+ region: which provider region this data is in. Required for some connection types only.
609
+ """
610
+ provider_for_connection = self._cloud_account_api.get_cloud_provider_for_connection_type(connection_type)
611
+
612
+ if connection_type == ConnectionType.EFS and region is None:
613
+ raise ValueError("Region must be specified")
614
+
615
+ cloud_account = self._cloud_account_api.resolve_cloud_account(
616
+ self.id, cloud_account, provider_for_connection, None
617
+ )
618
+
619
+ cloud_accounts = self._cloud_account_api.list_cloud_accounts(self.id)
620
+ resolved_cloud_accounts = [
621
+ external_cloud for external_cloud in cloud_accounts if external_cloud.id == cloud_account
622
+ ]
623
+
624
+ if len(resolved_cloud_accounts) == 0:
625
+ raise ValueError(f"Cloud account not found: {cloud_account}")
626
+
627
+ resolved_cloud_account = resolved_cloud_accounts[0]
628
+
629
+ self._teamspace_api.new_connection(self.id, name, source, resolved_cloud_account, writable, region or "")
630
+
518
631
 
519
632
  def _list_files(path: Union[str, Path]) -> Tuple[List[Path], List[str]]:
520
633
  """List all folders in a directory and return them as a list and relative path."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightning_sdk
3
- Version: 2025.9.11
3
+ Version: 2025.9.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
@@ -1,12 +1,12 @@
1
1
  docs/source/conf.py,sha256=r8yX20eC-4mHhMTd0SbQb5TlSWHhO6wnJ0VJ_FBFpag,13249
2
- lightning_sdk/__init__.py,sha256=RTO9pPFR4nJ_S0mZ9BOC-0Wp3hCm4yZ1f8Lvr8RjDwI,1145
2
+ lightning_sdk/__init__.py,sha256=i1LQ7zzjJrjKEzaMjFBS0esHYmtvvMuXySVY_i34Upg,1221
3
3
  lightning_sdk/agents.py,sha256=ly6Ma1j0ZgGPFyvPvMN28JWiB9dATIstFa5XM8pMi6I,1577
4
4
  lightning_sdk/ai_hub.py,sha256=iI1vNhgcz_Ff1c3rN1ogN7dK-r-HXRj6NMtS2cA14UA,6925
5
5
  lightning_sdk/base_studio.py,sha256=_Pwwl37R9GRd7t-f2kO5aQXiLNrP4sUtUNht2ZkP8LE,3678
6
6
  lightning_sdk/constants.py,sha256=ztl1PTUBULnqTf3DyKUSJaV_O20hNtUYT6XvAYIrmIk,749
7
7
  lightning_sdk/helpers.py,sha256=KWMWnORHItIIA3PGn71YPs-7RjzGi8IXa2kQ5Qo4U8M,2459
8
8
  lightning_sdk/lit_container.py,sha256=8ys49TXI9MO89jLTA7MwDrKrssTRARAIF9OVmolDLq0,5273
9
- lightning_sdk/machine.py,sha256=FUZMGnvIXeXT-14WjA3ke98bG33tMN7v8K3kOLepAnk,8887
9
+ lightning_sdk/machine.py,sha256=WXBAkO42GdvL3rYykVTl5UUhpQpnbLThngzymijBInk,9462
10
10
  lightning_sdk/models.py,sha256=ur1My3oivAe0p2tAYtOAGM_1tPBht3Oh-xLun-oUK08,7861
11
11
  lightning_sdk/organization.py,sha256=jizjG4oW_oxkP2Qz4vL5cle4Cg12xB_vf4eT-bnvLP0,1352
12
12
  lightning_sdk/owner.py,sha256=t5svD2it4C9pbSpVuG9WJL46CYi37JXNziwnXxhiU5U,1361
@@ -15,24 +15,24 @@ lightning_sdk/sandbox.py,sha256=_NvnWotEXW2rBiVFZZ4krKXxVjuAqfNh04qELSM0-Pg,5786
15
15
  lightning_sdk/serve.py,sha256=uW7zLhQ3X90ifetpxzTb8FNxifv5vIs7qZlgfEjVKzk,11794
16
16
  lightning_sdk/status.py,sha256=lLGAuSvXBoXQFEEsEYwdCi0RcSNatUn5OPjJVjDtoM0,386
17
17
  lightning_sdk/studio.py,sha256=3BE44Ya-2HXmDkF58lVQmd7KVzzzPktqX1SV7K3nkfo,31438
18
- lightning_sdk/teamspace.py,sha256=qYpIYgg3eyWj9vcqdfhQN-YCpS47tOc_xB3de07p4Y8,21630
18
+ lightning_sdk/teamspace.py,sha256=eMatrnUr6E-ok96HIHRhghT2x_yEj_ToA6chSgYddGk,26265
19
19
  lightning_sdk/user.py,sha256=TSYh38rxoi7qKOfrK2JYh_Nknya2Kbz2ngDIY85fFOY,1778
20
- lightning_sdk/api/__init__.py,sha256=Qn2VVRvir_gO7w4yxGLkZY-R3T7kdiTPKgQ57BhIA9k,413
20
+ lightning_sdk/api/__init__.py,sha256=xrp_RNECJGQtL5rZHF69WOzEuEIbWSLtjWAJAz4R5K4,500
21
21
  lightning_sdk/api/agents_api.py,sha256=G47TbFo9kYqnBMqdw2RW-lfS1VAUBSXDmzs6fpIEMUs,4059
22
22
  lightning_sdk/api/ai_hub_api.py,sha256=azqDZ-PzasVAcoQHno7k7OO_xFOHQ4NDozxF8jEh83Y,7864
23
23
  lightning_sdk/api/base_studio_api.py,sha256=3R8tucZX2e9yKHBcY2rWFRP4dxqLrC6H75vdBDkH0ck,3617
24
- lightning_sdk/api/cloud_account_api.py,sha256=b_Egg5yhDh5G39g1cqA3ZxBDmU9iuDs2PlBMmW0HVnA,8417
24
+ lightning_sdk/api/cloud_account_api.py,sha256=B9-Ixy6OESAH_5wn6gKJLpcazj_48eiFLRTpunuyzjc,9191
25
25
  lightning_sdk/api/deployment_api.py,sha256=v2AfoTDkQ-1CBh75FOjFkRpf6yc3U_edDy43uYSn19I,24852
26
- lightning_sdk/api/job_api.py,sha256=7spFPyotlSQ0Krx6WymslnpSQaaeOmFkydvhtg6p0Ic,16410
26
+ lightning_sdk/api/job_api.py,sha256=4uaqLiEGRmvFk2chiOltbgAfEC7seeNTmsBENsaft88,16468
27
27
  lightning_sdk/api/license_api.py,sha256=XV3RhefyPQDYjwY9AaBZe4rByZTEAnsvLDxcdm9q0Wo,2438
28
28
  lightning_sdk/api/lit_container_api.py,sha256=jCJVwd-3MNjejL9FyvH89pzt-SeG3G8qCJD16iTMJAQ,11454
29
29
  lightning_sdk/api/llm_api.py,sha256=8ouowlX-Ld0hRvueIS6fQqaEJLQMYskvhKV5-Z_A6gE,11886
30
- lightning_sdk/api/mmt_api.py,sha256=hIBsGiJ2qn5UjcHDxP5WUyKGT_AIFfpSHrQVwg0afBw,10699
30
+ lightning_sdk/api/mmt_api.py,sha256=adVGNFpinnsE3lkY4QZhpWxq4ZQjhyC1XTmOhpBWXvk,10691
31
31
  lightning_sdk/api/org_api.py,sha256=Ze3z_ATVrukobujV5YdC42DKj45Vuwl7X52q_Vr-o3U,803
32
32
  lightning_sdk/api/pipeline_api.py,sha256=rJYp_FN7uUjC5xbc6K67l2eRSmVuOkijd5i8Nm5BF7I,4621
33
- lightning_sdk/api/studio_api.py,sha256=Aji8lke2jGeef-1lh6wV7EQ15iuiw1Cn_IuBNnEb8SA,39492
34
- lightning_sdk/api/teamspace_api.py,sha256=ygQAGG5W4EqcXv2PO2cYPdAN5fGj75R-1IZkNq7Z9gU,18416
35
- lightning_sdk/api/user_api.py,sha256=FQFgdZOopEvtFJcJT1mRRuStqKKYmg4iuVUYDSQfDVM,4514
33
+ lightning_sdk/api/studio_api.py,sha256=ZwyaRcjPiE2O5gZ3Ko5zuB_QtzzDLZI9vUF6k7Z9Hl0,39484
34
+ lightning_sdk/api/teamspace_api.py,sha256=UZ6Ka6TL7zggs5qhQB4wl47VRusVTMpth1-toGVWbjQ,19974
35
+ lightning_sdk/api/user_api.py,sha256=o9gZmtvqNfj4kdSpo2fyyRuFAP7bM5w7mx0Oj9__Ads,4702
36
36
  lightning_sdk/api/utils.py,sha256=1NJgW4HCfzMqgnAqbMA7RRr2v3iM3KTuPIUQK5klDeQ,27127
37
37
  lightning_sdk/cli/__init__.py,sha256=lksw08t_ZIuHOH47LCIqSVHeZ8cUXI2aJWHYhyujYHM,32
38
38
  lightning_sdk/cli/entrypoint.py,sha256=85icG0fmUqXuFErkD2v_kOlqnr18veD8oxe7gPBWHE8,4556
@@ -83,11 +83,12 @@ lightning_sdk/cli/studio/switch.py,sha256=qhWhLvjk-BbwU8Br3rXPQIMVQLTUTq21f0v2P-
83
83
  lightning_sdk/cli/utils/__init__.py,sha256=0gHdWY3bqrIyiFiEh_uSBuxWpOykCjqUc8fPEV0z3no,186
84
84
  lightning_sdk/cli/utils/cloud_account_map.py,sha256=7oM7ns4ZJfkxxZhl-GMkxEkkinKbVkskx5dLcyEQiz4,414
85
85
  lightning_sdk/cli/utils/coloring.py,sha256=YEb1LiYb6CekVfCRb83-npAmNgd-7c1LzXuNDo4GLSc,2415
86
+ lightning_sdk/cli/utils/owner_selection.py,sha256=PbKbFoGblxi60lF_kZxnlqxjCmI9r9b9iDZClHkJcpQ,4216
86
87
  lightning_sdk/cli/utils/resolve.py,sha256=RWQ8MgXEJCdsoY50y8Pa9B-A6NA_SvxOqL5pVKfvSr8,917
87
88
  lightning_sdk/cli/utils/richt_print.py,sha256=psSY65nLAQbxK_K0w93Qq9857aUUbm77FLS1sc3Oecg,1262
88
89
  lightning_sdk/cli/utils/save_to_config.py,sha256=mMjjfA4Yv1e7MtE03iADTXcTSAuUL0Ws9OZObx6ufo0,1164
89
90
  lightning_sdk/cli/utils/studio_selection.py,sha256=tjYTm4sY3NSQaa-6Ob9R0UQ60wIXiTNT2406BUJvJbU,3927
90
- lightning_sdk/cli/utils/teamspace_selection.py,sha256=ZWo_aZuEuLzBWRgQEykypEM1sE_deDKLDcCyLgFWaaQ,5188
91
+ lightning_sdk/cli/utils/teamspace_selection.py,sha256=n-FFfWLTV63SxoK6Fi2VQikBIfKYqMNTew9b9v-1UQk,4945
91
92
  lightning_sdk/deployment/__init__.py,sha256=dXsa4psDzFYFklsq3JC-2V_L4FQjGZnQAf-ZiVlqG9c,545
92
93
  lightning_sdk/deployment/deployment.py,sha256=7Sk4ORKmgMDk7Owpk_q-89ws8-Y9orX0jRZF99tNv1E,21775
93
94
  lightning_sdk/job/__init__.py,sha256=1MxjQ6rHkyUHCypSW9RuXuVMVH11WiqhIXcU2LCFMwE,64
@@ -1152,7 +1153,7 @@ lightning_sdk/lightning_cloud/utils/dataset.py,sha256=4nUspe8iAaRPgSYpXA2uAQCgyd
1152
1153
  lightning_sdk/lightning_cloud/utils/name_generator.py,sha256=MkciuA10332V0mcE2PxLIiwWomWE0Fm_gNGK01vwRr4,58046
1153
1154
  lightning_sdk/lightning_cloud/utils/network.py,sha256=axPgl8rhyPcPjxiztDxyksfxax3VNg2OXL5F5Uc81b4,406
1154
1155
  lightning_sdk/llm/__init__.py,sha256=ErZva0HqN2iPtK_6hI6GN7A_HPGNrHo3wYh7vyFdO3Q,57
1155
- lightning_sdk/llm/llm.py,sha256=M2gKP77SDz05flUpvnqh6vLXLdNwNzPn2jzXTIls7S0,20805
1156
+ lightning_sdk/llm/llm.py,sha256=pjBs8fbGZjs0tQRKHx-WdfTmeECKAwI_T1ShVBgdyO4,20836
1156
1157
  lightning_sdk/llm/public_assistants.py,sha256=k0yc41AyrKImnRa8Fv-ow9javnlrRQeP63507p2VybA,1579
1157
1158
  lightning_sdk/mmt/__init__.py,sha256=ExMu90-96bGBnyp5h0CErQszUGB1-PcjC4-R8_NYbeY,117
1158
1159
  lightning_sdk/mmt/base.py,sha256=iqVudKDxazomuezj6l7pa-m9I1EQnM82OKs4ZQbo4Ck,16434
@@ -1177,9 +1178,9 @@ lightning_sdk/utils/enum.py,sha256=h2JRzqoBcSlUdanFHmkj_j5DleBHAu1esQYUsdNI-hU,4
1177
1178
  lightning_sdk/utils/names.py,sha256=1EuXbIh7wldkDp1FG10oz9vIOyWrpGWeFFVy-DQBgzA,18162
1178
1179
  lightning_sdk/utils/progress.py,sha256=IXfEcUF-rL5jIw0Hir6eSxN7VBZfR--1O2LaEhGAU70,12698
1179
1180
  lightning_sdk/utils/resolve.py,sha256=4TyEnIgIrvkSvYk5i5PmcIogD_5Y9pBhiphRLfLMttc,10477
1180
- lightning_sdk-2025.9.11.dist-info/LICENSE,sha256=uFIuZwj5z-4TeF2UuacPZ1o17HkvKObT8fY50qN84sg,1064
1181
- lightning_sdk-2025.9.11.dist-info/METADATA,sha256=qmaN-yNF1oQ04YxqFC1BOQJ8sdFTT8EjzzwqRihys3Q,4130
1182
- lightning_sdk-2025.9.11.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
1183
- lightning_sdk-2025.9.11.dist-info/entry_points.txt,sha256=OoZa4Fc8NMs6GSN0cdA1J8e6couzAcL82CbM1yo4f_M,122
1184
- lightning_sdk-2025.9.11.dist-info/top_level.txt,sha256=ps8doKILFXmN7F1mHncShmnQoTxKBRPIcchC8TpoBw4,19
1185
- lightning_sdk-2025.9.11.dist-info/RECORD,,
1181
+ lightning_sdk-2025.9.23.dist-info/LICENSE,sha256=uFIuZwj5z-4TeF2UuacPZ1o17HkvKObT8fY50qN84sg,1064
1182
+ lightning_sdk-2025.9.23.dist-info/METADATA,sha256=vBeooGy4iWhjSE6eAD-ZHrX1v5_0CXTqb5UeSsCb8WQ,4130
1183
+ lightning_sdk-2025.9.23.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
1184
+ lightning_sdk-2025.9.23.dist-info/entry_points.txt,sha256=OoZa4Fc8NMs6GSN0cdA1J8e6couzAcL82CbM1yo4f_M,122
1185
+ lightning_sdk-2025.9.23.dist-info/top_level.txt,sha256=ps8doKILFXmN7F1mHncShmnQoTxKBRPIcchC8TpoBw4,19
1186
+ lightning_sdk-2025.9.23.dist-info/RECORD,,