lightning-sdk 0.2.9__py3-none-any.whl → 0.2.10__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 +1 -0
- lightning_sdk/api/lit_container_api.py +19 -2
- lightning_sdk/api/teamspace_api.py +20 -16
- lightning_sdk/api/utils.py +1 -1
- lightning_sdk/cli/entrypoint.py +2 -2
- lightning_sdk/cli/serve.py +141 -16
- lightning_sdk/cli/upload.py +4 -1
- lightning_sdk/deployment/deployment.py +5 -2
- 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 +22 -9
- lightning_sdk/serve.py +3 -0
- lightning_sdk/teamspace.py +2 -0
- lightning_sdk/utils/resolve.py +11 -4
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.10.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.10.dist-info}/RECORD +50 -45
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.10.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.10.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.10.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.10.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
|
@@ -228,6 +228,7 @@ class DeploymentApi:
|
|
|
228
228
|
return self._client.jobs_service_create_deployment(
|
|
229
229
|
project_id=deployment.project_id,
|
|
230
230
|
body=CreateDeploymentRequestDefinesASpecForTheJobThatAllowsForAutoscalingJobs(
|
|
231
|
+
cloudspace_id=deployment.cloudspace_id,
|
|
231
232
|
autoscaling=deployment.autoscaling,
|
|
232
233
|
cluster_id=deployment.spec.cluster_id,
|
|
233
234
|
endpoint=deployment.endpoint,
|
|
@@ -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"]
|
|
@@ -34,7 +34,7 @@ class TeamspaceApi:
|
|
|
34
34
|
|
|
35
35
|
def __init__(self) -> None:
|
|
36
36
|
self._client = LightningClient(max_tries=7)
|
|
37
|
-
self.
|
|
37
|
+
self._models_api: Optional[ModelsStoreApi] = None
|
|
38
38
|
|
|
39
39
|
def get_teamspace(self, name: str, owner_id: str) -> V1Project:
|
|
40
40
|
"""Get the current teamspace from the owner."""
|
|
@@ -166,12 +166,12 @@ class TeamspaceApi:
|
|
|
166
166
|
|
|
167
167
|
# lazy property which is only created when needed
|
|
168
168
|
@property
|
|
169
|
-
def
|
|
170
|
-
if not self.
|
|
171
|
-
self.
|
|
172
|
-
return self.
|
|
169
|
+
def models_api(self) -> ModelsStoreApi:
|
|
170
|
+
if not self._models_api:
|
|
171
|
+
self._models_api = ModelsStoreApi(self._client.api_client)
|
|
172
|
+
return self._models_api
|
|
173
173
|
|
|
174
|
-
def get_model_version(self, name: str, version: str, teamspace_id: str) -> V1ModelVersionArchive:
|
|
174
|
+
def get_model_version(self, name: str, version: Optional[str], teamspace_id: str) -> V1ModelVersionArchive:
|
|
175
175
|
return _get_model_version(client=self._client, name=name, version=version, teamspace_id=teamspace_id)
|
|
176
176
|
|
|
177
177
|
def create_model(
|
|
@@ -184,14 +184,14 @@ class TeamspaceApi:
|
|
|
184
184
|
cloud_account: str,
|
|
185
185
|
) -> V1ModelVersionArchive:
|
|
186
186
|
# ask if such model already exists by listing models with specific name
|
|
187
|
-
models = self.
|
|
187
|
+
models = self.models_api.models_store_list_models(project_id=teamspace_id, name=name).models
|
|
188
188
|
if len(models) == 0:
|
|
189
|
-
return self.
|
|
189
|
+
return self.models_api.models_store_create_model(
|
|
190
190
|
body=ProjectIdModelsBody(cluster_id=cloud_account, metadata=metadata, name=name, private=private),
|
|
191
191
|
project_id=teamspace_id,
|
|
192
192
|
)
|
|
193
193
|
assert len(models) == 1, "Multiple models with the same name found"
|
|
194
|
-
return self.
|
|
194
|
+
return self.models_api.models_store_create_model_version(
|
|
195
195
|
body=ModelIdVersionsBody(cluster_id=cloud_account, version=version),
|
|
196
196
|
project_id=teamspace_id,
|
|
197
197
|
model_id=models[0].id,
|
|
@@ -199,16 +199,18 @@ class TeamspaceApi:
|
|
|
199
199
|
|
|
200
200
|
def delete_model(self, name: str, version: Optional[str], teamspace_id: str) -> None:
|
|
201
201
|
"""Delete a model or a version from the model store."""
|
|
202
|
-
models = self.
|
|
202
|
+
models = self.models_api.models_store_list_models(project_id=teamspace_id, name=name).models
|
|
203
203
|
assert len(models) == 1, "Multiple models with the same name found"
|
|
204
|
-
|
|
204
|
+
model = models[0]
|
|
205
205
|
# decide if delete only version of whole model
|
|
206
206
|
if version:
|
|
207
207
|
if version == "default":
|
|
208
|
-
version =
|
|
209
|
-
self.
|
|
208
|
+
version = model.default_version
|
|
209
|
+
self.models_api.models_store_delete_model_version(
|
|
210
|
+
project_id=teamspace_id, model_id=model.id, version=version
|
|
211
|
+
)
|
|
210
212
|
else:
|
|
211
|
-
self.
|
|
213
|
+
self.models_api.models_store_delete_model(project_id=teamspace_id, model_id=model.id)
|
|
212
214
|
|
|
213
215
|
def upload_model_file(
|
|
214
216
|
self,
|
|
@@ -256,7 +258,7 @@ class TeamspaceApi:
|
|
|
256
258
|
main_pbar.update(1)
|
|
257
259
|
|
|
258
260
|
def complete_model_upload(self, model_id: str, version: str, teamspace_id: str) -> None:
|
|
259
|
-
self.
|
|
261
|
+
self.models_api.models_store_complete_model_upload(
|
|
260
262
|
body=_DummyBody(),
|
|
261
263
|
project_id=teamspace_id,
|
|
262
264
|
model_id=model_id,
|
|
@@ -266,12 +268,14 @@ class TeamspaceApi:
|
|
|
266
268
|
def download_model_files(
|
|
267
269
|
self,
|
|
268
270
|
name: str,
|
|
269
|
-
version: str,
|
|
271
|
+
version: Optional[str],
|
|
270
272
|
download_dir: Path,
|
|
271
273
|
teamspace_name: str,
|
|
272
274
|
teamspace_owner_name: str,
|
|
273
275
|
progress_bar: bool = True,
|
|
274
276
|
) -> List[str]:
|
|
277
|
+
if version is None:
|
|
278
|
+
version = "default"
|
|
275
279
|
return _download_model_files(
|
|
276
280
|
client=self._client,
|
|
277
281
|
teamspace_name=teamspace_name,
|
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,127 @@ 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(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
|
+
start_time = datetime.now()
|
|
343
|
+
while self.status != _OnboardingStatus.ONBOARDED:
|
|
344
|
+
time.sleep(5)
|
|
345
|
+
if self.is_onboarded:
|
|
346
|
+
return
|
|
347
|
+
if (datetime.now() - start_time).total_seconds() > timeout:
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
raise RuntimeError("Timed out waiting for onboarding status")
|
|
351
|
+
|
|
352
|
+
def get_cloudspace_id(self, teamspace: Teamspace) -> Optional[str]:
|
|
353
|
+
cloudspaces: List[V1CloudSpace] = self.client.cloud_space_service_list_cloud_spaces(teamspace.id).cloudspaces
|
|
354
|
+
for cloudspace in cloudspaces:
|
|
355
|
+
if "scratch-studio" in cloudspace.name or "scratch-studio" in cloudspace.display_name:
|
|
356
|
+
return cloudspace.id
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
def select_teamspace(self, teamspace: Optional[str], org: Optional[str], user: Optional[str]) -> Teamspace:
|
|
360
|
+
"""Select a teamspace while onboarding.
|
|
361
|
+
|
|
362
|
+
If user is being onboarded and can't join any org, the teamspace it will be resolved to the default
|
|
363
|
+
personal teamspace.
|
|
364
|
+
If user is being onboarded and can join an org then it will select default teamspace from the org.
|
|
365
|
+
"""
|
|
366
|
+
if self.is_onboarded:
|
|
367
|
+
return select_teamspace(teamspace, org, user)
|
|
368
|
+
|
|
369
|
+
# Run only when user hasn't completed onboarding yet.
|
|
370
|
+
menu = _TeamspacesMenu()
|
|
371
|
+
possible_teamspaces = menu._get_possible_teamspaces(self.user)
|
|
372
|
+
can_join_org = self.can_join_org
|
|
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
|
+
|
|
380
|
+
possible_teamspaces = menu._get_possible_teamspaces(self.user)
|
|
381
|
+
if len(possible_teamspaces) == 1:
|
|
382
|
+
# User didn't select any org
|
|
383
|
+
value = next(iter(possible_teamspaces.values()))
|
|
384
|
+
return Teamspace(name=value["name"], org=value["org"], user=value["user"])
|
|
385
|
+
|
|
386
|
+
for _, value in possible_teamspaces.items():
|
|
387
|
+
# User select an org
|
|
388
|
+
# Onboarding teamspace will be the default teamspace in the selected org
|
|
389
|
+
if value["org"]:
|
|
390
|
+
return Teamspace(name=value["name"], org=value["org"], user=value["user"])
|
|
391
|
+
raise RuntimeError("Unable to select teamspace. Visit lightning.ai")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def is_connected(host: str = "8.8.8.8", port: int = 53, timeout: int = 10) -> bool:
|
|
395
|
+
try:
|
|
396
|
+
socket.setdefaulttimeout(timeout)
|
|
397
|
+
socket.create_connection((host, port))
|
|
398
|
+
return True
|
|
399
|
+
except OSError:
|
|
400
|
+
return False
|
|
289
401
|
|
|
290
402
|
|
|
291
403
|
def _handle_cloud(
|
|
@@ -306,6 +418,11 @@ def _handle_cloud(
|
|
|
306
418
|
replicas: Optional[int] = 1,
|
|
307
419
|
include_credentials: Optional[bool] = True,
|
|
308
420
|
) -> None:
|
|
421
|
+
if not is_connected():
|
|
422
|
+
console.print("❌ Internet connection required to deploy to the cloud.", style="red")
|
|
423
|
+
console.print("To run locally instead, use: `lightning serve [SCRIPT | server.py] --local`")
|
|
424
|
+
return
|
|
425
|
+
|
|
309
426
|
deployment_name = os.path.basename(repository)
|
|
310
427
|
tag = tag if tag else "latest"
|
|
311
428
|
|
|
@@ -346,11 +463,17 @@ def _handle_cloud(
|
|
|
346
463
|
console.print("\nPushing container to registry. It may take a while...", style="bold")
|
|
347
464
|
# Authenticate with LitServe affiliate
|
|
348
465
|
authenticate(shall_confirm=not non_interactive)
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if not verified:
|
|
466
|
+
user_status = poll_verified_status()
|
|
467
|
+
cloudspace_id: Optional[str] = None
|
|
468
|
+
if not user_status["verified"]:
|
|
352
469
|
console.print("❌ Verify phone number to continue. Visit lightning.ai.", style="red")
|
|
353
470
|
return
|
|
471
|
+
if not user_status["onboarded"]:
|
|
472
|
+
onboarding = _Onboarding(console)
|
|
473
|
+
resolved_teamspace = onboarding.select_teamspace(teamspace, org, user)
|
|
474
|
+
cloudspace_id = onboarding.get_cloudspace_id(resolved_teamspace)
|
|
475
|
+
else:
|
|
476
|
+
resolved_teamspace = select_teamspace(teamspace, org, user)
|
|
354
477
|
|
|
355
478
|
# list containers to create the project if it doesn't exist
|
|
356
479
|
lit_cr = LitContainerApi()
|
|
@@ -393,6 +516,8 @@ def _handle_cloud(
|
|
|
393
516
|
max_replica=max_replica,
|
|
394
517
|
replicas=replicas,
|
|
395
518
|
include_credentials=include_credentials,
|
|
519
|
+
cloudspace_id=cloudspace_id,
|
|
396
520
|
)
|
|
397
521
|
console.print(f"🚀 Deployment started, access at [i]{deployment_status.get('url')}[/i]")
|
|
398
|
-
|
|
522
|
+
if user_status["onboarded"]:
|
|
523
|
+
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,6 +117,7 @@ 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,
|
|
122
123
|
) -> None:
|
|
@@ -141,9 +142,10 @@ class Deployment:
|
|
|
141
142
|
auth: The auth config to protect your services. Only Basic and Token supported.
|
|
142
143
|
cloud_account: The name of the cloud account, the studio should be created on.
|
|
143
144
|
Doesn't matter when the studio already exists.
|
|
144
|
-
custom_domain: Whether your service would be referenced under a custom
|
|
145
|
+
custom_domain: Whether your service would be referenced under a custom domain.
|
|
146
|
+
cloudspace_id: Connect deployment to a Studio.
|
|
145
147
|
quantity: The number of machines per replica to deploy.
|
|
146
|
-
include_credentials: Whether to include the
|
|
148
|
+
include_credentials: Whether to include the environment variables for the SDK to authenticate
|
|
147
149
|
|
|
148
150
|
Note:
|
|
149
151
|
Since a teamspace can either be owned by an org or by a user directly,
|
|
@@ -166,6 +168,7 @@ class Deployment:
|
|
|
166
168
|
name=self._name,
|
|
167
169
|
project_id=self._teamspace.id,
|
|
168
170
|
replicas=replicas,
|
|
171
|
+
cloudspace_id=cloudspace_id,
|
|
169
172
|
spec=to_spec(
|
|
170
173
|
cloud_account=cloud_account,
|
|
171
174
|
command=command,
|
|
@@ -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
|
|
@@ -3174,6 +3174,99 @@ class CloudSpaceServiceApi(object):
|
|
|
3174
3174
|
_request_timeout=params.get('_request_timeout'),
|
|
3175
3175
|
collection_formats=collection_formats)
|
|
3176
3176
|
|
|
3177
|
+
def cloud_space_service_get_cloud_space_cold_start_metrics_stats(self, project_id: 'str', **kwargs) -> 'V1GetCloudSpaceColdStartMetricsStatsResponse': # noqa: E501
|
|
3178
|
+
"""cloud_space_service_get_cloud_space_cold_start_metrics_stats # noqa: E501
|
|
3179
|
+
|
|
3180
|
+
This method makes a synchronous HTTP request by default. To make an
|
|
3181
|
+
asynchronous HTTP request, please pass async_req=True
|
|
3182
|
+
>>> thread = api.cloud_space_service_get_cloud_space_cold_start_metrics_stats(project_id, async_req=True)
|
|
3183
|
+
>>> result = thread.get()
|
|
3184
|
+
|
|
3185
|
+
:param async_req bool
|
|
3186
|
+
:param str project_id: (required)
|
|
3187
|
+
:return: V1GetCloudSpaceColdStartMetricsStatsResponse
|
|
3188
|
+
If the method is called asynchronously,
|
|
3189
|
+
returns the request thread.
|
|
3190
|
+
"""
|
|
3191
|
+
kwargs['_return_http_data_only'] = True
|
|
3192
|
+
if kwargs.get('async_req'):
|
|
3193
|
+
return self.cloud_space_service_get_cloud_space_cold_start_metrics_stats_with_http_info(project_id, **kwargs) # noqa: E501
|
|
3194
|
+
else:
|
|
3195
|
+
(data) = self.cloud_space_service_get_cloud_space_cold_start_metrics_stats_with_http_info(project_id, **kwargs) # noqa: E501
|
|
3196
|
+
return data
|
|
3197
|
+
|
|
3198
|
+
def cloud_space_service_get_cloud_space_cold_start_metrics_stats_with_http_info(self, project_id: 'str', **kwargs) -> 'V1GetCloudSpaceColdStartMetricsStatsResponse': # noqa: E501
|
|
3199
|
+
"""cloud_space_service_get_cloud_space_cold_start_metrics_stats # noqa: E501
|
|
3200
|
+
|
|
3201
|
+
This method makes a synchronous HTTP request by default. To make an
|
|
3202
|
+
asynchronous HTTP request, please pass async_req=True
|
|
3203
|
+
>>> thread = api.cloud_space_service_get_cloud_space_cold_start_metrics_stats_with_http_info(project_id, async_req=True)
|
|
3204
|
+
>>> result = thread.get()
|
|
3205
|
+
|
|
3206
|
+
:param async_req bool
|
|
3207
|
+
:param str project_id: (required)
|
|
3208
|
+
:return: V1GetCloudSpaceColdStartMetricsStatsResponse
|
|
3209
|
+
If the method is called asynchronously,
|
|
3210
|
+
returns the request thread.
|
|
3211
|
+
"""
|
|
3212
|
+
|
|
3213
|
+
all_params = ['project_id'] # noqa: E501
|
|
3214
|
+
all_params.append('async_req')
|
|
3215
|
+
all_params.append('_return_http_data_only')
|
|
3216
|
+
all_params.append('_preload_content')
|
|
3217
|
+
all_params.append('_request_timeout')
|
|
3218
|
+
|
|
3219
|
+
params = locals()
|
|
3220
|
+
for key, val in six.iteritems(params['kwargs']):
|
|
3221
|
+
if key not in all_params:
|
|
3222
|
+
raise TypeError(
|
|
3223
|
+
"Got an unexpected keyword argument '%s'"
|
|
3224
|
+
" to method cloud_space_service_get_cloud_space_cold_start_metrics_stats" % key
|
|
3225
|
+
)
|
|
3226
|
+
params[key] = val
|
|
3227
|
+
del params['kwargs']
|
|
3228
|
+
# verify the required parameter 'project_id' is set
|
|
3229
|
+
if ('project_id' not in params or
|
|
3230
|
+
params['project_id'] is None):
|
|
3231
|
+
raise ValueError("Missing the required parameter `project_id` when calling `cloud_space_service_get_cloud_space_cold_start_metrics_stats`") # noqa: E501
|
|
3232
|
+
|
|
3233
|
+
collection_formats = {}
|
|
3234
|
+
|
|
3235
|
+
path_params = {}
|
|
3236
|
+
if 'project_id' in params:
|
|
3237
|
+
path_params['projectId'] = params['project_id'] # noqa: E501
|
|
3238
|
+
|
|
3239
|
+
query_params = []
|
|
3240
|
+
|
|
3241
|
+
header_params = {}
|
|
3242
|
+
|
|
3243
|
+
form_params = []
|
|
3244
|
+
local_var_files = {}
|
|
3245
|
+
|
|
3246
|
+
body_params = None
|
|
3247
|
+
# HTTP header `Accept`
|
|
3248
|
+
header_params['Accept'] = self.api_client.select_header_accept(
|
|
3249
|
+
['application/json']) # noqa: E501
|
|
3250
|
+
|
|
3251
|
+
# Authentication setting
|
|
3252
|
+
auth_settings = [] # noqa: E501
|
|
3253
|
+
|
|
3254
|
+
return self.api_client.call_api(
|
|
3255
|
+
'/v1/projects/{projectId}/cloudspaces/cold-start-stats', 'GET',
|
|
3256
|
+
path_params,
|
|
3257
|
+
query_params,
|
|
3258
|
+
header_params,
|
|
3259
|
+
body=body_params,
|
|
3260
|
+
post_params=form_params,
|
|
3261
|
+
files=local_var_files,
|
|
3262
|
+
response_type='V1GetCloudSpaceColdStartMetricsStatsResponse', # noqa: E501
|
|
3263
|
+
auth_settings=auth_settings,
|
|
3264
|
+
async_req=params.get('async_req'),
|
|
3265
|
+
_return_http_data_only=params.get('_return_http_data_only'),
|
|
3266
|
+
_preload_content=params.get('_preload_content', True),
|
|
3267
|
+
_request_timeout=params.get('_request_timeout'),
|
|
3268
|
+
collection_formats=collection_formats)
|
|
3269
|
+
|
|
3177
3270
|
def cloud_space_service_get_cloud_space_folder_index(self, project_id: 'str', id: 'str', **kwargs) -> 'V1GetFolderIndexResponse': # noqa: E501
|
|
3178
3271
|
"""cloud_space_service_get_cloud_space_folder_index # noqa: E501
|
|
3179
3272
|
|
|
@@ -3499,6 +3592,7 @@ class CloudSpaceServiceApi(object):
|
|
|
3499
3592
|
|
|
3500
3593
|
:param async_req bool
|
|
3501
3594
|
:param str project_id: (required)
|
|
3595
|
+
:param str cloudspace_id:
|
|
3502
3596
|
:return: V1GetCloudSpaceInstanceSystemMetricsAggregateResponse
|
|
3503
3597
|
If the method is called asynchronously,
|
|
3504
3598
|
returns the request thread.
|
|
@@ -3520,12 +3614,13 @@ class CloudSpaceServiceApi(object):
|
|
|
3520
3614
|
|
|
3521
3615
|
:param async_req bool
|
|
3522
3616
|
:param str project_id: (required)
|
|
3617
|
+
:param str cloudspace_id:
|
|
3523
3618
|
:return: V1GetCloudSpaceInstanceSystemMetricsAggregateResponse
|
|
3524
3619
|
If the method is called asynchronously,
|
|
3525
3620
|
returns the request thread.
|
|
3526
3621
|
"""
|
|
3527
3622
|
|
|
3528
|
-
all_params = ['project_id'] # noqa: E501
|
|
3623
|
+
all_params = ['project_id', 'cloudspace_id'] # noqa: E501
|
|
3529
3624
|
all_params.append('async_req')
|
|
3530
3625
|
all_params.append('_return_http_data_only')
|
|
3531
3626
|
all_params.append('_preload_content')
|
|
@@ -3552,6 +3647,8 @@ class CloudSpaceServiceApi(object):
|
|
|
3552
3647
|
path_params['projectId'] = params['project_id'] # noqa: E501
|
|
3553
3648
|
|
|
3554
3649
|
query_params = []
|
|
3650
|
+
if 'cloudspace_id' in params:
|
|
3651
|
+
query_params.append(('cloudspaceId', params['cloudspace_id'])) # noqa: E501
|
|
3555
3652
|
|
|
3556
3653
|
header_params = {}
|
|
3557
3654
|
|