lightning-sdk 0.2.2__py3-none-any.whl → 0.2.3__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
@@ -31,6 +31,6 @@ __all__ = [
31
31
  "User",
32
32
  ]
33
33
 
34
- __version__ = "0.2.2"
34
+ __version__ = "0.2.3"
35
35
  _check_version_and_prompt_upgrade(__version__)
36
36
  _set_tqdm_envvars_noninteractive()
@@ -258,6 +258,7 @@ class DeploymentApi:
258
258
  auth: Optional[Union[BasicAuth, TokenAuth]] = None,
259
259
  custom_domain: Optional[str] = None,
260
260
  quantity: Optional[int] = None,
261
+ include_credentials: Optional[bool] = None,
261
262
  ) -> V1Deployment:
262
263
  # Update the deployment in place
263
264
 
@@ -283,6 +284,7 @@ class DeploymentApi:
283
284
  requires_release |= apply_change(deployment.spec, "cluster_id", cloud_account)
284
285
  requires_release |= apply_change(deployment.spec, "spot", spot)
285
286
  requires_release |= apply_change(deployment.spec, "quantity", quantity)
287
+ requires_release |= apply_change(deployment.spec, "include_credentials", include_credentials)
286
288
 
287
289
  if requires_release:
288
290
  if deployment.strategy is None:
@@ -549,6 +551,7 @@ def to_spec(
549
551
  env: Union[List[Union[Secret, Env]], Dict[str, str], None] = None,
550
552
  health_check: Optional[Union[HttpHealthCheck, ExecHealthCheck]] = None,
551
553
  quantity: Optional[int] = None,
554
+ include_credentials: Optional[bool] = None,
552
555
  ) -> V1JobSpec:
553
556
  if cloud_account is None:
554
557
  raise ValueError("The cloud account should be defined.")
@@ -569,6 +572,7 @@ def to_spec(
569
572
  instance_name=_machine_to_compute_name(machine),
570
573
  readiness_probe=to_health_check(health_check),
571
574
  quantity=quantity,
575
+ include_credentials=include_credentials,
572
576
  )
573
577
 
574
578
 
@@ -120,6 +120,7 @@ class LitContainerApi:
120
120
  yield {
121
121
  "finish": True,
122
122
  "url": f"{LIGHTNING_CLOUD_URL}/{teamspace.owner.name}/{teamspace.name}/containers/{container_basename}",
123
+ "repository": repository,
123
124
  }
124
125
 
125
126
  def _push_with_retry(self, repository: str, max_retries: int = 3) -> Iterator[Dict[str, Any]]:
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import subprocess
2
3
  from pathlib import Path
3
4
  from typing import Optional, Union
@@ -8,7 +9,9 @@ from rich.console import Console
8
9
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
9
10
  from rich.prompt import Confirm
10
11
 
12
+ from lightning_sdk.api.deployment_api import DeploymentApi
11
13
  from lightning_sdk.api.lit_container_api import LitContainerApi
14
+ from lightning_sdk.cli.exceptions import StudioCliError
12
15
  from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
13
16
  from lightning_sdk.serve import _LitServeDeployer
14
17
 
@@ -46,7 +49,7 @@ def serve() -> None:
46
49
  help="Deploy the model to the Lightning AI platform",
47
50
  )
48
51
  @click.option("--gpu", is_flag=True, default=False, flag_value=True, help="Use GPU for serving")
49
- @click.option("--repository", default=None, help="Docker repository name (e.g., 'username/model-name')")
52
+ @click.option("--name", default=None, help="Name of the deployed API (e.g., 'classification-api', 'Llama-api')")
50
53
  @click.option(
51
54
  "--non-interactive",
52
55
  "--non_interactive",
@@ -60,12 +63,12 @@ def api(
60
63
  easy: bool,
61
64
  cloud: bool,
62
65
  gpu: bool,
63
- repository: str,
66
+ name: str,
64
67
  non_interactive: bool,
65
68
  ) -> None:
66
69
  """Deploy a LitServe model script."""
67
70
  return api_impl(
68
- script_path=script_path, easy=easy, cloud=cloud, gpu=gpu, repository=repository, non_interactive=non_interactive
71
+ script_path=script_path, easy=easy, cloud=cloud, gpu=gpu, repository=name, non_interactive=non_interactive
69
72
  )
70
73
 
71
74
 
@@ -74,7 +77,8 @@ def api_impl(
74
77
  easy: bool = False,
75
78
  cloud: bool = False,
76
79
  gpu: bool = False,
77
- repository: Optional[str] = None,
80
+ repository: [str] = None,
81
+ tag: Optional[str] = None,
78
82
  non_interactive: bool = False,
79
83
  ) -> None:
80
84
  """Deploy a LitServe model script."""
@@ -85,12 +89,13 @@ def api_impl(
85
89
  if not script_path.is_file():
86
90
  raise ValueError(f"Path is not a file: {script_path}")
87
91
 
88
- ls_deployer = _LitServeDeployer()
89
- ls_deployer.generate_client() if easy else None
92
+ _LitServeDeployer.generate_client() if easy else None
90
93
 
91
94
  if cloud:
92
- tag = repository if repository else "litserve-model"
93
- return _handle_cloud(script_path, console, gpu=gpu, tag=tag, non_interactive=non_interactive)
95
+ repository = repository or "litserve-model"
96
+ return _handle_cloud(
97
+ script_path, console, gpu=gpu, repository=repository, tag=tag, non_interactive=non_interactive
98
+ )
94
99
 
95
100
  try:
96
101
  subprocess.run(
@@ -112,11 +117,13 @@ def _handle_cloud(
112
117
  teamspace: Optional[str] = None,
113
118
  non_interactive: bool = False,
114
119
  ) -> None:
120
+ menu = _TeamspacesMenu()
121
+ teamspace = menu._resolve_teamspace(teamspace)
115
122
  try:
116
123
  client = docker.from_env()
117
124
  client.ping()
118
125
  except docker.errors.DockerException as e:
119
- raise RuntimeError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
126
+ raise StudioCliError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
120
127
 
121
128
  ls_deployer = _LitServeDeployer()
122
129
  path = ls_deployer.dockerize_api(script_path, port=8000, gpu=gpu, tag=tag)
@@ -133,18 +140,29 @@ def _handle_cloud(
133
140
  tag = tag if tag else "latest"
134
141
 
135
142
  lit_cr = LitContainerApi()
136
- menu = _TeamspacesMenu()
137
- teamspace = menu._resolve_teamspace(teamspace)
143
+ deployment_name = os.path.basename(repository)
144
+
145
+ if DeploymentApi().get_deployment_by_name(deployment_name, teamspace.id):
146
+ raise StudioCliError(f"Deployment {deployment_name} already exists. Please choose a different name.") from None
147
+
138
148
  with Progress(
139
149
  SpinnerColumn(),
140
150
  TextColumn("[progress.description]{task.description}"),
141
151
  TimeElapsedColumn(),
142
152
  console=console,
143
- transient=False,
153
+ transient=True,
144
154
  ) as progress:
145
- ls_deployer._build_container(path, repository, tag, console, progress)
146
- ls_deployer._push_container(repository, tag, teamspace, lit_cr, progress)
147
- console.print(f"\n✅ Image pushed to {tag}", style="bold green")
148
- console.print(
149
- "Soon you will be able to deploy this model to the Lightning Studio!",
155
+ try:
156
+ ls_deployer.build_container(path, repository, tag, console, progress)
157
+ push_status = ls_deployer.push_container(repository, tag, teamspace, lit_cr, progress)
158
+ except Exception as e:
159
+ console.print(f" Deployment failed: {e}", style="red")
160
+ return
161
+ console.print(f"\n✅ Image pushed to {repository}:{tag}")
162
+ console.print(f"🔗 You can access the container at: [i]{push_status.get('url')}[/i]")
163
+ repository = push_status.get("repository")
164
+
165
+ deployment_status = ls_deployer._run_on_cloud(
166
+ deployment_name=deployment_name, image=repository, teamspace=teamspace, ports=[8000], gpu=gpu, metric=None
150
167
  )
168
+ console.print(f"🚀 Deployment started, access at [i]{deployment_status.get('url')}[/i]")
@@ -32,7 +32,7 @@ from lightning_sdk.organization import Organization
32
32
  from lightning_sdk.services.utilities import _get_cluster
33
33
  from lightning_sdk.teamspace import Teamspace
34
34
  from lightning_sdk.user import User
35
- from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_org, _resolve_teamspace, _resolve_user
35
+ from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_teamspace, _resolve_user
36
36
 
37
37
 
38
38
  class Deployment:
@@ -73,13 +73,12 @@ class Deployment:
73
73
  raise e
74
74
 
75
75
  self._name = name
76
- self._org = _resolve_org(org)
77
76
  self._user = _resolve_user(self._user or user)
78
77
 
79
78
  self._teamspace = _resolve_teamspace(
80
79
  teamspace=teamspace,
81
- org=self._org,
82
- user=self._user,
80
+ org=org,
81
+ user=user,
83
82
  )
84
83
  if self._teamspace is None:
85
84
  raise ValueError("You need to pass a teamspace or an org for your deployment.")
@@ -116,6 +115,7 @@ class Deployment:
116
115
  custom_domain: Optional[str] = None,
117
116
  cluster: Optional[str] = None, # deprecated in favor of cloud_account
118
117
  quantity: Optional[int] = None,
118
+ include_credentials: Optional[bool] = None,
119
119
  ) -> None:
120
120
  """The Lightning AI Deployment.
121
121
 
@@ -140,6 +140,7 @@ class Deployment:
140
140
  Doesn't matter when the studio already exists.
141
141
  custom_domain: Whether your service would be referenced under a custom doamin.
142
142
  quantity: The number of machines per replica to deploy.
143
+ include_credentials: Whether to include the environement variables for the SDK to authenticate
143
144
 
144
145
  Note:
145
146
  Since a teamspace can either be owned by an org or by a user directly,
@@ -172,6 +173,7 @@ class Deployment:
172
173
  machine=machine,
173
174
  health_check=health_check,
174
175
  quantity=quantity,
176
+ include_credentials=include_credentials if include_credentials is not None else True,
175
177
  ),
176
178
  strategy=to_strategy(release_strategy),
177
179
  )
@@ -203,6 +205,7 @@ class Deployment:
203
205
  custom_domain: Optional[str] = None,
204
206
  cluster: Optional[str] = None, # deprecated in favor of cloud_account
205
207
  quantity: Optional[int] = None,
208
+ include_credentials: Optional[bool] = None,
206
209
  ) -> None:
207
210
  cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
208
211
 
@@ -225,6 +228,7 @@ class Deployment:
225
228
  health_check=health_check,
226
229
  release_strategy=release_strategy,
227
230
  quantity=quantity,
231
+ include_credentials=include_credentials if include_credentials is not None else True,
228
232
  )
229
233
 
230
234
  def stop(self) -> None:
@@ -359,6 +363,14 @@ class Deployment:
359
363
  return self._deployment.spec.quantity
360
364
  return None
361
365
 
366
+ @property
367
+ def include_credentials(self) -> Optional[bool]:
368
+ """The number of machines per replica."""
369
+ if self._deployment:
370
+ self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
371
+ return self._deployment.spec.include_credentials
372
+ return None
373
+
362
374
  @property
363
375
  def user(self) -> Optional[User]:
364
376
  """The teamspace of the deployment."""
@@ -52,6 +52,7 @@ class ProjectIdCloudspacesBody(object):
52
52
  'name': 'str',
53
53
  'plugins': 'list[str]',
54
54
  'requested_run_duration_seconds': 'str',
55
+ 'same_compute_on_resume': 'bool',
55
56
  'seed_files': 'list[V1CloudSpaceSeedFile]',
56
57
  'spot': 'bool'
57
58
  }
@@ -68,11 +69,12 @@ class ProjectIdCloudspacesBody(object):
68
69
  'name': 'name',
69
70
  'plugins': 'plugins',
70
71
  'requested_run_duration_seconds': 'requestedRunDurationSeconds',
72
+ 'same_compute_on_resume': 'sameComputeOnResume',
71
73
  'seed_files': 'seedFiles',
72
74
  'spot': 'spot'
73
75
  }
74
76
 
75
- def __init__(self, can_download_source_code: 'bool' =None, cloud_space_instance_cpu_image_override: 'str' =None, cloud_space_instance_gpu_image_override: 'str' =None, cluster_id: 'str' =None, compute_name: 'str' =None, data_connection_mounts: 'list[V1DataConnectionMount]' =None, disk_size: 'str' =None, display_name: 'str' =None, name: 'str' =None, plugins: 'list[str]' =None, requested_run_duration_seconds: 'str' =None, seed_files: 'list[V1CloudSpaceSeedFile]' =None, spot: 'bool' =None): # noqa: E501
77
+ def __init__(self, can_download_source_code: 'bool' =None, cloud_space_instance_cpu_image_override: 'str' =None, cloud_space_instance_gpu_image_override: 'str' =None, cluster_id: 'str' =None, compute_name: 'str' =None, data_connection_mounts: 'list[V1DataConnectionMount]' =None, disk_size: 'str' =None, display_name: 'str' =None, name: 'str' =None, plugins: 'list[str]' =None, requested_run_duration_seconds: 'str' =None, same_compute_on_resume: 'bool' =None, seed_files: 'list[V1CloudSpaceSeedFile]' =None, spot: 'bool' =None): # noqa: E501
76
78
  """ProjectIdCloudspacesBody - a model defined in Swagger""" # noqa: E501
77
79
  self._can_download_source_code = None
78
80
  self._cloud_space_instance_cpu_image_override = None
@@ -85,6 +87,7 @@ class ProjectIdCloudspacesBody(object):
85
87
  self._name = None
86
88
  self._plugins = None
87
89
  self._requested_run_duration_seconds = None
90
+ self._same_compute_on_resume = None
88
91
  self._seed_files = None
89
92
  self._spot = None
90
93
  self.discriminator = None
@@ -110,6 +113,8 @@ class ProjectIdCloudspacesBody(object):
110
113
  self.plugins = plugins
111
114
  if requested_run_duration_seconds is not None:
112
115
  self.requested_run_duration_seconds = requested_run_duration_seconds
116
+ if same_compute_on_resume is not None:
117
+ self.same_compute_on_resume = same_compute_on_resume
113
118
  if seed_files is not None:
114
119
  self.seed_files = seed_files
115
120
  if spot is not None:
@@ -346,6 +351,27 @@ class ProjectIdCloudspacesBody(object):
346
351
 
347
352
  self._requested_run_duration_seconds = requested_run_duration_seconds
348
353
 
354
+ @property
355
+ def same_compute_on_resume(self) -> 'bool':
356
+ """Gets the same_compute_on_resume of this ProjectIdCloudspacesBody. # noqa: E501
357
+
358
+
359
+ :return: The same_compute_on_resume of this ProjectIdCloudspacesBody. # noqa: E501
360
+ :rtype: bool
361
+ """
362
+ return self._same_compute_on_resume
363
+
364
+ @same_compute_on_resume.setter
365
+ def same_compute_on_resume(self, same_compute_on_resume: 'bool'):
366
+ """Sets the same_compute_on_resume of this ProjectIdCloudspacesBody.
367
+
368
+
369
+ :param same_compute_on_resume: The same_compute_on_resume of this ProjectIdCloudspacesBody. # noqa: E501
370
+ :type: bool
371
+ """
372
+
373
+ self._same_compute_on_resume = same_compute_on_resume
374
+
349
375
  @property
350
376
  def seed_files(self) -> 'list[V1CloudSpaceSeedFile]':
351
377
  """Gets the seed_files of this ProjectIdCloudspacesBody. # noqa: E501
@@ -61,6 +61,7 @@ class V1UserFeatures(object):
61
61
  'deployment_alerts': 'bool',
62
62
  'deployment_persistent_disk': 'bool',
63
63
  'deployment_reservations': 'bool',
64
+ 'dgx_cloud': 'bool',
64
65
  'docs_agent': 'bool',
65
66
  'drive_v2': 'bool',
66
67
  'enable_crypto_crackdown': 'bool',
@@ -82,7 +83,6 @@ class V1UserFeatures(object):
82
83
  'open_api_in_studio': 'bool',
83
84
  'org_level_member_permissions': 'bool',
84
85
  'pipelines': 'bool',
85
- 'plugin_biz_chat': 'bool',
86
86
  'plugin_distributed': 'bool',
87
87
  'plugin_fiftyone': 'bool',
88
88
  'plugin_inference': 'bool',
@@ -139,6 +139,7 @@ class V1UserFeatures(object):
139
139
  'deployment_alerts': 'deploymentAlerts',
140
140
  'deployment_persistent_disk': 'deploymentPersistentDisk',
141
141
  'deployment_reservations': 'deploymentReservations',
142
+ 'dgx_cloud': 'dgxCloud',
142
143
  'docs_agent': 'docsAgent',
143
144
  'drive_v2': 'driveV2',
144
145
  'enable_crypto_crackdown': 'enableCryptoCrackdown',
@@ -160,7 +161,6 @@ class V1UserFeatures(object):
160
161
  'open_api_in_studio': 'openApiInStudio',
161
162
  'org_level_member_permissions': 'orgLevelMemberPermissions',
162
163
  'pipelines': 'pipelines',
163
- 'plugin_biz_chat': 'pluginBizChat',
164
164
  'plugin_distributed': 'pluginDistributed',
165
165
  'plugin_fiftyone': 'pluginFiftyone',
166
166
  'plugin_inference': 'pluginInference',
@@ -196,7 +196,7 @@ class V1UserFeatures(object):
196
196
  'vultr': 'vultr'
197
197
  }
198
198
 
199
- def __init__(self, affiliate_links: 'bool' =None, agents_v2: 'bool' =None, ai_hub_monetization: 'bool' =None, auto_fast_load: 'bool' =None, auto_join_orgs: 'bool' =None, b2c_experience: 'bool' =None, cap_add: 'list[str]' =None, cap_drop: 'list[str]' =None, capacity_reservation_byoc: 'bool' =None, capacity_reservation_dry_run: 'bool' =None, chat_models: 'bool' =None, code_tab: 'bool' =None, collab_screen_sharing: 'bool' =None, cost_attribution_settings: 'bool' =None, custom_app_domain: 'bool' =None, custom_instance_types: 'bool' =None, default_one_cluster: 'bool' =None, deployment_alerts: 'bool' =None, deployment_persistent_disk: 'bool' =None, deployment_reservations: 'bool' =None, docs_agent: 'bool' =None, drive_v2: 'bool' =None, enable_crypto_crackdown: 'bool' =None, enable_storage_limits: 'bool' =None, fair_share: 'bool' =None, featured_studios_admin: 'bool' =None, filestore: 'bool' =None, instant_capacity_reservation: 'bool' =None, job_artifacts_v2: 'bool' =None, jobs_v2: 'bool' =None, lambda_labs: 'bool' =None, landing_studios: 'bool' =None, lit_logger: 'bool' =None, mmt_fault_tolerance: 'bool' =None, mmt_strategy_selector: 'bool' =None, mmt_v2: 'bool' =None, multicloud_saas: 'bool' =None, multiple_studio_versions: 'bool' =None, open_api_in_studio: 'bool' =None, org_level_member_permissions: 'bool' =None, pipelines: 'bool' =None, plugin_biz_chat: 'bool' =None, plugin_distributed: 'bool' =None, plugin_fiftyone: 'bool' =None, plugin_inference: 'bool' =None, plugin_label_studio: 'bool' =None, plugin_langflow: 'bool' =None, plugin_lightning_apps: 'bool' =None, plugin_lightning_apps_distributed: 'bool' =None, plugin_mage_ai: 'bool' =None, plugin_milvus: 'bool' =None, plugin_python_profiler: 'bool' =None, plugin_react: 'bool' =None, plugin_service: 'bool' =None, plugin_sweeps: 'bool' =None, plugin_weviate: 'bool' =None, pricing_updates: 'bool' =None, product_generator: 'bool' =None, project_selector: 'bool' =None, restartable_jobs: 'bool' =None, runnable_public_studio_page: 'bool' =None, security_docs: 'bool' =None, show_dev_admin: 'bool' =None, slurm: 'bool' =None, slurm_machine_selector: 'bool' =None, snapshotter_service: 'bool' =None, snowflake_connection: 'bool' =None, stop_ide_container_on_shutdown: 'bool' =None, studio_config: 'bool' =None, studio_on_stop: 'bool' =None, studio_version_visibility: 'bool' =None, teamspace_storage_tab: 'bool' =None, trainium2: 'bool' =None, use_rclone_mounts_only: 'bool' =None, vultr: 'bool' =None): # noqa: E501
199
+ def __init__(self, affiliate_links: 'bool' =None, agents_v2: 'bool' =None, ai_hub_monetization: 'bool' =None, auto_fast_load: 'bool' =None, auto_join_orgs: 'bool' =None, b2c_experience: 'bool' =None, cap_add: 'list[str]' =None, cap_drop: 'list[str]' =None, capacity_reservation_byoc: 'bool' =None, capacity_reservation_dry_run: 'bool' =None, chat_models: 'bool' =None, code_tab: 'bool' =None, collab_screen_sharing: 'bool' =None, cost_attribution_settings: 'bool' =None, custom_app_domain: 'bool' =None, custom_instance_types: 'bool' =None, default_one_cluster: 'bool' =None, deployment_alerts: 'bool' =None, deployment_persistent_disk: 'bool' =None, deployment_reservations: 'bool' =None, dgx_cloud: 'bool' =None, docs_agent: 'bool' =None, drive_v2: 'bool' =None, enable_crypto_crackdown: 'bool' =None, enable_storage_limits: 'bool' =None, fair_share: 'bool' =None, featured_studios_admin: 'bool' =None, filestore: 'bool' =None, instant_capacity_reservation: 'bool' =None, job_artifacts_v2: 'bool' =None, jobs_v2: 'bool' =None, lambda_labs: 'bool' =None, landing_studios: 'bool' =None, lit_logger: 'bool' =None, mmt_fault_tolerance: 'bool' =None, mmt_strategy_selector: 'bool' =None, mmt_v2: 'bool' =None, multicloud_saas: 'bool' =None, multiple_studio_versions: 'bool' =None, open_api_in_studio: 'bool' =None, org_level_member_permissions: 'bool' =None, pipelines: 'bool' =None, plugin_distributed: 'bool' =None, plugin_fiftyone: 'bool' =None, plugin_inference: 'bool' =None, plugin_label_studio: 'bool' =None, plugin_langflow: 'bool' =None, plugin_lightning_apps: 'bool' =None, plugin_lightning_apps_distributed: 'bool' =None, plugin_mage_ai: 'bool' =None, plugin_milvus: 'bool' =None, plugin_python_profiler: 'bool' =None, plugin_react: 'bool' =None, plugin_service: 'bool' =None, plugin_sweeps: 'bool' =None, plugin_weviate: 'bool' =None, pricing_updates: 'bool' =None, product_generator: 'bool' =None, project_selector: 'bool' =None, restartable_jobs: 'bool' =None, runnable_public_studio_page: 'bool' =None, security_docs: 'bool' =None, show_dev_admin: 'bool' =None, slurm: 'bool' =None, slurm_machine_selector: 'bool' =None, snapshotter_service: 'bool' =None, snowflake_connection: 'bool' =None, stop_ide_container_on_shutdown: 'bool' =None, studio_config: 'bool' =None, studio_on_stop: 'bool' =None, studio_version_visibility: 'bool' =None, teamspace_storage_tab: 'bool' =None, trainium2: 'bool' =None, use_rclone_mounts_only: 'bool' =None, vultr: 'bool' =None): # noqa: E501
200
200
  """V1UserFeatures - a model defined in Swagger""" # noqa: E501
201
201
  self._affiliate_links = None
202
202
  self._agents_v2 = None
@@ -218,6 +218,7 @@ class V1UserFeatures(object):
218
218
  self._deployment_alerts = None
219
219
  self._deployment_persistent_disk = None
220
220
  self._deployment_reservations = None
221
+ self._dgx_cloud = None
221
222
  self._docs_agent = None
222
223
  self._drive_v2 = None
223
224
  self._enable_crypto_crackdown = None
@@ -239,7 +240,6 @@ class V1UserFeatures(object):
239
240
  self._open_api_in_studio = None
240
241
  self._org_level_member_permissions = None
241
242
  self._pipelines = None
242
- self._plugin_biz_chat = None
243
243
  self._plugin_distributed = None
244
244
  self._plugin_fiftyone = None
245
245
  self._plugin_inference = None
@@ -314,6 +314,8 @@ class V1UserFeatures(object):
314
314
  self.deployment_persistent_disk = deployment_persistent_disk
315
315
  if deployment_reservations is not None:
316
316
  self.deployment_reservations = deployment_reservations
317
+ if dgx_cloud is not None:
318
+ self.dgx_cloud = dgx_cloud
317
319
  if docs_agent is not None:
318
320
  self.docs_agent = docs_agent
319
321
  if drive_v2 is not None:
@@ -356,8 +358,6 @@ class V1UserFeatures(object):
356
358
  self.org_level_member_permissions = org_level_member_permissions
357
359
  if pipelines is not None:
358
360
  self.pipelines = pipelines
359
- if plugin_biz_chat is not None:
360
- self.plugin_biz_chat = plugin_biz_chat
361
361
  if plugin_distributed is not None:
362
362
  self.plugin_distributed = plugin_distributed
363
363
  if plugin_fiftyone is not None:
@@ -845,6 +845,27 @@ class V1UserFeatures(object):
845
845
 
846
846
  self._deployment_reservations = deployment_reservations
847
847
 
848
+ @property
849
+ def dgx_cloud(self) -> 'bool':
850
+ """Gets the dgx_cloud of this V1UserFeatures. # noqa: E501
851
+
852
+
853
+ :return: The dgx_cloud of this V1UserFeatures. # noqa: E501
854
+ :rtype: bool
855
+ """
856
+ return self._dgx_cloud
857
+
858
+ @dgx_cloud.setter
859
+ def dgx_cloud(self, dgx_cloud: 'bool'):
860
+ """Sets the dgx_cloud of this V1UserFeatures.
861
+
862
+
863
+ :param dgx_cloud: The dgx_cloud of this V1UserFeatures. # noqa: E501
864
+ :type: bool
865
+ """
866
+
867
+ self._dgx_cloud = dgx_cloud
868
+
848
869
  @property
849
870
  def docs_agent(self) -> 'bool':
850
871
  """Gets the docs_agent of this V1UserFeatures. # noqa: E501
@@ -1286,27 +1307,6 @@ class V1UserFeatures(object):
1286
1307
 
1287
1308
  self._pipelines = pipelines
1288
1309
 
1289
- @property
1290
- def plugin_biz_chat(self) -> 'bool':
1291
- """Gets the plugin_biz_chat of this V1UserFeatures. # noqa: E501
1292
-
1293
-
1294
- :return: The plugin_biz_chat of this V1UserFeatures. # noqa: E501
1295
- :rtype: bool
1296
- """
1297
- return self._plugin_biz_chat
1298
-
1299
- @plugin_biz_chat.setter
1300
- def plugin_biz_chat(self, plugin_biz_chat: 'bool'):
1301
- """Sets the plugin_biz_chat of this V1UserFeatures.
1302
-
1303
-
1304
- :param plugin_biz_chat: The plugin_biz_chat of this V1UserFeatures. # noqa: E501
1305
- :type: bool
1306
- """
1307
-
1308
- self._plugin_biz_chat = plugin_biz_chat
1309
-
1310
1310
  @property
1311
1311
  def plugin_distributed(self) -> 'bool':
1312
1312
  """Gets the plugin_distributed of this V1UserFeatures. # noqa: E501
lightning_sdk/serve.py CHANGED
@@ -1,13 +1,18 @@
1
1
  import os
2
+ import shlex
3
+ import subprocess
2
4
  import warnings
3
5
  from pathlib import Path
6
+ from typing import Generator, List, Optional
4
7
 
5
8
  import docker
6
9
  from rich.console import Console
7
10
  from rich.progress import Progress
8
11
 
9
- from lightning_sdk import Teamspace
12
+ from lightning_sdk import Deployment, Machine, Teamspace
13
+ from lightning_sdk.api.deployment_api import AutoScaleConfig
10
14
  from lightning_sdk.api.lit_container_api import LitContainerApi
15
+ from lightning_sdk.api.utils import _get_cloud_url
11
16
 
12
17
 
13
18
  class _LitServeDeployer:
@@ -17,6 +22,8 @@ class _LitServeDeployer:
17
22
 
18
23
  @property
19
24
  def client(self) -> docker.DockerClient:
25
+ os.environ["DOCKER_BUILDKIT"] = "1"
26
+
20
27
  if self._client is None:
21
28
  try:
22
29
  self._client = docker.from_env()
@@ -80,8 +87,11 @@ Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additio
80
87
  console.print(success_msg)
81
88
  return os.path.abspath("Dockerfile")
82
89
 
83
- def generate_client(self) -> None:
84
- console = self._console
90
+ @staticmethod
91
+ def generate_client() -> None:
92
+ from rich.console import Console
93
+
94
+ console = Console()
85
95
  try:
86
96
  from litserve.python_client import client_template
87
97
  except ImportError:
@@ -99,36 +109,113 @@ Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additio
99
109
  except OSError as e:
100
110
  raise OSError(f"Failed to generate client.py: {e!s}") from None
101
111
 
102
- def _build_container(self, path: str, repository: str, tag: str, console: Console, progress: Progress) -> None:
103
- build_task = progress.add_task("Building Docker image", total=None)
104
- build_status = self.client.api.build(
105
- path=os.path.dirname(path), dockerfile=path, tag=f"{repository}:{tag}", decode=True, quiet=False
112
+ def _docker_build_with_logs(
113
+ self, path: str, repository: str, tag: str, platform: str = "linux/amd64"
114
+ ) -> Generator[str, None, None]:
115
+ """Build Docker image using CLI with real-time log streaming.
116
+
117
+ Returns:
118
+ Tuple: (image_id, logs generator)
119
+
120
+ Raises:
121
+ RuntimeError: On build failure
122
+ """
123
+ cmd = f"docker build --platform {platform} -t {repository}:{tag} ."
124
+ proc = subprocess.Popen(
125
+ shlex.split(cmd),
126
+ stdout=subprocess.PIPE,
127
+ stderr=subprocess.STDOUT,
128
+ text=True,
129
+ bufsize=1, # Line buffered
106
130
  )
107
- for line in build_status:
131
+
132
+ def log_generator() -> Generator[str, None, None]:
133
+ while True:
134
+ line = proc.stdout.readline()
135
+ if not line and proc.poll() is not None:
136
+ break
137
+ yield line.strip()
138
+ if "error" in line.lower():
139
+ proc.terminate()
140
+ raise RuntimeError(f"Build failed: {line.strip()}")
141
+
142
+ if proc.returncode != 0:
143
+ raise RuntimeError(f"Build failed with exit code {proc.returncode}")
144
+
145
+ return log_generator()
146
+
147
+ def build_container(self, path: str, repository: str, tag: str, console: Console, progress: Progress) -> None:
148
+ build_task = progress.add_task("Building Docker image", total=None)
149
+ build_logs = self._docker_build_with_logs(path, repository, tag=tag)
150
+
151
+ for line in build_logs:
108
152
  if "error" in line:
109
153
  progress.stop()
110
154
  console.print(f"\n[red]{line}[/red]")
111
- return
112
- if "stream" in line and line["stream"].strip():
113
- console.print(line["stream"].strip(), style="bright_black")
155
+ raise RuntimeError(f"Failed to build image: {line}")
156
+ else:
157
+ console.print(
158
+ line.strip(),
159
+ )
114
160
  progress.update(build_task, description="Building Docker image")
115
161
 
116
162
  progress.update(build_task, description="[green]Build completed![/green]")
117
163
 
118
- def _push_container(
164
+ def push_container(
119
165
  self, repository: str, tag: str, teamspace: Teamspace, lit_cr: LitContainerApi, progress: Progress
120
- ) -> None:
166
+ ) -> dict:
121
167
  console = self._console
122
168
  push_task = progress.add_task("Pushing to registry", total=None)
123
169
  console.print("\nPushing image...", style="bold blue")
124
170
  lit_cr.authenticate()
125
171
  push_status = lit_cr.upload_container(repository, teamspace, tag=tag)
172
+ last_status = {}
126
173
  for line in push_status:
174
+ last_status = line
127
175
  if "error" in line:
128
176
  progress.stop()
129
177
  console.print(f"\n[red]{line}[/red]")
130
- return
178
+ raise RuntimeError(f"Failed to push image: {line}")
131
179
  if "status" in line:
132
- console.print(line["status"], style="bright_black")
180
+ console.print(line["status"].strip())
133
181
  progress.update(push_task, description="Pushing to registry")
134
182
  progress.update(push_task, description="[green]Push completed![/green]")
183
+ return last_status
184
+
185
+ def _run_on_cloud(
186
+ self,
187
+ deployment_name: str,
188
+ teamspace: Teamspace,
189
+ image: str,
190
+ ports: List[int],
191
+ gpu: bool = False,
192
+ metric: Optional[str] = None,
193
+ machine: Optional[Machine] = None,
194
+ min_replica: Optional[int] = 1,
195
+ max_replica: Optional[int] = 1,
196
+ spot: Optional[bool] = None,
197
+ replicas: Optional[int] = None,
198
+ cloud_account: Optional[str] = None,
199
+ ) -> dict:
200
+ machine = machine or Machine.CPU
201
+ metric = metric or "GPU" if gpu else "CPU"
202
+ url = f"{_get_cloud_url()}/{teamspace.owner.name}/{teamspace.name}/jobs/{deployment_name}"
203
+ deployment = Deployment(deployment_name, teamspace)
204
+ if deployment.is_started:
205
+ raise RuntimeError(
206
+ f"Deployment with name {deployment_name} already running. "
207
+ "Please stop the deployment before starting a new one.\n"
208
+ f"You can access the deployment at {url}"
209
+ )
210
+ autoscale = AutoScaleConfig(min_replicas=min_replica, max_replicas=max_replica, metric=metric, threshold=0.95)
211
+ deployment.start(
212
+ machine=machine,
213
+ image=image,
214
+ ports=ports,
215
+ autoscale=autoscale,
216
+ spot=spot,
217
+ replicas=replicas,
218
+ cloud_account=cloud_account,
219
+ )
220
+
221
+ return {"deployment": deployment, "url": url}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightning_sdk
3
- Version: 0.2.2
3
+ Version: 0.2.3
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,5 +1,5 @@
1
1
  docs/source/conf.py,sha256=r8yX20eC-4mHhMTd0SbQb5TlSWHhO6wnJ0VJ_FBFpag,13249
2
- lightning_sdk/__init__.py,sha256=AXfGavmhTcfNwZPxvjYvNeu_YAKQzZ8A9NSbbFp8e90,1104
2
+ lightning_sdk/__init__.py,sha256=YGP-QC_WV8gn8xxxESb1X6DcaOVnY-lkbM0NHBZor_s,1104
3
3
  lightning_sdk/agents.py,sha256=ly6Ma1j0ZgGPFyvPvMN28JWiB9dATIstFa5XM8pMi6I,1577
4
4
  lightning_sdk/ai_hub.py,sha256=dfw5CS93ehGmPlZCbbE2Pu3v0ANI6ZY05-ULbcHFqs4,7189
5
5
  lightning_sdk/constants.py,sha256=ztl1PTUBULnqTf3DyKUSJaV_O20hNtUYT6XvAYIrmIk,749
@@ -10,7 +10,7 @@ lightning_sdk/models.py,sha256=inFyRA8aLMvrAhCeIdJaTGHjp0Qtou9G_J3gXePxU4E,5975
10
10
  lightning_sdk/organization.py,sha256=WCfzdgjtvY1_A07DnxOpp74V2JR2gQwtXbIEcFDnoVU,1232
11
11
  lightning_sdk/owner.py,sha256=t5svD2it4C9pbSpVuG9WJL46CYi37JXNziwnXxhiU5U,1361
12
12
  lightning_sdk/plugin.py,sha256=TGxCa-xsOpv_kaKiVnNBNG3jLe9MweSS5Cnz9PcxQfY,14860
13
- lightning_sdk/serve.py,sha256=FydpScppL1HhzwkvvcihRVcWc6kdQSuajwTlC6tXHiU,5368
13
+ lightning_sdk/serve.py,sha256=gllkjhCzG_Pdo5iV9AF78ytwgJXxjIUmb8vebi50M1o,8334
14
14
  lightning_sdk/status.py,sha256=lLGAuSvXBoXQFEEsEYwdCi0RcSNatUn5OPjJVjDtoM0,386
15
15
  lightning_sdk/studio.py,sha256=hyvAiVhkETAtbu0RRF1Aw6F8Y__E1SSAmsB8PHfmqHo,19935
16
16
  lightning_sdk/teamspace.py,sha256=CzpQJ6Uj_at0W-HIzYd-Y3gN9qBotUbAV3YBWjNT9U8,14364
@@ -18,9 +18,9 @@ lightning_sdk/user.py,sha256=vdn8pZqkAZO0-LoRsBdg0TckRKtd_H3QF4gpiZcl4iY,1130
18
18
  lightning_sdk/api/__init__.py,sha256=Qn2VVRvir_gO7w4yxGLkZY-R3T7kdiTPKgQ57BhIA9k,413
19
19
  lightning_sdk/api/agents_api.py,sha256=G47TbFo9kYqnBMqdw2RW-lfS1VAUBSXDmzs6fpIEMUs,4059
20
20
  lightning_sdk/api/ai_hub_api.py,sha256=Yr9VvxueIqPUUeMqExbjbWD5CE_yeWjq0dXQQv47erg,6779
21
- lightning_sdk/api/deployment_api.py,sha256=rtjhC19LuyABQ2R5CjXrF6_Y05kHQTyzhc6UTG36J84,22332
21
+ lightning_sdk/api/deployment_api.py,sha256=z_D7ZZAnsVe3Q_ZVx1azcLK2_pP5xj63tmU97qhgyOw,22583
22
22
  lightning_sdk/api/job_api.py,sha256=_mMAI_BG_48i-BLwCP_U72zgmM5zYa2KUZ7u66HWkIc,13568
23
- lightning_sdk/api/lit_container_api.py,sha256=ij2Z3Hg1JceVmabSn8imw8s2F74NyTIDCb3YYP5r8a4,7610
23
+ lightning_sdk/api/lit_container_api.py,sha256=m-3qZIIpZ24Z2Z8z9x8Di6cOSjkZmBFcf9R2yVMpA_4,7648
24
24
  lightning_sdk/api/mmt_api.py,sha256=-v7ATab-ThAM-HRClS92Ehxuu9MlBfdKWWFCGvVUHiM,8962
25
25
  lightning_sdk/api/org_api.py,sha256=Ze3z_ATVrukobujV5YdC42DKj45Vuwl7X52q_Vr-o3U,803
26
26
  lightning_sdk/api/pipeline_api.py,sha256=P5P9C6qOpyBGU0t5N68h1LuFAsAKmPPgkac6uObrYKw,1676
@@ -47,7 +47,7 @@ lightning_sdk/cli/list.py,sha256=dAZ94QPvE4IkH6GfL7171TMrMfcuWB53cvW6wk2eL4w,101
47
47
  lightning_sdk/cli/mmts_menu.py,sha256=HUXo3ZoZ3fWOCNWTQWoJgUlFXYq5uVm_6uFjAq7BDe8,2219
48
48
  lightning_sdk/cli/open.py,sha256=sgMLWBnkXdIq8H9XK_rph2bye3b07AKTJBQIk9fCGVc,1937
49
49
  lightning_sdk/cli/run.py,sha256=8JZiDrKwDhlaTOJd6qq2mCWJRqKm6shCWLzpbmFYIkE,13929
50
- lightning_sdk/cli/serve.py,sha256=bnAgsotNaTpud4grws3eWxfybVTRhPnoDpT13kOeGYI,4880
50
+ lightning_sdk/cli/serve.py,sha256=9g8FygBIIgT8qsL885xa-tUt4IXGSm7pNEnx23CIdOE,5729
51
51
  lightning_sdk/cli/start.py,sha256=jUk52lkEFC_fqiEPkwM8GwE68WMNEtzBuzjkvr3POd0,1840
52
52
  lightning_sdk/cli/stop.py,sha256=5nCrUe1BONpX1nKNhbSFqLaXXKaRhSO7PvM1BVYLgn4,2864
53
53
  lightning_sdk/cli/studios_menu.py,sha256=TA9rO6_fFHGMz0Nt4rJ6iV80X5pZE4xShrSiyXoU-oQ,4129
@@ -55,7 +55,7 @@ lightning_sdk/cli/switch.py,sha256=qLvDoQRldCNgO1XvIGg-i-GyqnkHVb_U1-X4svqeNNU,1
55
55
  lightning_sdk/cli/teamspace_menu.py,sha256=C3g3spTKgtMwoK7pnooy0MBPz4AKhFjcObkvZyZ4v04,3797
56
56
  lightning_sdk/cli/upload.py,sha256=nZ7hVKccis4Fnx2ae3Y2utUWTlKH_9N-xjRFG9Xh3q4,12486
57
57
  lightning_sdk/deployment/__init__.py,sha256=dXsa4psDzFYFklsq3JC-2V_L4FQjGZnQAf-ZiVlqG9c,545
58
- lightning_sdk/deployment/deployment.py,sha256=uXqRT6zbwp1_od-reQpv0QagrNEZuEQVsoaILzU17E8,16532
58
+ lightning_sdk/deployment/deployment.py,sha256=EbFoDLdj1UubXyOTSp1VTlpDTgwdczfyusdLStqD7WA,17218
59
59
  lightning_sdk/job/__init__.py,sha256=1MxjQ6rHkyUHCypSW9RuXuVMVH11WiqhIXcU2LCFMwE,64
60
60
  lightning_sdk/job/base.py,sha256=TS5KavtfBAFbLbNqqumEizY9edjO1joSmtUdcO5CThQ,17748
61
61
  lightning_sdk/job/job.py,sha256=1Xf0ne4wwXpkb_GJgWD8mfueJZoKR8G4lC5T6OXijlw,13075
@@ -207,7 +207,7 @@ lightning_sdk/lightning_cloud/openapi/models/profiler_captures_body.py,sha256=xT
207
207
  lightning_sdk/lightning_cloud/openapi/models/profiler_enabled_body.py,sha256=4YnP4KmUlXM5Brm5Sj5WnF82MTPr48FqH0C4R5YZRVY,4703
208
208
  lightning_sdk/lightning_cloud/openapi/models/project_id_agentmanagedendpoints_body.py,sha256=pK1HjgWsXhU7OOKwYdt-SwklzF39N3Ap7Lyrk75T43k,4673
209
209
  lightning_sdk/lightning_cloud/openapi/models/project_id_agents_body.py,sha256=oE3OGIhX0FfiKqE96YJmHnrRRAXgqrFsIAw840ib64Q,16193
210
- lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py,sha256=q36orD3j5-RjVaMtFp3q_DFD1ZbrHBnT2VLyciDsIeM,15176
210
+ lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py,sha256=kroaORLZVLUQIXO-jQrlZvmVXXJF9t6CFM09jTFRBeQ,16188
211
211
  lightning_sdk/lightning_cloud/openapi/models/project_id_clusters_body.py,sha256=ewyBgf8oHlhgXKmn4YdjuyG84iONOeNXiWtbRQJfj4U,4348
212
212
  lightning_sdk/lightning_cloud/openapi/models/project_id_datasets_body.py,sha256=NkuH_enmOZ0MnYJ_hH2iVP1-Y1a9XinHR_c-5DaJxqU,15094
213
213
  lightning_sdk/lightning_cloud/openapi/models/project_id_endpoints_body.py,sha256=bnCgn3Jz3KE7blDn7chONuxJuc7Wg1eYM1QG0hxGR9U,7507
@@ -876,7 +876,7 @@ lightning_sdk/lightning_cloud/openapi/models/v1_upstream_open_ai.py,sha256=jt1qQ
876
876
  lightning_sdk/lightning_cloud/openapi/models/v1_usage.py,sha256=RhhnH9ygScZyExg06WhvMNPPRLSe8FYkIftqF-D9NIU,13408
877
877
  lightning_sdk/lightning_cloud/openapi/models/v1_usage_details.py,sha256=U7qC698Xj5tb3D93ZskG6sDf3lTXE13UTlGeDTvtRU4,14062
878
878
  lightning_sdk/lightning_cloud/openapi/models/v1_usage_report.py,sha256=iH67BcONBSLYzcZpGpKWSOzJTCpuqYt7FU4OUs8BJ9k,6076
879
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py,sha256=Hsvc-MaidKATyRa6gshe0SXCSlcSQLjoh-3SmplRZys,68004
879
+ lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py,sha256=_I8vCY0BOl6yv-f_ZaqOmBhogr1SPhAz3JZZ7Veoq94,67885
880
880
  lightning_sdk/lightning_cloud/openapi/models/v1_user_requested_compute_config.py,sha256=3jeJfpbBpYY2B2Ao2j2N93CMO2CnvmPqndE4Lw3ZfMA,13056
881
881
  lightning_sdk/lightning_cloud/openapi/models/v1_user_requested_flow_compute_config.py,sha256=3WpZ-lf7xPwuYyQDMdP7Uc6-dh3vf5TaaUlcMfesfMk,5208
882
882
  lightning_sdk/lightning_cloud/openapi/models/v1_user_slurm_job_action_response.py,sha256=BdNzXH8Vsf5PHjl9Rd-TVkpAgx1YC9rf8LD0js-ba20,3058
@@ -928,9 +928,9 @@ lightning_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
928
928
  lightning_sdk/utils/dynamic.py,sha256=glUTO1JC9APtQ6Gr9SO02a3zr56-sPAXM5C3NrTpgyQ,1959
929
929
  lightning_sdk/utils/enum.py,sha256=h2JRzqoBcSlUdanFHmkj_j5DleBHAu1esQYUsdNI-hU,4106
930
930
  lightning_sdk/utils/resolve.py,sha256=MALzFO5iVlkZpnEiC9QkyWcTTYksCeHsloG6MCSJl48,6461
931
- lightning_sdk-0.2.2.dist-info/LICENSE,sha256=uFIuZwj5z-4TeF2UuacPZ1o17HkvKObT8fY50qN84sg,1064
932
- lightning_sdk-0.2.2.dist-info/METADATA,sha256=9XLJ6BN4gnCUuy4FTWyOA4mwG9e3vdoyv5_tktYGcsI,3991
933
- lightning_sdk-0.2.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
934
- lightning_sdk-0.2.2.dist-info/entry_points.txt,sha256=msB9PJWIJ784dX-OP8by51d4IbKYH3Fj1vCuA9oXjHY,68
935
- lightning_sdk-0.2.2.dist-info/top_level.txt,sha256=ps8doKILFXmN7F1mHncShmnQoTxKBRPIcchC8TpoBw4,19
936
- lightning_sdk-0.2.2.dist-info/RECORD,,
931
+ lightning_sdk-0.2.3.dist-info/LICENSE,sha256=uFIuZwj5z-4TeF2UuacPZ1o17HkvKObT8fY50qN84sg,1064
932
+ lightning_sdk-0.2.3.dist-info/METADATA,sha256=dqptSi26kADENeZWIzeE4uyJdaLagaxgvam8I_49_uQ,3991
933
+ lightning_sdk-0.2.3.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
934
+ lightning_sdk-0.2.3.dist-info/entry_points.txt,sha256=msB9PJWIJ784dX-OP8by51d4IbKYH3Fj1vCuA9oXjHY,68
935
+ lightning_sdk-0.2.3.dist-info/top_level.txt,sha256=ps8doKILFXmN7F1mHncShmnQoTxKBRPIcchC8TpoBw4,19
936
+ lightning_sdk-0.2.3.dist-info/RECORD,,