lightning-sdk 0.2.10__py3-none-any.whl → 0.2.12__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 +1 -1
- lightning_sdk/api/deployment_api.py +37 -3
- lightning_sdk/api/lit_container_api.py +13 -7
- lightning_sdk/api/llm_api.py +34 -0
- lightning_sdk/api/teamspace_api.py +29 -4
- lightning_sdk/cli/serve.py +87 -36
- lightning_sdk/deployment/deployment.py +55 -6
- lightning_sdk/lightning_cloud/openapi/__init__.py +5 -1
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +13 -1
- lightning_sdk/lightning_cloud/openapi/api/data_connection_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +115 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +5 -1
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/update.py +65 -195
- lightning_sdk/lightning_cloud/openapi/models/update1.py +357 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_cloud_space_environment_template_response.py +1 -53
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_job_resource.py +279 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_job_type.py +108 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_job_resources_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +55 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +29 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_reservation_billing_session.py +279 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_resources.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_usage.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +105 -1
- lightning_sdk/llm/__init__.py +3 -0
- lightning_sdk/llm/llm.py +56 -0
- lightning_sdk/models.py +17 -1
- lightning_sdk/serve.py +7 -6
- lightning_sdk/teamspace.py +14 -2
- {lightning_sdk-0.2.10.dist-info → lightning_sdk-0.2.12.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.10.dist-info → lightning_sdk-0.2.12.dist-info}/RECORD +45 -38
- lightning_sdk/lightning_cloud/openapi/models/environmenttemplates_id_body.py +0 -253
- {lightning_sdk-0.2.10.dist-info → lightning_sdk-0.2.12.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.10.dist-info → lightning_sdk-0.2.12.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.10.dist-info → lightning_sdk-0.2.12.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.10.dist-info → lightning_sdk-0.2.12.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
|
@@ -224,6 +224,7 @@ class DeploymentApi:
|
|
|
224
224
|
def create_deployment(
|
|
225
225
|
self,
|
|
226
226
|
deployment: V1Deployment,
|
|
227
|
+
from_onboarding: Optional[bool] = None,
|
|
227
228
|
) -> V1Deployment:
|
|
228
229
|
return self._client.jobs_service_create_deployment(
|
|
229
230
|
project_id=deployment.project_id,
|
|
@@ -236,6 +237,7 @@ class DeploymentApi:
|
|
|
236
237
|
replicas=deployment.replicas,
|
|
237
238
|
spec=deployment.spec,
|
|
238
239
|
strategy=deployment.strategy,
|
|
240
|
+
from_onboarding=from_onboarding,
|
|
239
241
|
),
|
|
240
242
|
)
|
|
241
243
|
|
|
@@ -458,7 +460,7 @@ def to_autoscaling(
|
|
|
458
460
|
if target_metrics is None and (threshold < 0 or threshold > 100):
|
|
459
461
|
raise ValueError("The autoscaling threshold should be defined between 0 and 100.")
|
|
460
462
|
|
|
461
|
-
if target_metrics is not None and len(target_metrics) == 0:
|
|
463
|
+
if target_metrics is not None and len(target_metrics) == 0 and metric is None:
|
|
462
464
|
raise ValueError("The target_metrics must be provided.")
|
|
463
465
|
|
|
464
466
|
if target_metrics is not None:
|
|
@@ -522,8 +524,14 @@ def to_endpoint(
|
|
|
522
524
|
def to_health_check(
|
|
523
525
|
health_check: Optional[Union[HttpHealthCheck, ExecHealthCheck]] = None
|
|
524
526
|
) -> Optional[V1JobHealthCheckConfig]:
|
|
527
|
+
# Use Default health check if none is provided
|
|
525
528
|
if not health_check:
|
|
526
|
-
return
|
|
529
|
+
return V1JobHealthCheckConfig(
|
|
530
|
+
failure_threshold=600,
|
|
531
|
+
initial_delay_seconds=0,
|
|
532
|
+
interval_seconds=1,
|
|
533
|
+
timeout_seconds=600,
|
|
534
|
+
)
|
|
527
535
|
|
|
528
536
|
health_check_config = V1JobHealthCheckConfig(
|
|
529
537
|
failure_threshold=health_check.failure_threshold,
|
|
@@ -553,6 +561,7 @@ def to_spec(
|
|
|
553
561
|
health_check: Optional[Union[HttpHealthCheck, ExecHealthCheck]] = None,
|
|
554
562
|
quantity: Optional[int] = None,
|
|
555
563
|
include_credentials: Optional[bool] = None,
|
|
564
|
+
cloudspace_id: Optional[None] = None,
|
|
556
565
|
) -> V1JobSpec:
|
|
557
566
|
if cloud_account is None:
|
|
558
567
|
raise ValueError("The cloud account should be defined.")
|
|
@@ -560,9 +569,15 @@ def to_spec(
|
|
|
560
569
|
if machine is None:
|
|
561
570
|
raise ValueError("The machine should be defined.")
|
|
562
571
|
|
|
563
|
-
if image is None:
|
|
572
|
+
if image is None and cloudspace_id is None:
|
|
564
573
|
raise ValueError("The image should be defined.")
|
|
565
574
|
|
|
575
|
+
if entrypoint is not None and cloudspace_id is not None:
|
|
576
|
+
raise ValueError("The entrypoint shouldn't be defined when a Studio is provided.")
|
|
577
|
+
|
|
578
|
+
if command is None and cloudspace_id is not None:
|
|
579
|
+
raise ValueError("The command should be defined.")
|
|
580
|
+
|
|
566
581
|
return V1JobSpec(
|
|
567
582
|
cluster_id=cloud_account,
|
|
568
583
|
command=command,
|
|
@@ -574,6 +589,7 @@ def to_spec(
|
|
|
574
589
|
readiness_probe=to_health_check(health_check),
|
|
575
590
|
quantity=quantity,
|
|
576
591
|
include_credentials=include_credentials,
|
|
592
|
+
cloudspace_id=cloudspace_id,
|
|
577
593
|
)
|
|
578
594
|
|
|
579
595
|
|
|
@@ -598,3 +614,21 @@ def apply_change(spec: Any, key: str, value: Any) -> bool:
|
|
|
598
614
|
return True
|
|
599
615
|
|
|
600
616
|
return False
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def compose_commands(commands: List[str]) -> str:
|
|
620
|
+
composite_command = []
|
|
621
|
+
|
|
622
|
+
for command in commands:
|
|
623
|
+
command = command.strip()
|
|
624
|
+
|
|
625
|
+
# Check if the command already has '&'
|
|
626
|
+
if command.endswith("&"):
|
|
627
|
+
# It's a background command, add it as a subshell without further adjustment
|
|
628
|
+
composite_command.append(f"( {command} )")
|
|
629
|
+
else:
|
|
630
|
+
# Sequential execution, add as-is and use `&&` to connect if followed by another command
|
|
631
|
+
composite_command.append(command)
|
|
632
|
+
|
|
633
|
+
# Joining commands, using `&&` between sequential parts and respecting subshell backgrounds
|
|
634
|
+
return " && ".join(composite_command)
|
|
@@ -125,6 +125,17 @@ class LitContainerApi:
|
|
|
125
125
|
except Exception as e:
|
|
126
126
|
raise ValueError(f"Could not delete container {container} from project {project_id}: {e!s}") from e
|
|
127
127
|
|
|
128
|
+
def get_container_url(
|
|
129
|
+
self, repository: str, tag: str, teamspace: Teamspace, cloud_account: Optional[str] = None
|
|
130
|
+
) -> str:
|
|
131
|
+
"""Docker container will be pushed to the URL returned from this function."""
|
|
132
|
+
registry_url = _get_registry_url()
|
|
133
|
+
container_basename = repository.split("/")[-1]
|
|
134
|
+
return (
|
|
135
|
+
f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
|
|
136
|
+
f"{teamspace.owner.name}/{teamspace.name}/{container_basename}"
|
|
137
|
+
)
|
|
138
|
+
|
|
128
139
|
@retry_on_lcr_auth_failure
|
|
129
140
|
def upload_container(
|
|
130
141
|
self,
|
|
@@ -147,7 +158,6 @@ class LitContainerApi:
|
|
|
147
158
|
Named cloud-account in the CLI options.
|
|
148
159
|
:param platform: If empty will be linux/amd64. This is important because our entire deployment infra runs on
|
|
149
160
|
linux/amd64. Will show user a warning otherwise.
|
|
150
|
-
:return_final_dict: Controls whether we respond with the dictionary containing metadata about container upload
|
|
151
161
|
:return: Generator[dict, None, dict]
|
|
152
162
|
"""
|
|
153
163
|
try:
|
|
@@ -163,18 +173,14 @@ class LitContainerApi:
|
|
|
163
173
|
except Exception as e:
|
|
164
174
|
raise ValueError(f"Unable to upload {container}:{tag}") from e
|
|
165
175
|
|
|
166
|
-
|
|
167
|
-
container_basename = container.split("/")[-1]
|
|
168
|
-
repository = (
|
|
169
|
-
f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
|
|
170
|
-
f"{teamspace.owner.name}/{teamspace.name}/{container_basename}"
|
|
171
|
-
)
|
|
176
|
+
repository = self.get_container_url(container, tag, teamspace, cloud_account)
|
|
172
177
|
tagged = self._docker_client.api.tag(f"{container}:{tag}", repository, tag)
|
|
173
178
|
if not tagged:
|
|
174
179
|
raise ValueError(f"Could not tag container {container}:{tag} with {repository}:{tag}")
|
|
175
180
|
yield from self._push_with_retry(repository, tag=tag)
|
|
176
181
|
|
|
177
182
|
if return_final_dict:
|
|
183
|
+
container_basename = repository.split("/")[-1]
|
|
178
184
|
yield {
|
|
179
185
|
"finish": True,
|
|
180
186
|
"url": f"{LIGHTNING_CLOUD_URL}/{teamspace.owner.name}/{teamspace.name}/containers/"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_conversation_response_chunk import V1ConversationResponseChunk
|
|
4
|
+
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LLMApi:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self._client = LightningClient(retry=False, max_tries=0)
|
|
10
|
+
|
|
11
|
+
def list_models(self) -> List[str]:
|
|
12
|
+
result = self._client.assistants_service_list_assistant_managed_endpoints()
|
|
13
|
+
return result.endpoints
|
|
14
|
+
|
|
15
|
+
def get_public_models(self) -> List[str]:
|
|
16
|
+
result = self._client.assistants_service_list_assistants(published=True)
|
|
17
|
+
return result.assistants
|
|
18
|
+
|
|
19
|
+
def start_conversation(
|
|
20
|
+
self, prompt: str, system_prompt: Optional[str], assistant_id: str
|
|
21
|
+
) -> V1ConversationResponseChunk:
|
|
22
|
+
body = {
|
|
23
|
+
"message": {
|
|
24
|
+
"author": {"role": "user"},
|
|
25
|
+
"content": [
|
|
26
|
+
{
|
|
27
|
+
"contentType": "text",
|
|
28
|
+
"parts": [prompt],
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
result = self._client.assistants_service_start_conversation(body, assistant_id)
|
|
34
|
+
return result.result
|
|
@@ -17,6 +17,7 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
17
17
|
V1ClusterAccelerator,
|
|
18
18
|
V1Endpoint,
|
|
19
19
|
V1Job,
|
|
20
|
+
V1Model,
|
|
20
21
|
V1ModelVersionArchive,
|
|
21
22
|
V1MultiMachineJob,
|
|
22
23
|
V1Project,
|
|
@@ -199,9 +200,7 @@ class TeamspaceApi:
|
|
|
199
200
|
|
|
200
201
|
def delete_model(self, name: str, version: Optional[str], teamspace_id: str) -> None:
|
|
201
202
|
"""Delete a model or a version from the model store."""
|
|
202
|
-
|
|
203
|
-
assert len(models) == 1, "Multiple models with the same name found"
|
|
204
|
-
model = models[0]
|
|
203
|
+
model = self.get_model(teamspace_id=teamspace_id, model_name=name)
|
|
205
204
|
# decide if delete only version of whole model
|
|
206
205
|
if version:
|
|
207
206
|
if version == "default":
|
|
@@ -257,7 +256,7 @@ class TeamspaceApi:
|
|
|
257
256
|
if main_pbar:
|
|
258
257
|
main_pbar.update(1)
|
|
259
258
|
|
|
260
|
-
def
|
|
259
|
+
def _complete_model_upload(self, model_id: str, version: str, teamspace_id: str) -> None:
|
|
261
260
|
self.models_api.models_store_complete_model_upload(
|
|
262
261
|
body=_DummyBody(),
|
|
263
262
|
project_id=teamspace_id,
|
|
@@ -306,3 +305,29 @@ class TeamspaceApi:
|
|
|
306
305
|
project_id=teamspace_id, id=cloud_account
|
|
307
306
|
)
|
|
308
307
|
return response.accelerator
|
|
308
|
+
|
|
309
|
+
def get_model(self, teamspace_id: str, model_id: Optional[str] = None, model_name: Optional[str] = None) -> V1Model:
|
|
310
|
+
if model_id:
|
|
311
|
+
return self.models_api.models_store_get_model(project_id=teamspace_id, model_id=model_id)
|
|
312
|
+
if not model_name:
|
|
313
|
+
raise ValueError("Either `model_id` or `model_name` must be provided.")
|
|
314
|
+
# list models with specific name
|
|
315
|
+
models = self.models_api.models_store_list_models(project_id=teamspace_id, name=model_name).models
|
|
316
|
+
if len(models) == 0:
|
|
317
|
+
raise ValueError(f"Model '{model_name}' does not exist.")
|
|
318
|
+
if len(models) > 1:
|
|
319
|
+
raise RuntimeError(f"Model name '{model_name}' is not a unique with this teamspace.")
|
|
320
|
+
# if there is only one model with the name, return it
|
|
321
|
+
return models[0]
|
|
322
|
+
|
|
323
|
+
def list_models(self, teamspace_id: str) -> List[V1Model]:
|
|
324
|
+
response = self.models_api.models_store_list_models(project_id=teamspace_id)
|
|
325
|
+
return response.models
|
|
326
|
+
|
|
327
|
+
def list_model_versions(
|
|
328
|
+
self, teamspace_id: str, model_id: Optional[str] = None, model_name: Optional[str] = None
|
|
329
|
+
) -> List[V1ModelVersionArchive]:
|
|
330
|
+
if model_name and not model_id:
|
|
331
|
+
model_id = self.get_model(teamspace_id=teamspace_id, model_name=model_name).id
|
|
332
|
+
response = self.models_api.models_store_list_model_versions(project_id=teamspace_id, model_id=model_id)
|
|
333
|
+
return response.versions
|
lightning_sdk/cli/serve.py
CHANGED
|
@@ -6,6 +6,7 @@ import webbrowser
|
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from threading import Thread
|
|
9
10
|
from typing import List, Optional, TypedDict, Union
|
|
10
11
|
from urllib.parse import urlencode
|
|
11
12
|
|
|
@@ -17,6 +18,7 @@ from rich.prompt import Confirm
|
|
|
17
18
|
from lightning_sdk import Machine, Teamspace
|
|
18
19
|
from lightning_sdk.api import UserApi
|
|
19
20
|
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
21
|
+
from lightning_sdk.api.utils import _get_registry_url
|
|
20
22
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
21
23
|
from lightning_sdk.lightning_cloud import env
|
|
22
24
|
from lightning_sdk.lightning_cloud.login import Auth, AuthServer
|
|
@@ -27,6 +29,7 @@ from lightning_sdk.utils.resolve import _get_authed_user, _resolve_teamspace
|
|
|
27
29
|
|
|
28
30
|
_MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
29
31
|
_POLL_TIMEOUT = 600
|
|
32
|
+
LITSERVE_CODE = os.environ.get("LITSERVE_CODE", "j39bzk903h")
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class _ServeGroup(click.Group):
|
|
@@ -233,7 +236,7 @@ def api_impl(
|
|
|
233
236
|
class _AuthServer(AuthServer):
|
|
234
237
|
def get_auth_url(self, port: int) -> str:
|
|
235
238
|
redirect_uri = f"http://localhost:{port}/login-complete"
|
|
236
|
-
params = urlencode({"redirectTo": redirect_uri, "
|
|
239
|
+
params = urlencode({"redirectTo": redirect_uri, "okbhrt": LITSERVE_CODE})
|
|
237
240
|
return f"{env.LIGHTNING_CLOUD_URL}/sign-in?{params}"
|
|
238
241
|
|
|
239
242
|
|
|
@@ -333,12 +336,13 @@ class _Onboarding:
|
|
|
333
336
|
return _OnboardingStatus.ONBOARDED
|
|
334
337
|
return _OnboardingStatus.ONBOARDING
|
|
335
338
|
|
|
336
|
-
def
|
|
339
|
+
def _wait_user_onboarding(self, timeout: int = _POLL_TIMEOUT) -> None:
|
|
337
340
|
"""Wait for user onboarding if they can join the teamspace otherwise move to select a teamspace."""
|
|
338
341
|
status = self.status
|
|
339
342
|
if status == _OnboardingStatus.ONBOARDED:
|
|
340
343
|
return
|
|
341
344
|
|
|
345
|
+
self.console.print("Waiting for account setup. Visit lightning.ai")
|
|
342
346
|
start_time = datetime.now()
|
|
343
347
|
while self.status != _OnboardingStatus.ONBOARDED:
|
|
344
348
|
time.sleep(5)
|
|
@@ -351,9 +355,13 @@ class _Onboarding:
|
|
|
351
355
|
|
|
352
356
|
def get_cloudspace_id(self, teamspace: Teamspace) -> Optional[str]:
|
|
353
357
|
cloudspaces: List[V1CloudSpace] = self.client.cloud_space_service_list_cloud_spaces(teamspace.id).cloudspaces
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
358
|
+
cloudspaces = sorted(cloudspaces, key=lambda cloudspace: cloudspace.created_at, reverse=True)
|
|
359
|
+
if len(cloudspaces) == 0:
|
|
360
|
+
raise RuntimeError("Error creating deployment! Finish account setup at lightning.ai first.")
|
|
361
|
+
# get the first cloudspace
|
|
362
|
+
cloudspace = cloudspaces[0]
|
|
363
|
+
if "scratch-studio" in cloudspace.name or "scratch-studio" in cloudspace.display_name:
|
|
364
|
+
return cloudspace.id
|
|
357
365
|
return None
|
|
358
366
|
|
|
359
367
|
def select_teamspace(self, teamspace: Optional[str], org: Optional[str], user: Optional[str]) -> Teamspace:
|
|
@@ -368,15 +376,8 @@ class _Onboarding:
|
|
|
368
376
|
|
|
369
377
|
# Run only when user hasn't completed onboarding yet.
|
|
370
378
|
menu = _TeamspacesMenu()
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if len(possible_teamspaces) == 1 and can_join_org:
|
|
375
|
-
# wait for onboarding to complete so that user can join an org
|
|
376
|
-
# create deployment in the org default teamspace
|
|
377
|
-
self.console.print("Waiting for account setup. Visit lightning.ai")
|
|
378
|
-
self._wait()
|
|
379
|
-
|
|
379
|
+
self._wait_user_onboarding()
|
|
380
|
+
# Onboarding has been completed - user already selected organization if they could
|
|
380
381
|
possible_teamspaces = menu._get_possible_teamspaces(self.user)
|
|
381
382
|
if len(possible_teamspaces) == 1:
|
|
382
383
|
# User didn't select any org
|
|
@@ -400,6 +401,38 @@ def is_connected(host: str = "8.8.8.8", port: int = 53, timeout: int = 10) -> bo
|
|
|
400
401
|
return False
|
|
401
402
|
|
|
402
403
|
|
|
404
|
+
def _upload_container(
|
|
405
|
+
console: Console,
|
|
406
|
+
ls_deployer: _LitServeDeployer,
|
|
407
|
+
repository: str,
|
|
408
|
+
tag: str,
|
|
409
|
+
resolved_teamspace: Teamspace,
|
|
410
|
+
lit_cr: LitContainerApi,
|
|
411
|
+
cloud_account: Optional[str],
|
|
412
|
+
) -> bool:
|
|
413
|
+
with Progress(
|
|
414
|
+
SpinnerColumn(),
|
|
415
|
+
TextColumn("[progress.description]{task.description}"),
|
|
416
|
+
TimeElapsedColumn(),
|
|
417
|
+
console=console,
|
|
418
|
+
transient=True,
|
|
419
|
+
) as progress:
|
|
420
|
+
try:
|
|
421
|
+
push_task = progress.add_task("Uploading container to Lightning registry", total=None)
|
|
422
|
+
for line in ls_deployer.push_container(
|
|
423
|
+
repository, tag, resolved_teamspace, lit_cr, cloud_account=cloud_account
|
|
424
|
+
):
|
|
425
|
+
progress.update(push_task, advance=1)
|
|
426
|
+
if not ("Pushing" in line["status"] or "Waiting" in line["status"]):
|
|
427
|
+
console.print(line["status"])
|
|
428
|
+
progress.update(push_task, description="[green]Push completed![/green]")
|
|
429
|
+
except Exception as e:
|
|
430
|
+
console.print(f"❌ Deployment failed: {e}", style="red")
|
|
431
|
+
return False
|
|
432
|
+
console.print(f"\n✅ Image pushed to {repository}:{tag}")
|
|
433
|
+
return True
|
|
434
|
+
|
|
435
|
+
|
|
403
436
|
def _handle_cloud(
|
|
404
437
|
script_path: Union[str, Path],
|
|
405
438
|
console: Console,
|
|
@@ -465,13 +498,16 @@ def _handle_cloud(
|
|
|
465
498
|
authenticate(shall_confirm=not non_interactive)
|
|
466
499
|
user_status = poll_verified_status()
|
|
467
500
|
cloudspace_id: Optional[str] = None
|
|
501
|
+
from_onboarding = False
|
|
468
502
|
if not user_status["verified"]:
|
|
469
503
|
console.print("❌ Verify phone number to continue. Visit lightning.ai.", style="red")
|
|
470
504
|
return
|
|
471
505
|
if not user_status["onboarded"]:
|
|
506
|
+
console.print("onboarding user")
|
|
472
507
|
onboarding = _Onboarding(console)
|
|
473
508
|
resolved_teamspace = onboarding.select_teamspace(teamspace, org, user)
|
|
474
509
|
cloudspace_id = onboarding.get_cloudspace_id(resolved_teamspace)
|
|
510
|
+
from_onboarding = True
|
|
475
511
|
else:
|
|
476
512
|
resolved_teamspace = select_teamspace(teamspace, org, user)
|
|
477
513
|
|
|
@@ -479,29 +515,43 @@ def _handle_cloud(
|
|
|
479
515
|
lit_cr = LitContainerApi()
|
|
480
516
|
lit_cr.list_containers(resolved_teamspace.id, cloud_account=cloud_account)
|
|
481
517
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
518
|
+
registry_url = _get_registry_url()
|
|
519
|
+
container_basename = repository.split("/")[-1]
|
|
520
|
+
image = (
|
|
521
|
+
f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
|
|
522
|
+
f"{resolved_teamspace.owner.name}/{resolved_teamspace.name}/{container_basename}"
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
if from_onboarding:
|
|
526
|
+
thread = Thread(
|
|
527
|
+
target=ls_deployer.run_on_cloud,
|
|
528
|
+
kwargs={
|
|
529
|
+
"deployment_name": deployment_name,
|
|
530
|
+
"image": image,
|
|
531
|
+
"teamspace": resolved_teamspace,
|
|
532
|
+
"metric": None,
|
|
533
|
+
"machine": machine,
|
|
534
|
+
"spot": interruptible,
|
|
535
|
+
"cloud_account": cloud_account,
|
|
536
|
+
"port": port,
|
|
537
|
+
"min_replica": min_replica,
|
|
538
|
+
"max_replica": max_replica,
|
|
539
|
+
"replicas": replicas,
|
|
540
|
+
"include_credentials": include_credentials,
|
|
541
|
+
"cloudspace_id": cloudspace_id,
|
|
542
|
+
"from_onboarding": from_onboarding,
|
|
543
|
+
},
|
|
544
|
+
)
|
|
545
|
+
thread.start()
|
|
546
|
+
console.print("🚀 Deployment started")
|
|
547
|
+
if not _upload_container(console, ls_deployer, repository, tag, resolved_teamspace, lit_cr, cloud_account):
|
|
548
|
+
thread.join()
|
|
502
549
|
return
|
|
503
|
-
|
|
504
|
-
|
|
550
|
+
thread.join()
|
|
551
|
+
return
|
|
552
|
+
|
|
553
|
+
if not _upload_container(console, ls_deployer, repository, tag, resolved_teamspace, lit_cr, cloud_account):
|
|
554
|
+
return
|
|
505
555
|
|
|
506
556
|
deployment_status = ls_deployer.run_on_cloud(
|
|
507
557
|
deployment_name=deployment_name,
|
|
@@ -517,6 +567,7 @@ def _handle_cloud(
|
|
|
517
567
|
replicas=replicas,
|
|
518
568
|
include_credentials=include_credentials,
|
|
519
569
|
cloudspace_id=cloudspace_id,
|
|
570
|
+
from_onboarding=from_onboarding,
|
|
520
571
|
)
|
|
521
572
|
console.print(f"🚀 Deployment started, access at [i]{deployment_status.get('url')}[/i]")
|
|
522
573
|
if user_status["onboarded"]:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from typing import Any, Dict, List, Optional, Union
|
|
3
4
|
|
|
4
5
|
import requests
|
|
@@ -15,6 +16,7 @@ from lightning_sdk.api.deployment_api import (
|
|
|
15
16
|
ReleaseStrategy,
|
|
16
17
|
Secret,
|
|
17
18
|
TokenAuth,
|
|
19
|
+
compose_commands,
|
|
18
20
|
restore_auth,
|
|
19
21
|
restore_autoscale,
|
|
20
22
|
restore_env,
|
|
@@ -30,6 +32,7 @@ from lightning_sdk.lightning_cloud.openapi import V1Deployment
|
|
|
30
32
|
from lightning_sdk.machine import Machine
|
|
31
33
|
from lightning_sdk.organization import Organization
|
|
32
34
|
from lightning_sdk.services.utilities import _get_cluster
|
|
35
|
+
from lightning_sdk.studio import Studio
|
|
33
36
|
from lightning_sdk.teamspace import Teamspace
|
|
34
37
|
from lightning_sdk.user import User
|
|
35
38
|
from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_org, _resolve_teamspace, _resolve_user
|
|
@@ -55,7 +58,7 @@ class Deployment:
|
|
|
55
58
|
|
|
56
59
|
def __init__(
|
|
57
60
|
self,
|
|
58
|
-
name: str,
|
|
61
|
+
name: Optional[str] = None,
|
|
59
62
|
teamspace: Optional[Union[str, Teamspace]] = None,
|
|
60
63
|
org: Optional[Union[str, Organization]] = None,
|
|
61
64
|
user: Optional[Union[str, User]] = None,
|
|
@@ -72,6 +75,9 @@ class Deployment:
|
|
|
72
75
|
except ConnectionError as e:
|
|
73
76
|
raise e
|
|
74
77
|
|
|
78
|
+
if name is None:
|
|
79
|
+
name = "dep_" + datetime.now().strftime("%m-%d_%H:%M:%S")
|
|
80
|
+
|
|
75
81
|
self._name = name
|
|
76
82
|
self._user = _resolve_user(self._user or user)
|
|
77
83
|
self._org = _resolve_org(org)
|
|
@@ -102,13 +108,15 @@ class Deployment:
|
|
|
102
108
|
|
|
103
109
|
def start(
|
|
104
110
|
self,
|
|
111
|
+
studio: Optional[Union[str, Studio]] = None,
|
|
105
112
|
machine: Optional[Machine] = None,
|
|
106
113
|
image: Optional[str] = None,
|
|
107
114
|
autoscale: Optional[AutoScaleConfig] = None,
|
|
108
|
-
ports: Optional[List[float]] = None,
|
|
115
|
+
ports: Optional[Union[float, List[float]]] = None,
|
|
109
116
|
release_strategy: Optional[ReleaseStrategy] = None,
|
|
110
117
|
entrypoint: Optional[str] = None,
|
|
111
118
|
command: Optional[str] = None,
|
|
119
|
+
commands: Optional[List[str]] = None,
|
|
112
120
|
env: Union[List[Union[Secret, Env]], Dict[str, str], None] = None,
|
|
113
121
|
spot: Optional[bool] = None,
|
|
114
122
|
replicas: Optional[int] = None,
|
|
@@ -120,6 +128,7 @@ class Deployment:
|
|
|
120
128
|
cloudspace_id: Optional[str] = None,
|
|
121
129
|
quantity: Optional[int] = None,
|
|
122
130
|
include_credentials: Optional[bool] = None,
|
|
131
|
+
from_onboarding: Optional[bool] = None,
|
|
123
132
|
) -> None:
|
|
124
133
|
"""The Lightning AI Deployment.
|
|
125
134
|
|
|
@@ -146,6 +155,7 @@ class Deployment:
|
|
|
146
155
|
cloudspace_id: Connect deployment to a Studio.
|
|
147
156
|
quantity: The number of machines per replica to deploy.
|
|
148
157
|
include_credentials: Whether to include the environment variables for the SDK to authenticate
|
|
158
|
+
from_onboarding: Whether the deployment is from onboarding.
|
|
149
159
|
|
|
150
160
|
Note:
|
|
151
161
|
Since a teamspace can either be owned by an org or by a user directly,
|
|
@@ -155,12 +165,45 @@ class Deployment:
|
|
|
155
165
|
if self._is_created:
|
|
156
166
|
raise RuntimeError("This deployment has already been started.")
|
|
157
167
|
|
|
158
|
-
|
|
168
|
+
if isinstance(studio, Studio):
|
|
169
|
+
cloudspace_id = studio._studio.id
|
|
170
|
+
cloud_account = studio._studio.cluster_id
|
|
171
|
+
|
|
172
|
+
if isinstance(studio, str):
|
|
173
|
+
studio = Studio(studio)
|
|
174
|
+
cloudspace_id = studio._studio.id
|
|
175
|
+
cloud_account = studio._studio.cluster_id
|
|
176
|
+
|
|
177
|
+
if cloud_account is None:
|
|
178
|
+
cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
|
|
159
179
|
|
|
160
180
|
if cloud_account is None and self._cloud_account is not None:
|
|
161
181
|
print(f"No cloud account was provided, defaulting to {self._cloud_account.cluster_id}")
|
|
162
182
|
cloud_account = os.getenv("LIGHTNING_CLUSTER_ID") or self._cloud_account.cluster_id
|
|
163
183
|
|
|
184
|
+
if isinstance(ports, float):
|
|
185
|
+
ports = [ports]
|
|
186
|
+
|
|
187
|
+
if replicas is None and autoscale is None:
|
|
188
|
+
replicas = 1
|
|
189
|
+
|
|
190
|
+
if machine is None:
|
|
191
|
+
machine = Machine.CPU
|
|
192
|
+
|
|
193
|
+
if commands is not None and command is not None:
|
|
194
|
+
raise ValueError("Commands and command are mutually exclusive")
|
|
195
|
+
|
|
196
|
+
if commands is not None:
|
|
197
|
+
command = compose_commands(commands)
|
|
198
|
+
|
|
199
|
+
if autoscale is None:
|
|
200
|
+
autoscale = AutoScaleConfig(
|
|
201
|
+
min_replicas=0,
|
|
202
|
+
max_replicas=1,
|
|
203
|
+
metric="CPU" if machine.is_cpu() else "GPU",
|
|
204
|
+
threshold=90,
|
|
205
|
+
)
|
|
206
|
+
|
|
164
207
|
self._deployment = self._deployment_api.create_deployment(
|
|
165
208
|
V1Deployment(
|
|
166
209
|
autoscaling=to_autoscaling(autoscale, replicas),
|
|
@@ -180,9 +223,11 @@ class Deployment:
|
|
|
180
223
|
health_check=health_check,
|
|
181
224
|
quantity=quantity,
|
|
182
225
|
include_credentials=include_credentials if include_credentials is not None else True,
|
|
226
|
+
cloudspace_id=cloudspace_id,
|
|
183
227
|
),
|
|
184
228
|
strategy=to_strategy(release_strategy),
|
|
185
|
-
)
|
|
229
|
+
),
|
|
230
|
+
from_onboarding=from_onboarding,
|
|
186
231
|
)
|
|
187
232
|
|
|
188
233
|
# Overrides the name
|
|
@@ -196,6 +241,7 @@ class Deployment:
|
|
|
196
241
|
image: Optional[str] = None,
|
|
197
242
|
entrypoint: Optional[str] = None,
|
|
198
243
|
command: Optional[str] = None,
|
|
244
|
+
commands: Optional[List[str]] = None,
|
|
199
245
|
env: Optional[List[Union[Env, Secret]]] = None,
|
|
200
246
|
spot: Optional[bool] = None,
|
|
201
247
|
cloud_account: Optional[str] = None,
|
|
@@ -215,6 +261,9 @@ class Deployment:
|
|
|
215
261
|
) -> None:
|
|
216
262
|
cloud_account = _resolve_deprecated_cluster(cloud_account, cluster)
|
|
217
263
|
|
|
264
|
+
if command is None and commands is not None:
|
|
265
|
+
command = compose_commands(commands)
|
|
266
|
+
|
|
218
267
|
self._deployment = self._deployment_api.update_deployment(
|
|
219
268
|
self._deployment,
|
|
220
269
|
name=name or self._name,
|
|
@@ -225,8 +274,8 @@ class Deployment:
|
|
|
225
274
|
cloud_account=cloud_account,
|
|
226
275
|
machine=machine,
|
|
227
276
|
image=image,
|
|
228
|
-
entrypoint=entrypoint,
|
|
229
|
-
command=command,
|
|
277
|
+
entrypoint=entrypoint or "",
|
|
278
|
+
command=command or "",
|
|
230
279
|
ports=ports,
|
|
231
280
|
custom_domain=custom_domain,
|
|
232
281
|
auth=auth,
|
|
@@ -104,7 +104,6 @@ from lightning_sdk.lightning_cloud.openapi.models.datasets_id_body import Datase
|
|
|
104
104
|
from lightning_sdk.lightning_cloud.openapi.models.deployments_id_body import DeploymentsIdBody
|
|
105
105
|
from lightning_sdk.lightning_cloud.openapi.models.deploymenttemplates_id_body import DeploymenttemplatesIdBody
|
|
106
106
|
from lightning_sdk.lightning_cloud.openapi.models.endpoints_id_body import EndpointsIdBody
|
|
107
|
-
from lightning_sdk.lightning_cloud.openapi.models.environmenttemplates_id_body import EnvironmenttemplatesIdBody
|
|
108
107
|
from lightning_sdk.lightning_cloud.openapi.models.experiment_name_variant_name_body import ExperimentNameVariantNameBody
|
|
109
108
|
from lightning_sdk.lightning_cloud.openapi.models.externalv1_cloud_space_instance_status import Externalv1CloudSpaceInstanceStatus
|
|
110
109
|
from lightning_sdk.lightning_cloud.openapi.models.externalv1_cluster import Externalv1Cluster
|
|
@@ -214,6 +213,7 @@ from lightning_sdk.lightning_cloud.openapi.models.stream_result_of_v1_conversati
|
|
|
214
213
|
from lightning_sdk.lightning_cloud.openapi.models.stream_result_of_v1_get_long_running_command_in_cloud_space_response import StreamResultOfV1GetLongRunningCommandInCloudSpaceResponse
|
|
215
214
|
from lightning_sdk.lightning_cloud.openapi.models.studioapp_jobs_body import StudioappJobsBody
|
|
216
215
|
from lightning_sdk.lightning_cloud.openapi.models.update import Update
|
|
216
|
+
from lightning_sdk.lightning_cloud.openapi.models.update1 import Update1
|
|
217
217
|
from lightning_sdk.lightning_cloud.openapi.models.upload_id_complete_body import UploadIdCompleteBody
|
|
218
218
|
from lightning_sdk.lightning_cloud.openapi.models.upload_id_complete_body1 import UploadIdCompleteBody1
|
|
219
219
|
from lightning_sdk.lightning_cloud.openapi.models.upload_id_parts_body import UploadIdPartsBody
|
|
@@ -556,8 +556,10 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_job_health_check_config imp
|
|
|
556
556
|
from lightning_sdk.lightning_cloud.openapi.models.v1_job_log_entry import V1JobLogEntry
|
|
557
557
|
from lightning_sdk.lightning_cloud.openapi.models.v1_job_logs_page import V1JobLogsPage
|
|
558
558
|
from lightning_sdk.lightning_cloud.openapi.models.v1_job_logs_response import V1JobLogsResponse
|
|
559
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_job_resource import V1JobResource
|
|
559
560
|
from lightning_sdk.lightning_cloud.openapi.models.v1_job_spec import V1JobSpec
|
|
560
561
|
from lightning_sdk.lightning_cloud.openapi.models.v1_job_timing import V1JobTiming
|
|
562
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_job_type import V1JobType
|
|
561
563
|
from lightning_sdk.lightning_cloud.openapi.models.v1_joinable_organization import V1JoinableOrganization
|
|
562
564
|
from lightning_sdk.lightning_cloud.openapi.models.v1_keep_alive_cloud_space_instance_response import V1KeepAliveCloudSpaceInstanceResponse
|
|
563
565
|
from lightning_sdk.lightning_cloud.openapi.models.v1_knowledge_configuration import V1KnowledgeConfiguration
|
|
@@ -624,6 +626,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_list_filesystem_snowflake_r
|
|
|
624
626
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_gallery_components_response import V1ListGalleryComponentsResponse
|
|
625
627
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_gallery_lightningapps_response import V1ListGalleryLightningappsResponse
|
|
626
628
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_job_files_response import V1ListJobFilesResponse
|
|
629
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_list_job_resources_response import V1ListJobResourcesResponse
|
|
627
630
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_jobs_response import V1ListJobsResponse
|
|
628
631
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_joinable_organizations_response import V1ListJoinableOrganizationsResponse
|
|
629
632
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_lightning_run_response import V1ListLightningRunResponse
|
|
@@ -788,6 +791,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_report_restart_timings_resp
|
|
|
788
791
|
from lightning_sdk.lightning_cloud.openapi.models.v1_request_cluster_access_request import V1RequestClusterAccessRequest
|
|
789
792
|
from lightning_sdk.lightning_cloud.openapi.models.v1_request_cluster_access_response import V1RequestClusterAccessResponse
|
|
790
793
|
from lightning_sdk.lightning_cloud.openapi.models.v1_request_verification_code_response import V1RequestVerificationCodeResponse
|
|
794
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_reservation_billing_session import V1ReservationBillingSession
|
|
791
795
|
from lightning_sdk.lightning_cloud.openapi.models.v1_reservation_details import V1ReservationDetails
|
|
792
796
|
from lightning_sdk.lightning_cloud.openapi.models.v1_resource_tag import V1ResourceTag
|
|
793
797
|
from lightning_sdk.lightning_cloud.openapi.models.v1_resource_visibility import V1ResourceVisibility
|