lightning-sdk 0.2.9__py3-none-any.whl → 0.2.11__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 +3 -0
- lightning_sdk/api/lit_container_api.py +19 -2
- lightning_sdk/api/teamspace_api.py +47 -18
- lightning_sdk/api/utils.py +1 -1
- lightning_sdk/cli/entrypoint.py +2 -2
- lightning_sdk/cli/serve.py +143 -16
- lightning_sdk/cli/upload.py +4 -1
- lightning_sdk/deployment/deployment.py +9 -3
- lightning_sdk/lightning_cloud/openapi/__init__.py +5 -0
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +98 -1
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +5 -0
- lightning_sdk/lightning_cloud/openapi/models/cluster_id_capacityreservations_body.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/create.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/update.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_cold_start_metrics_stats.py +357 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template_config.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_cloudflare_v1.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_accelerator.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_capacity_reservation.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_cloud_space_environment_template_request.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_create_cluster_capacity_reservation_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_gcp_direct_vpc.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_cold_start_metrics_stats_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +17 -17
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_project_cluster_binding.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_r2_data_connection.py +253 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +50 -24
- lightning_sdk/lightning_cloud/openapi/models/v1_validate_data_connection_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_weka_data_connection.py +29 -55
- lightning_sdk/lightning_cloud/openapi/models/validate.py +27 -1
- lightning_sdk/lit_container.py +12 -2
- lightning_sdk/models.py +38 -9
- lightning_sdk/serve.py +6 -0
- lightning_sdk/teamspace.py +16 -2
- lightning_sdk/utils/resolve.py +11 -4
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/RECORD +50 -45
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
|
@@ -224,10 +224,12 @@ 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,
|
|
230
231
|
body=CreateDeploymentRequestDefinesASpecForTheJobThatAllowsForAutoscalingJobs(
|
|
232
|
+
cloudspace_id=deployment.cloudspace_id,
|
|
231
233
|
autoscaling=deployment.autoscaling,
|
|
232
234
|
cluster_id=deployment.spec.cluster_id,
|
|
233
235
|
endpoint=deployment.endpoint,
|
|
@@ -235,6 +237,7 @@ class DeploymentApi:
|
|
|
235
237
|
replicas=deployment.replicas,
|
|
236
238
|
spec=deployment.spec,
|
|
237
239
|
strategy=deployment.strategy,
|
|
240
|
+
from_onboarding=from_onboarding,
|
|
238
241
|
),
|
|
239
242
|
)
|
|
240
243
|
|
|
@@ -7,6 +7,7 @@ import requests
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
|
|
9
9
|
from lightning_sdk.api.utils import _get_registry_url
|
|
10
|
+
from lightning_sdk.lightning_cloud.env import LIGHTNING_CLOUD_URL
|
|
10
11
|
from lightning_sdk.lightning_cloud.openapi.models import V1DeleteLitRepositoryResponse
|
|
11
12
|
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
12
13
|
from lightning_sdk.teamspace import Teamspace
|
|
@@ -126,8 +127,14 @@ class LitContainerApi:
|
|
|
126
127
|
|
|
127
128
|
@retry_on_lcr_auth_failure
|
|
128
129
|
def upload_container(
|
|
129
|
-
self,
|
|
130
|
-
|
|
130
|
+
self,
|
|
131
|
+
container: str,
|
|
132
|
+
teamspace: Teamspace,
|
|
133
|
+
tag: str,
|
|
134
|
+
cloud_account: str,
|
|
135
|
+
platform: str,
|
|
136
|
+
return_final_dict: bool = False,
|
|
137
|
+
) -> Generator[dict, None, Dict]:
|
|
131
138
|
"""Upload container will push the container to LitCR.
|
|
132
139
|
|
|
133
140
|
It uses docker push API to interact with docker daemon which will then push the container to a storage
|
|
@@ -140,6 +147,7 @@ class LitContainerApi:
|
|
|
140
147
|
Named cloud-account in the CLI options.
|
|
141
148
|
:param platform: If empty will be linux/amd64. This is important because our entire deployment infra runs on
|
|
142
149
|
linux/amd64. Will show user a warning otherwise.
|
|
150
|
+
:return_final_dict: Controls whether we respond with the dictionary containing metadata about container upload
|
|
143
151
|
:return: Generator[dict, None, dict]
|
|
144
152
|
"""
|
|
145
153
|
try:
|
|
@@ -166,6 +174,15 @@ class LitContainerApi:
|
|
|
166
174
|
raise ValueError(f"Could not tag container {container}:{tag} with {repository}:{tag}")
|
|
167
175
|
yield from self._push_with_retry(repository, tag=tag)
|
|
168
176
|
|
|
177
|
+
if return_final_dict:
|
|
178
|
+
yield {
|
|
179
|
+
"finish": True,
|
|
180
|
+
"url": f"{LIGHTNING_CLOUD_URL}/{teamspace.owner.name}/{teamspace.name}/containers/"
|
|
181
|
+
f"{container_basename}?section=tags"
|
|
182
|
+
f"{f'?clusterId={cloud_account}' if cloud_account is not None else ''}",
|
|
183
|
+
"repository": repository,
|
|
184
|
+
}
|
|
185
|
+
|
|
169
186
|
def _push_with_retry(self, repository: str, tag: str, max_retries: int = 3) -> Iterator[Dict[str, Any]]:
|
|
170
187
|
def is_auth_error(error_msg: str) -> bool:
|
|
171
188
|
auth_errors = ["unauthorized", "authentication required", "unauth"]
|
|
@@ -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,
|
|
@@ -34,7 +35,7 @@ class TeamspaceApi:
|
|
|
34
35
|
|
|
35
36
|
def __init__(self) -> None:
|
|
36
37
|
self._client = LightningClient(max_tries=7)
|
|
37
|
-
self.
|
|
38
|
+
self._models_api: Optional[ModelsStoreApi] = None
|
|
38
39
|
|
|
39
40
|
def get_teamspace(self, name: str, owner_id: str) -> V1Project:
|
|
40
41
|
"""Get the current teamspace from the owner."""
|
|
@@ -166,12 +167,12 @@ class TeamspaceApi:
|
|
|
166
167
|
|
|
167
168
|
# lazy property which is only created when needed
|
|
168
169
|
@property
|
|
169
|
-
def
|
|
170
|
-
if not self.
|
|
171
|
-
self.
|
|
172
|
-
return self.
|
|
170
|
+
def models_api(self) -> ModelsStoreApi:
|
|
171
|
+
if not self._models_api:
|
|
172
|
+
self._models_api = ModelsStoreApi(self._client.api_client)
|
|
173
|
+
return self._models_api
|
|
173
174
|
|
|
174
|
-
def get_model_version(self, name: str, version: str, teamspace_id: str) -> V1ModelVersionArchive:
|
|
175
|
+
def get_model_version(self, name: str, version: Optional[str], teamspace_id: str) -> V1ModelVersionArchive:
|
|
175
176
|
return _get_model_version(client=self._client, name=name, version=version, teamspace_id=teamspace_id)
|
|
176
177
|
|
|
177
178
|
def create_model(
|
|
@@ -184,14 +185,14 @@ class TeamspaceApi:
|
|
|
184
185
|
cloud_account: str,
|
|
185
186
|
) -> V1ModelVersionArchive:
|
|
186
187
|
# ask if such model already exists by listing models with specific name
|
|
187
|
-
models = self.
|
|
188
|
+
models = self.models_api.models_store_list_models(project_id=teamspace_id, name=name).models
|
|
188
189
|
if len(models) == 0:
|
|
189
|
-
return self.
|
|
190
|
+
return self.models_api.models_store_create_model(
|
|
190
191
|
body=ProjectIdModelsBody(cluster_id=cloud_account, metadata=metadata, name=name, private=private),
|
|
191
192
|
project_id=teamspace_id,
|
|
192
193
|
)
|
|
193
194
|
assert len(models) == 1, "Multiple models with the same name found"
|
|
194
|
-
return self.
|
|
195
|
+
return self.models_api.models_store_create_model_version(
|
|
195
196
|
body=ModelIdVersionsBody(cluster_id=cloud_account, version=version),
|
|
196
197
|
project_id=teamspace_id,
|
|
197
198
|
model_id=models[0].id,
|
|
@@ -199,16 +200,16 @@ 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_id = models[0].id
|
|
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":
|
|
208
|
-
version =
|
|
209
|
-
self.
|
|
207
|
+
version = model.default_version
|
|
208
|
+
self.models_api.models_store_delete_model_version(
|
|
209
|
+
project_id=teamspace_id, model_id=model.id, version=version
|
|
210
|
+
)
|
|
210
211
|
else:
|
|
211
|
-
self.
|
|
212
|
+
self.models_api.models_store_delete_model(project_id=teamspace_id, model_id=model.id)
|
|
212
213
|
|
|
213
214
|
def upload_model_file(
|
|
214
215
|
self,
|
|
@@ -255,8 +256,8 @@ class TeamspaceApi:
|
|
|
255
256
|
if main_pbar:
|
|
256
257
|
main_pbar.update(1)
|
|
257
258
|
|
|
258
|
-
def
|
|
259
|
-
self.
|
|
259
|
+
def _complete_model_upload(self, model_id: str, version: str, teamspace_id: str) -> None:
|
|
260
|
+
self.models_api.models_store_complete_model_upload(
|
|
260
261
|
body=_DummyBody(),
|
|
261
262
|
project_id=teamspace_id,
|
|
262
263
|
model_id=model_id,
|
|
@@ -266,12 +267,14 @@ class TeamspaceApi:
|
|
|
266
267
|
def download_model_files(
|
|
267
268
|
self,
|
|
268
269
|
name: str,
|
|
269
|
-
version: str,
|
|
270
|
+
version: Optional[str],
|
|
270
271
|
download_dir: Path,
|
|
271
272
|
teamspace_name: str,
|
|
272
273
|
teamspace_owner_name: str,
|
|
273
274
|
progress_bar: bool = True,
|
|
274
275
|
) -> List[str]:
|
|
276
|
+
if version is None:
|
|
277
|
+
version = "default"
|
|
275
278
|
return _download_model_files(
|
|
276
279
|
client=self._client,
|
|
277
280
|
teamspace_name=teamspace_name,
|
|
@@ -302,3 +305,29 @@ class TeamspaceApi:
|
|
|
302
305
|
project_id=teamspace_id, id=cloud_account
|
|
303
306
|
)
|
|
304
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/api/utils.py
CHANGED
|
@@ -493,7 +493,7 @@ def _get_model_version(client: LightningClient, teamspace_id: str, name: str, ve
|
|
|
493
493
|
raise ValueError(f"Model `{name}` does not exist")
|
|
494
494
|
elif len(models) > 1:
|
|
495
495
|
raise ValueError("Multiple models with the same name found")
|
|
496
|
-
if version ==
|
|
496
|
+
if version is None or version == "default":
|
|
497
497
|
return models[0].default_version
|
|
498
498
|
versions = api.models_store_list_model_versions(project_id=teamspace_id, model_id=models[0].id).versions
|
|
499
499
|
if not versions:
|
lightning_sdk/cli/entrypoint.py
CHANGED
|
@@ -21,7 +21,7 @@ from lightning_sdk.cli.inspect import inspect
|
|
|
21
21
|
from lightning_sdk.cli.list import list_cli
|
|
22
22
|
from lightning_sdk.cli.open import open
|
|
23
23
|
from lightning_sdk.cli.run import run
|
|
24
|
-
from lightning_sdk.cli.serve import
|
|
24
|
+
from lightning_sdk.cli.serve import deploy
|
|
25
25
|
from lightning_sdk.cli.start import start
|
|
26
26
|
from lightning_sdk.cli.stop import stop
|
|
27
27
|
from lightning_sdk.cli.switch import switch
|
|
@@ -76,7 +76,7 @@ main_cli.add_command(generate)
|
|
|
76
76
|
main_cli.add_command(inspect)
|
|
77
77
|
main_cli.add_command(list_cli)
|
|
78
78
|
main_cli.add_command(run)
|
|
79
|
-
main_cli.add_command(
|
|
79
|
+
main_cli.add_command(deploy)
|
|
80
80
|
main_cli.add_command(start)
|
|
81
81
|
main_cli.add_command(stop)
|
|
82
82
|
main_cli.add_command(switch)
|
lightning_sdk/cli/serve.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import socket
|
|
2
3
|
import subprocess
|
|
3
4
|
import time
|
|
4
5
|
import webbrowser
|
|
5
6
|
from datetime import datetime
|
|
7
|
+
from enum import Enum
|
|
6
8
|
from pathlib import Path
|
|
7
|
-
from typing import Optional, Union
|
|
9
|
+
from typing import List, Optional, TypedDict, Union
|
|
8
10
|
from urllib.parse import urlencode
|
|
9
11
|
|
|
10
12
|
import click
|
|
@@ -18,10 +20,13 @@ from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
|
18
20
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
19
21
|
from lightning_sdk.lightning_cloud import env
|
|
20
22
|
from lightning_sdk.lightning_cloud.login import Auth, AuthServer
|
|
23
|
+
from lightning_sdk.lightning_cloud.openapi import V1CloudSpace
|
|
24
|
+
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
21
25
|
from lightning_sdk.serve import _LitServeDeployer
|
|
22
26
|
from lightning_sdk.utils.resolve import _get_authed_user, _resolve_teamspace
|
|
23
27
|
|
|
24
28
|
_MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
29
|
+
_POLL_TIMEOUT = 600
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
class _ServeGroup(click.Group):
|
|
@@ -33,22 +38,22 @@ class _ServeGroup(click.Group):
|
|
|
33
38
|
return super().parse_args(ctx, args)
|
|
34
39
|
|
|
35
40
|
|
|
36
|
-
@click.group("
|
|
37
|
-
def
|
|
38
|
-
"""
|
|
41
|
+
@click.group("deploy", cls=_ServeGroup)
|
|
42
|
+
def deploy() -> None:
|
|
43
|
+
"""Deploy a LitServe model.
|
|
39
44
|
|
|
40
45
|
Example:
|
|
41
|
-
lightning
|
|
46
|
+
lightning deploy server.py # deploy to the cloud
|
|
42
47
|
|
|
43
48
|
Example:
|
|
44
|
-
lightning
|
|
49
|
+
lightning deploy server.py --local # run locally
|
|
45
50
|
|
|
46
|
-
You can deploy the API to the cloud by running `lightning
|
|
51
|
+
You can deploy the API to the cloud by running `lightning deploy server.py`.
|
|
47
52
|
This will build a docker container for the server.py script and deploy it to the Lightning AI platform.
|
|
48
53
|
"""
|
|
49
54
|
|
|
50
55
|
|
|
51
|
-
@
|
|
56
|
+
@deploy.command("api")
|
|
52
57
|
@click.argument("script-path", type=click.Path(exists=True))
|
|
53
58
|
@click.option(
|
|
54
59
|
"--easy",
|
|
@@ -272,20 +277,125 @@ def select_teamspace(teamspace: Optional[str], org: Optional[str], user: Optiona
|
|
|
272
277
|
return _resolve_teamspace(teamspace=teamspace, org=org, user=user)
|
|
273
278
|
|
|
274
279
|
|
|
275
|
-
|
|
280
|
+
class _UserStatus(TypedDict):
|
|
281
|
+
verified: bool
|
|
282
|
+
onboarded: bool
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def poll_verified_status(timeout: int = _POLL_TIMEOUT) -> _UserStatus:
|
|
276
286
|
"""Polls the verified status of the user until it is True or a timeout occurs."""
|
|
277
287
|
user_api = UserApi()
|
|
278
288
|
user = _get_authed_user()
|
|
279
289
|
start_time = datetime.now()
|
|
280
|
-
|
|
290
|
+
result = {"onboarded": False, "verified": False}
|
|
281
291
|
while True:
|
|
282
292
|
user_resp = user_api.get_user(name=user.name)
|
|
293
|
+
result["onboarded"] = user_resp.status.completed_project_onboarding
|
|
294
|
+
result["verified"] = user_resp.status.verified
|
|
283
295
|
if user_resp.status.verified:
|
|
284
|
-
return
|
|
296
|
+
return result
|
|
285
297
|
if (datetime.now() - start_time).total_seconds() > timeout:
|
|
286
298
|
break
|
|
287
299
|
time.sleep(5)
|
|
288
|
-
return
|
|
300
|
+
return result
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class _OnboardingStatus(Enum):
|
|
304
|
+
NOT_VERIFIED = "not_verified"
|
|
305
|
+
ONBOARDING = "onboarding"
|
|
306
|
+
ONBOARDED = "onboarded"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class _Onboarding:
|
|
310
|
+
def __init__(self, console: Console) -> None:
|
|
311
|
+
self.console = console
|
|
312
|
+
self.user = _get_authed_user()
|
|
313
|
+
self.user_api = UserApi()
|
|
314
|
+
self.client = LightningClient(max_tries=7)
|
|
315
|
+
|
|
316
|
+
@property
|
|
317
|
+
def verified(self) -> bool:
|
|
318
|
+
return self.user_api.get_user(name=self.user.name).status.verified
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def is_onboarded(self) -> bool:
|
|
322
|
+
return self.user_api.get_user(name=self.user.name).status.completed_project_onboarding
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def can_join_org(self) -> bool:
|
|
326
|
+
return len(self.client.organizations_service_list_joinable_organizations().joinable_organizations) > 0
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def status(self) -> _OnboardingStatus:
|
|
330
|
+
if not self.verified:
|
|
331
|
+
return _OnboardingStatus.NOT_VERIFIED
|
|
332
|
+
if self.is_onboarded:
|
|
333
|
+
return _OnboardingStatus.ONBOARDED
|
|
334
|
+
return _OnboardingStatus.ONBOARDING
|
|
335
|
+
|
|
336
|
+
def _wait_user_onboarding(self, timeout: int = _POLL_TIMEOUT) -> None:
|
|
337
|
+
"""Wait for user onboarding if they can join the teamspace otherwise move to select a teamspace."""
|
|
338
|
+
status = self.status
|
|
339
|
+
if status == _OnboardingStatus.ONBOARDED:
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
self.console.print("Waiting for account setup. Visit lightning.ai")
|
|
343
|
+
start_time = datetime.now()
|
|
344
|
+
while self.status != _OnboardingStatus.ONBOARDED:
|
|
345
|
+
time.sleep(5)
|
|
346
|
+
if self.is_onboarded:
|
|
347
|
+
return
|
|
348
|
+
if (datetime.now() - start_time).total_seconds() > timeout:
|
|
349
|
+
break
|
|
350
|
+
|
|
351
|
+
raise RuntimeError("Timed out waiting for onboarding status")
|
|
352
|
+
|
|
353
|
+
def get_cloudspace_id(self, teamspace: Teamspace) -> Optional[str]:
|
|
354
|
+
cloudspaces: List[V1CloudSpace] = self.client.cloud_space_service_list_cloud_spaces(teamspace.id).cloudspaces
|
|
355
|
+
cloudspaces = sorted(cloudspaces, key=lambda cloudspace: cloudspace.created_at, reverse=True)
|
|
356
|
+
if len(cloudspaces) == 0:
|
|
357
|
+
raise RuntimeError("Error creating deployment! Finish account setup at lightning.ai first.")
|
|
358
|
+
# get the first cloudspace
|
|
359
|
+
cloudspace = cloudspaces[0]
|
|
360
|
+
if "scratch-studio" in cloudspace.name or "scratch-studio" in cloudspace.display_name:
|
|
361
|
+
return cloudspace.id
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
def select_teamspace(self, teamspace: Optional[str], org: Optional[str], user: Optional[str]) -> Teamspace:
|
|
365
|
+
"""Select a teamspace while onboarding.
|
|
366
|
+
|
|
367
|
+
If user is being onboarded and can't join any org, the teamspace it will be resolved to the default
|
|
368
|
+
personal teamspace.
|
|
369
|
+
If user is being onboarded and can join an org then it will select default teamspace from the org.
|
|
370
|
+
"""
|
|
371
|
+
if self.is_onboarded:
|
|
372
|
+
return select_teamspace(teamspace, org, user)
|
|
373
|
+
|
|
374
|
+
# Run only when user hasn't completed onboarding yet.
|
|
375
|
+
menu = _TeamspacesMenu()
|
|
376
|
+
self._wait_user_onboarding()
|
|
377
|
+
# Onboarding has been completed - user already selected organization if they could
|
|
378
|
+
possible_teamspaces = menu._get_possible_teamspaces(self.user)
|
|
379
|
+
if len(possible_teamspaces) == 1:
|
|
380
|
+
# User didn't select any org
|
|
381
|
+
value = next(iter(possible_teamspaces.values()))
|
|
382
|
+
return Teamspace(name=value["name"], org=value["org"], user=value["user"])
|
|
383
|
+
|
|
384
|
+
for _, value in possible_teamspaces.items():
|
|
385
|
+
# User select an org
|
|
386
|
+
# Onboarding teamspace will be the default teamspace in the selected org
|
|
387
|
+
if value["org"]:
|
|
388
|
+
return Teamspace(name=value["name"], org=value["org"], user=value["user"])
|
|
389
|
+
raise RuntimeError("Unable to select teamspace. Visit lightning.ai")
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def is_connected(host: str = "8.8.8.8", port: int = 53, timeout: int = 10) -> bool:
|
|
393
|
+
try:
|
|
394
|
+
socket.setdefaulttimeout(timeout)
|
|
395
|
+
socket.create_connection((host, port))
|
|
396
|
+
return True
|
|
397
|
+
except OSError:
|
|
398
|
+
return False
|
|
289
399
|
|
|
290
400
|
|
|
291
401
|
def _handle_cloud(
|
|
@@ -306,6 +416,11 @@ def _handle_cloud(
|
|
|
306
416
|
replicas: Optional[int] = 1,
|
|
307
417
|
include_credentials: Optional[bool] = True,
|
|
308
418
|
) -> None:
|
|
419
|
+
if not is_connected():
|
|
420
|
+
console.print("❌ Internet connection required to deploy to the cloud.", style="red")
|
|
421
|
+
console.print("To run locally instead, use: `lightning serve [SCRIPT | server.py] --local`")
|
|
422
|
+
return
|
|
423
|
+
|
|
309
424
|
deployment_name = os.path.basename(repository)
|
|
310
425
|
tag = tag if tag else "latest"
|
|
311
426
|
|
|
@@ -346,11 +461,20 @@ def _handle_cloud(
|
|
|
346
461
|
console.print("\nPushing container to registry. It may take a while...", style="bold")
|
|
347
462
|
# Authenticate with LitServe affiliate
|
|
348
463
|
authenticate(shall_confirm=not non_interactive)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
464
|
+
user_status = poll_verified_status()
|
|
465
|
+
cloudspace_id: Optional[str] = None
|
|
466
|
+
from_onboarding = False
|
|
467
|
+
if not user_status["verified"]:
|
|
352
468
|
console.print("❌ Verify phone number to continue. Visit lightning.ai.", style="red")
|
|
353
469
|
return
|
|
470
|
+
if not user_status["onboarded"]:
|
|
471
|
+
console.print("onboarding user")
|
|
472
|
+
onboarding = _Onboarding(console)
|
|
473
|
+
resolved_teamspace = onboarding.select_teamspace(teamspace, org, user)
|
|
474
|
+
cloudspace_id = onboarding.get_cloudspace_id(resolved_teamspace)
|
|
475
|
+
from_onboarding = True
|
|
476
|
+
else:
|
|
477
|
+
resolved_teamspace = select_teamspace(teamspace, org, user)
|
|
354
478
|
|
|
355
479
|
# list containers to create the project if it doesn't exist
|
|
356
480
|
lit_cr = LitContainerApi()
|
|
@@ -393,6 +517,9 @@ def _handle_cloud(
|
|
|
393
517
|
max_replica=max_replica,
|
|
394
518
|
replicas=replicas,
|
|
395
519
|
include_credentials=include_credentials,
|
|
520
|
+
cloudspace_id=cloudspace_id,
|
|
521
|
+
from_onboarding=from_onboarding,
|
|
396
522
|
)
|
|
397
523
|
console.print(f"🚀 Deployment started, access at [i]{deployment_status.get('url')}[/i]")
|
|
398
|
-
|
|
524
|
+
if user_status["onboarded"]:
|
|
525
|
+
webbrowser.open(deployment_status.get("url"))
|
lightning_sdk/cli/upload.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import concurrent.futures
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
import webbrowser
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Dict, Generator, List, Optional
|
|
6
7
|
|
|
@@ -162,7 +163,7 @@ def upload_container(
|
|
|
162
163
|
# let the push with retry take control of auth moving forward
|
|
163
164
|
pass
|
|
164
165
|
|
|
165
|
-
lines = api.upload_container(container, teamspace, tag, cloud_account, platform)
|
|
166
|
+
lines = api.upload_container(container, teamspace, tag, cloud_account, platform, return_final_dict=True)
|
|
166
167
|
_print_docker_push(lines, console, progress, push_task)
|
|
167
168
|
except DockerNotRunningError as e:
|
|
168
169
|
e.print_help()
|
|
@@ -303,6 +304,8 @@ def _print_docker_push(lines: Generator, console: Console, progress: Progress, p
|
|
|
303
304
|
console.print(f"\n[red]{line}[/red]")
|
|
304
305
|
return
|
|
305
306
|
elif "finish" in line:
|
|
307
|
+
if "url" in line:
|
|
308
|
+
webbrowser.open(line["url"])
|
|
306
309
|
console.print(f"Container available at [i]{line['url']}[/i]")
|
|
307
310
|
return
|
|
308
311
|
else:
|
|
@@ -117,8 +117,10 @@ class Deployment:
|
|
|
117
117
|
cloud_account: Optional[str] = None,
|
|
118
118
|
custom_domain: Optional[str] = None,
|
|
119
119
|
cluster: Optional[str] = None, # deprecated in favor of cloud_account
|
|
120
|
+
cloudspace_id: Optional[str] = None,
|
|
120
121
|
quantity: Optional[int] = None,
|
|
121
122
|
include_credentials: Optional[bool] = None,
|
|
123
|
+
from_onboarding: Optional[bool] = None,
|
|
122
124
|
) -> None:
|
|
123
125
|
"""The Lightning AI Deployment.
|
|
124
126
|
|
|
@@ -141,9 +143,11 @@ class Deployment:
|
|
|
141
143
|
auth: The auth config to protect your services. Only Basic and Token supported.
|
|
142
144
|
cloud_account: The name of the cloud account, the studio should be created on.
|
|
143
145
|
Doesn't matter when the studio already exists.
|
|
144
|
-
custom_domain: Whether your service would be referenced under a custom
|
|
146
|
+
custom_domain: Whether your service would be referenced under a custom domain.
|
|
147
|
+
cloudspace_id: Connect deployment to a Studio.
|
|
145
148
|
quantity: The number of machines per replica to deploy.
|
|
146
|
-
include_credentials: Whether to include the
|
|
149
|
+
include_credentials: Whether to include the environment variables for the SDK to authenticate
|
|
150
|
+
from_onboarding: Whether the deployment is from onboarding.
|
|
147
151
|
|
|
148
152
|
Note:
|
|
149
153
|
Since a teamspace can either be owned by an org or by a user directly,
|
|
@@ -166,6 +170,7 @@ class Deployment:
|
|
|
166
170
|
name=self._name,
|
|
167
171
|
project_id=self._teamspace.id,
|
|
168
172
|
replicas=replicas,
|
|
173
|
+
cloudspace_id=cloudspace_id,
|
|
169
174
|
spec=to_spec(
|
|
170
175
|
cloud_account=cloud_account,
|
|
171
176
|
command=command,
|
|
@@ -179,7 +184,8 @@ class Deployment:
|
|
|
179
184
|
include_credentials=include_credentials if include_credentials is not None else True,
|
|
180
185
|
),
|
|
181
186
|
strategy=to_strategy(release_strategy),
|
|
182
|
-
)
|
|
187
|
+
),
|
|
188
|
+
from_onboarding=from_onboarding,
|
|
183
189
|
)
|
|
184
190
|
|
|
185
191
|
# Overrides the name
|
|
@@ -278,6 +278,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event_
|
|
|
278
278
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version import V1CloudSpaceCodeVersion
|
|
279
279
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version_status import V1CloudSpaceCodeVersionStatus
|
|
280
280
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_cold_start_metrics import V1CloudSpaceColdStartMetrics
|
|
281
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_cold_start_metrics_stats import V1CloudSpaceColdStartMetricsStats
|
|
281
282
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_engagement_response import V1CloudSpaceEngagementResponse
|
|
282
283
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template import V1CloudSpaceEnvironmentTemplate
|
|
283
284
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template_config import V1CloudSpaceEnvironmentTemplateConfig
|
|
@@ -293,6 +294,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_session import
|
|
|
293
294
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_state import V1CloudSpaceState
|
|
294
295
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_version import V1CloudSpaceVersion
|
|
295
296
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_version_publication import V1CloudSpaceVersionPublication
|
|
297
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_cloudflare_v1 import V1CloudflareV1
|
|
296
298
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_accelerator import V1ClusterAccelerator
|
|
297
299
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_availability import V1ClusterAvailability
|
|
298
300
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cluster_capacity_reservation import V1ClusterCapacityReservation
|
|
@@ -484,6 +486,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_filesystem_work import V1Fi
|
|
|
484
486
|
from lightning_sdk.lightning_cloud.openapi.models.v1_find_capacity_block_offering_response import V1FindCapacityBlockOfferingResponse
|
|
485
487
|
from lightning_sdk.lightning_cloud.openapi.models.v1_flowserver import V1Flowserver
|
|
486
488
|
from lightning_sdk.lightning_cloud.openapi.models.v1_folder_index_status import V1FolderIndexStatus
|
|
489
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_gcp_direct_vpc import V1GCPDirectVPC
|
|
487
490
|
from lightning_sdk.lightning_cloud.openapi.models.v1_gcs_folder_data_connection import V1GCSFolderDataConnection
|
|
488
491
|
from lightning_sdk.lightning_cloud.openapi.models.v1_gpu_system_metrics import V1GPUSystemMetrics
|
|
489
492
|
from lightning_sdk.lightning_cloud.openapi.models.v1_gallery_app import V1GalleryApp
|
|
@@ -495,6 +498,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_get_affiliate_link_response
|
|
|
495
498
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_agent_job_env_response import V1GetAgentJobEnvResponse
|
|
496
499
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_agent_job_logs_metadata_response import V1GetAgentJobLogsMetadataResponse
|
|
497
500
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_artifacts_page_response import V1GetArtifactsPageResponse
|
|
501
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_cold_start_metrics_stats_response import V1GetCloudSpaceColdStartMetricsStatsResponse
|
|
498
502
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_instance_status_response import V1GetCloudSpaceInstanceStatusResponse
|
|
499
503
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_instance_system_metrics_aggregate_response import V1GetCloudSpaceInstanceSystemMetricsAggregateResponse
|
|
500
504
|
from lightning_sdk.lightning_cloud.openapi.models.v1_get_cloud_space_size_response import V1GetCloudSpaceSizeResponse
|
|
@@ -770,6 +774,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_quest_status import V1Quest
|
|
|
770
774
|
from lightning_sdk.lightning_cloud.openapi.models.v1_queue_server_type import V1QueueServerType
|
|
771
775
|
from lightning_sdk.lightning_cloud.openapi.models.v1_quotas import V1Quotas
|
|
772
776
|
from lightning_sdk.lightning_cloud.openapi.models.v1_quote_subscription_response import V1QuoteSubscriptionResponse
|
|
777
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_r2_data_connection import V1R2DataConnection
|
|
773
778
|
from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_index_response import V1RefreshIndexResponse
|
|
774
779
|
from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_path_response import V1RefreshPathResponse
|
|
775
780
|
from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_request import V1RefreshRequest
|