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.
Files changed (50) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/deployment_api.py +3 -0
  3. lightning_sdk/api/lit_container_api.py +19 -2
  4. lightning_sdk/api/teamspace_api.py +47 -18
  5. lightning_sdk/api/utils.py +1 -1
  6. lightning_sdk/cli/entrypoint.py +2 -2
  7. lightning_sdk/cli/serve.py +143 -16
  8. lightning_sdk/cli/upload.py +4 -1
  9. lightning_sdk/deployment/deployment.py +9 -3
  10. lightning_sdk/lightning_cloud/openapi/__init__.py +5 -0
  11. lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +98 -1
  12. lightning_sdk/lightning_cloud/openapi/models/__init__.py +5 -0
  13. lightning_sdk/lightning_cloud/openapi/models/cluster_id_capacityreservations_body.py +55 -3
  14. lightning_sdk/lightning_cloud/openapi/models/create.py +53 -1
  15. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +55 -3
  16. lightning_sdk/lightning_cloud/openapi/models/update.py +27 -1
  17. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +53 -1
  18. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_cold_start_metrics_stats.py +357 -0
  19. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template_config.py +29 -3
  20. lightning_sdk/lightning_cloud/openapi/models/v1_cloudflare_v1.py +227 -0
  21. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_accelerator.py +27 -1
  22. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_capacity_reservation.py +55 -3
  23. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +53 -1
  24. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +27 -1
  25. lightning_sdk/lightning_cloud/openapi/models/v1_create_cloud_space_environment_template_request.py +29 -3
  26. lightning_sdk/lightning_cloud/openapi/models/v1_create_cluster_capacity_reservation_response.py +27 -1
  27. lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
  28. lightning_sdk/lightning_cloud/openapi/models/v1_gcp_direct_vpc.py +149 -0
  29. lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_cold_start_metrics_stats_response.py +123 -0
  30. lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +53 -1
  31. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +17 -17
  32. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +55 -3
  33. lightning_sdk/lightning_cloud/openapi/models/v1_project_cluster_binding.py +27 -1
  34. lightning_sdk/lightning_cloud/openapi/models/v1_r2_data_connection.py +253 -0
  35. lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +27 -1
  36. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +50 -24
  37. lightning_sdk/lightning_cloud/openapi/models/v1_validate_data_connection_response.py +27 -1
  38. lightning_sdk/lightning_cloud/openapi/models/v1_weka_data_connection.py +29 -55
  39. lightning_sdk/lightning_cloud/openapi/models/validate.py +27 -1
  40. lightning_sdk/lit_container.py +12 -2
  41. lightning_sdk/models.py +38 -9
  42. lightning_sdk/serve.py +6 -0
  43. lightning_sdk/teamspace.py +16 -2
  44. lightning_sdk/utils/resolve.py +11 -4
  45. {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/METADATA +1 -1
  46. {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/RECORD +50 -45
  47. {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/LICENSE +0 -0
  48. {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/WHEEL +0 -0
  49. {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/entry_points.txt +0 -0
  50. {lightning_sdk-0.2.9.dist-info → lightning_sdk-0.2.11.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py CHANGED
@@ -31,6 +31,6 @@ __all__ = [
31
31
  "User",
32
32
  ]
33
33
 
34
- __version__ = "0.2.9"
34
+ __version__ = "0.2.11"
35
35
  _check_version_and_prompt_upgrade(__version__)
36
36
  _set_tqdm_envvars_noninteractive()
@@ -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, container: str, teamspace: Teamspace, tag: str, cloud_account: str, platform: str
130
- ) -> Generator[dict, None, None]:
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._models: Optional[ModelsStoreApi] = None
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 models(self) -> ModelsStoreApi:
170
- if not self._models:
171
- self._models = ModelsStoreApi(self._client.api_client)
172
- return self._models
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.models.models_store_list_models(project_id=teamspace_id, name=name).models
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.models.models_store_create_model(
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.models.models_store_create_model_version(
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
- models = self.models.models_store_list_models(project_id=teamspace_id, name=name).models
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 = models[0].default_version
209
- self.models.models_store_delete_model_version(project_id=teamspace_id, model_id=model_id, version=version)
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.models.models_store_delete_model(project_id=teamspace_id, model_id=model_id)
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 complete_model_upload(self, model_id: str, version: str, teamspace_id: str) -> None:
259
- self.models.models_store_complete_model_upload(
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
@@ -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 == ("default"):
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:
@@ -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 serve
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(serve)
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)
@@ -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("serve", cls=_ServeGroup)
37
- def serve() -> None:
38
- """Serve a LitServe model.
41
+ @click.group("deploy", cls=_ServeGroup)
42
+ def deploy() -> None:
43
+ """Deploy a LitServe model.
39
44
 
40
45
  Example:
41
- lightning serve server.py # deploy to the cloud
46
+ lightning deploy server.py # deploy to the cloud
42
47
 
43
48
  Example:
44
- lightning serve server.py --local # serve locally
49
+ lightning deploy server.py --local # run locally
45
50
 
46
- You can deploy the API to the cloud by running `lightning serve server.py`.
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
- @serve.command("api")
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
- def poll_verified_status() -> bool:
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
- timeout = 600 # 10 minutes
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 True
296
+ return result
285
297
  if (datetime.now() - start_time).total_seconds() > timeout:
286
298
  break
287
299
  time.sleep(5)
288
- return False
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
- resolved_teamspace = select_teamspace(teamspace, org, user)
350
- verified = poll_verified_status()
351
- if not verified:
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
- webbrowser.open(deployment_status.get("url"))
524
+ if user_status["onboarded"]:
525
+ webbrowser.open(deployment_status.get("url"))
@@ -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 doamin.
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 environement variables for the SDK to authenticate
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