lightning-sdk 0.2.3__py3-none-any.whl → 0.2.5__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 (110) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/ai_hub.py +10 -17
  3. lightning_sdk/api/ai_hub_api.py +20 -3
  4. lightning_sdk/api/studio_api.py +0 -8
  5. lightning_sdk/cli/serve.py +139 -22
  6. lightning_sdk/deployment/deployment.py +32 -4
  7. lightning_sdk/lightning_cloud/openapi/__init__.py +49 -1
  8. lightning_sdk/lightning_cloud/openapi/api/__init__.py +4 -0
  9. lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +537 -0
  10. lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +10 -6
  11. lightning_sdk/lightning_cloud/openapi/api/lit_dataset_service_api.py +1973 -0
  12. lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +5 -1
  13. lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +20 -20
  14. lightning_sdk/lightning_cloud/openapi/api/pipeline_templates_service_api.py +339 -0
  15. lightning_sdk/lightning_cloud/openapi/api/pipelines_service_api.py +5 -1
  16. lightning_sdk/lightning_cloud/openapi/api/schedules_service_api.py +573 -0
  17. lightning_sdk/lightning_cloud/openapi/api/slurm_jobs_user_service_api.py +202 -0
  18. lightning_sdk/lightning_cloud/openapi/models/__init__.py +45 -1
  19. lightning_sdk/lightning_cloud/openapi/models/cluster_id_capacityblock_body.py +15 -15
  20. lightning_sdk/lightning_cloud/openapi/models/cluster_id_slurmusers_body.py +201 -0
  21. lightning_sdk/lightning_cloud/openapi/models/dataset_id_versions_body.py +149 -0
  22. lightning_sdk/lightning_cloud/openapi/models/dataset_id_visibility_body.py +149 -0
  23. lightning_sdk/lightning_cloud/openapi/models/environmenttemplates_id_body.py +227 -0
  24. lightning_sdk/lightning_cloud/openapi/models/externalv1_cloud_space_instance_status.py +69 -69
  25. lightning_sdk/lightning_cloud/openapi/models/litdatasets_dataset_id_body.py +149 -0
  26. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +27 -1
  27. lightning_sdk/lightning_cloud/openapi/models/pipelines_id_body.py +69 -17
  28. lightning_sdk/lightning_cloud/openapi/models/pipelinetemplates_id_body.py +331 -0
  29. lightning_sdk/lightning_cloud/openapi/models/project_id_litdatasets_body.py +227 -0
  30. lightning_sdk/lightning_cloud/openapi/models/project_id_pipelines_body.py +17 -17
  31. lightning_sdk/lightning_cloud/openapi/models/project_id_schedules_body.py +201 -0
  32. lightning_sdk/lightning_cloud/openapi/models/schedules_id_body.py +383 -0
  33. lightning_sdk/lightning_cloud/openapi/models/slurm_jobs_body.py +15 -15
  34. lightning_sdk/lightning_cloud/openapi/models/upload_id_complete_body1.py +149 -0
  35. lightning_sdk/lightning_cloud/openapi/models/upload_id_parts_body1.py +149 -0
  36. lightning_sdk/lightning_cloud/openapi/models/v1_agent_job.py +124 -20
  37. lightning_sdk/lightning_cloud/openapi/models/v1_assistant_model_status.py +2 -0
  38. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
  39. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +27 -1
  40. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +253 -0
  41. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +27 -1
  42. lightning_sdk/lightning_cloud/openapi/models/v1_complete_lit_dataset_multi_part_upload_response.py +97 -0
  43. lightning_sdk/lightning_cloud/openapi/models/v1_complete_lit_dataset_upload_response.py +97 -0
  44. lightning_sdk/lightning_cloud/openapi/models/v1_create_cloud_space_environment_template_request.py +149 -0
  45. lightning_sdk/lightning_cloud/openapi/models/v1_create_lit_dataset_multi_part_upload_response.py +123 -0
  46. lightning_sdk/lightning_cloud/openapi/models/v1_create_organization_request.py +27 -1
  47. lightning_sdk/lightning_cloud/openapi/models/v1_create_pipeline_template_request.py +383 -0
  48. lightning_sdk/lightning_cloud/openapi/models/v1_create_project_request.py +27 -1
  49. lightning_sdk/lightning_cloud/openapi/models/{v1_pipeline_schedule.py → v1_delete_cloud_space_environment_template_response.py} +32 -32
  50. lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_dataset_response.py +97 -0
  51. lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_dataset_version_response.py +97 -0
  52. lightning_sdk/lightning_cloud/openapi/models/v1_delete_schedule_response.py +175 -0
  53. lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_size_response.py +79 -1
  54. lightning_sdk/lightning_cloud/openapi/models/v1_get_lit_dataset_file_upload_urls_response.py +123 -0
  55. lightning_sdk/lightning_cloud/openapi/models/v1_get_lit_dataset_files_url_response.py +149 -0
  56. lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +27 -1
  57. lightning_sdk/lightning_cloud/openapi/models/v1_instance_overprovisioning_spec.py +79 -27
  58. lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +27 -1
  59. lightning_sdk/lightning_cloud/openapi/models/v1_list_cloud_space_environment_templates_response.py +123 -0
  60. lightning_sdk/lightning_cloud/openapi/models/v1_list_lit_dataset_versions_response.py +123 -0
  61. lightning_sdk/lightning_cloud/openapi/models/v1_list_lit_datasets_response.py +123 -0
  62. lightning_sdk/lightning_cloud/openapi/models/v1_list_schedules_response.py +123 -0
  63. lightning_sdk/lightning_cloud/openapi/models/v1_list_slurm_cluster_users_response.py +123 -0
  64. lightning_sdk/lightning_cloud/openapi/models/v1_lit_dataset.py +539 -0
  65. lightning_sdk/lightning_cloud/openapi/models/v1_lit_dataset_file.py +175 -0
  66. lightning_sdk/lightning_cloud/openapi/models/v1_lit_dataset_version_archive.py +435 -0
  67. lightning_sdk/lightning_cloud/openapi/models/v1_lit_registry_project.py +27 -1
  68. lightning_sdk/lightning_cloud/openapi/models/v1_lit_repository.py +27 -1
  69. lightning_sdk/lightning_cloud/openapi/models/v1_magic_link_login_request.py +27 -1
  70. lightning_sdk/lightning_cloud/openapi/models/v1_managed_model.py +107 -3
  71. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +53 -1
  72. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline.py +69 -17
  73. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter.py +435 -0
  74. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter_placement.py +149 -0
  75. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter_placement_type.py +106 -0
  76. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_parameter_type.py +106 -0
  77. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_template.py +513 -0
  78. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_template_visibility_type.py +105 -0
  79. lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +27 -1
  80. lightning_sdk/lightning_cloud/openapi/models/v1_schedule.py +435 -0
  81. lightning_sdk/lightning_cloud/openapi/models/v1_schedule_resource_type.py +103 -0
  82. lightning_sdk/lightning_cloud/openapi/models/v1_slurm_cluster_user.py +227 -0
  83. lightning_sdk/lightning_cloud/openapi/models/v1_slurm_job.py +58 -6
  84. lightning_sdk/lightning_cloud/openapi/models/v1_slurm_node.py +31 -291
  85. lightning_sdk/lightning_cloud/openapi/models/v1_update_lit_dataset_visibility_response.py +123 -0
  86. lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +27 -1
  87. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +157 -105
  88. lightning_sdk/lightning_cloud/openapi/models/v1_validate_managed_endpoint_response.py +27 -1
  89. lightning_sdk/lightning_cloud/openapi/models/v1_voltage_park_direct_v1.py +203 -0
  90. lightning_sdk/lightning_cloud/openapi/models/version_default_body.py +29 -29
  91. lightning_sdk/lightning_cloud/openapi/models/version_default_body1.py +149 -0
  92. lightning_sdk/lightning_cloud/openapi/models/version_uploads_body1.py +123 -0
  93. lightning_sdk/lightning_cloud/openapi/models/versions_version_body1.py +123 -0
  94. lightning_sdk/lightning_cloud/rest_client.py +2 -0
  95. lightning_sdk/lit_container.py +8 -1
  96. lightning_sdk/mmt/mmt.py +1 -18
  97. lightning_sdk/mmt/v1.py +1 -28
  98. lightning_sdk/models.py +15 -6
  99. lightning_sdk/pipeline/pipeline.py +2 -2
  100. lightning_sdk/pipeline/types.py +28 -2
  101. lightning_sdk/pipeline/utils.py +1 -1
  102. lightning_sdk/plugin.py +0 -6
  103. lightning_sdk/serve.py +55 -22
  104. lightning_sdk/utils/resolve.py +1 -0
  105. {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/METADATA +1 -1
  106. {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/RECORD +110 -62
  107. {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/WHEEL +1 -1
  108. {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/LICENSE +0 -0
  109. {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.dist-info}/entry_points.txt +0 -0
  110. {lightning_sdk-0.2.3.dist-info → lightning_sdk-0.2.5.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.3"
34
+ __version__ = "0.2.5"
35
35
  _check_version_and_prompt_upgrade(__version__)
36
36
  _set_tqdm_envvars_noninteractive()
lightning_sdk/ai_hub.py CHANGED
@@ -3,6 +3,7 @@ from urllib.parse import quote
3
3
 
4
4
  from lightning_sdk.api import AIHubApi
5
5
  from lightning_sdk.api.utils import _get_cloud_url
6
+ from lightning_sdk.lightning_cloud.openapi.models import V1Deployment
6
7
  from lightning_sdk.user import User
7
8
  from lightning_sdk.utils.resolve import _resolve_teamspace
8
9
 
@@ -109,7 +110,8 @@ class AIHub:
109
110
  org: Optional[Union[str, "Organization"]] = None,
110
111
  user: Optional[Union[str, "User"]] = None,
111
112
  machine: Optional[Union[str, "Machine"]] = None,
112
- ) -> Dict[str, Union[str, bool]]:
113
+ quantity: Optional[int] = None,
114
+ ) -> V1Deployment:
113
115
  """Deploy an API from the AI Hub.
114
116
 
115
117
  Example:
@@ -126,14 +128,14 @@ class AIHub:
126
128
  api_arguments: Additional API argument, such as model name, or batch size.
127
129
  name: Name for the deployed API. Defaults to None.
128
130
  cloud_account: The cloud account where you want to run the template, such as "lightning-public-prod".
129
- Defaults to None.
131
+ Defaults to None, which will use the cloud account of the teamspace.
130
132
  teamspace: The team or group for deployment. Defaults to None.
131
133
  org: The organization for deployment. Don't pass user with this. Defaults to None.
132
134
  user: The user for deployment. Don't pass org with this. Defaults to None.
133
135
  machine: The machine to run the deployment on. Defaults to the first option set in the AI Hub template.
134
136
 
135
137
  Returns:
136
- A dictionary containing the name of the deployed API,
138
+ A V1Deployment object containing the name of the deployed API,
137
139
  the URL to access it, and whether it is interruptible.
138
140
 
139
141
  Raises:
@@ -157,6 +159,7 @@ class AIHub:
157
159
  name=name,
158
160
  api_arguments=api_arguments,
159
161
  machine=machine,
162
+ quantity=quantity,
160
163
  )
161
164
 
162
165
  url = (
@@ -169,16 +172,9 @@ class AIHub:
169
172
 
170
173
  print("Deployment available at:", url)
171
174
 
172
- return {
173
- "id": deployment.id,
174
- "name": deployment.name,
175
- "deployment_url": url,
176
- "api_endpoint": deployment.status.urls[0],
177
- "interruptible": deployment.spec.spot,
178
- "teamspace id": teamspace_id,
179
- }
175
+ return deployment
180
176
 
181
- def delete_deployment(self, deployment: Dict[str, Union[str, bool]]) -> None:
177
+ def delete_deployment(self, deployment: V1Deployment) -> None:
182
178
  """Delete a deployment from the AI Hub.
183
179
 
184
180
  Example:
@@ -188,9 +184,6 @@ class AIHub:
188
184
  hub.delete_deployment(deployment)
189
185
 
190
186
  Args:
191
- deployment: The deployment dictionary returned by the run method.
187
+ deployment: The deployment object returned by the run method.
192
188
  """
193
- if "teamspace id" not in deployment or "id" not in deployment:
194
- raise ValueError("Deployment dictionary must contain 'teamspace id' and 'id' keys.")
195
-
196
- self._api.delete_api(deployment["id"], deployment["teamspace id"])
189
+ self._api.delete_api(deployment.id, deployment.project_id)
@@ -1,4 +1,5 @@
1
1
  import traceback
2
+ import warnings
2
3
  from typing import Dict, List, Optional, Tuple, Union
3
4
 
4
5
  import backoff
@@ -95,15 +96,17 @@ class AIHubApi:
95
96
 
96
97
  if p.type == V1DeploymentTemplateParameterType.CHECKBOX and p.checkbox:
97
98
  api_arguments[p.name] = (
98
- (p.checkbox.true_value or "True")
99
- if p.checkbox.is_checked
100
- else (p.checkbox.false_value or "False")
99
+ (p.checkbox.true_value or "") if p.checkbox.is_checked else (p.checkbox.false_value or "")
101
100
  )
102
101
 
103
102
  for p in parameters:
104
103
  name = p.name
105
104
  pattern = f"${{{name}}}"
106
105
  if name in api_arguments:
106
+ if p.type == V1DeploymentTemplateParameterType.CHECKBOX and p.checkbox:
107
+ api_arguments[p.name] = (
108
+ (p.checkbox.true_value or "") if api_arguments[name] is True else (p.checkbox.false_value or "")
109
+ )
107
110
  AIHubApi._update_parameters(job, p.placements, pattern, api_arguments[name])
108
111
  elif not p.required:
109
112
  AIHubApi._update_parameters(job, p.placements, pattern, "")
@@ -120,6 +123,7 @@ class AIHubApi:
120
123
  name: Optional[str],
121
124
  api_arguments: Dict[str, str],
122
125
  machine: Optional[Union[str, Machine]],
126
+ quantity: Optional[int],
123
127
  ) -> V1Deployment:
124
128
  template = self._client.deployment_templates_service_get_deployment_template(template_id)
125
129
  name = name or template.name
@@ -139,6 +143,19 @@ class AIHubApi:
139
143
  apply_change(template.spec_v2.job, "instance_name", machine)
140
144
  apply_change(template.spec_v2.job, "instance_type", machine)
141
145
 
146
+ if quantity != template.spec_v2.job.quantity:
147
+ # If the quantity is different from the published template quantity, override it with warnging
148
+ warnings.warn(
149
+ "Overriding the quantity of the template with the provided quantity. "
150
+ "This may result in unexpected behavior. "
151
+ f"Please verify the template (https://lightning.ai/lightning-ai/ai-hub/{template_id}) "
152
+ "and asscoiated parameters before running."
153
+ )
154
+ apply_change(template.spec_v2.job, "quantity", quantity)
155
+
156
+ # Override the cluster_id with the cloud_account if it is provided
157
+ if len(cloud_account) > 0:
158
+ apply_change(template.spec_v2.job, "cluster_id", cloud_account)
142
159
  return self._client.jobs_service_create_deployment(
143
160
  project_id=project_id,
144
161
  body=CreateDeploymentRequestDefinesASpecForTheJobThatAllowsForAutoscalingJobs(
@@ -140,11 +140,6 @@ class StudioApi:
140
140
  startup_status = self.get_studio_status(studio_id, teamspace_id).in_use.startup_status
141
141
  return startup_status and startup_status.top_up_restore_finished
142
142
 
143
- @backoff.on_exception(backoff.expo, AttributeError, max_tries=10)
144
- def _check_code_status_sync_in_progress(self, studio_id: str, teamspace_id: str) -> bool:
145
- """Retries checking the sync_in_progress value of the code status when there's an AttributeError."""
146
- return self.get_studio_status(studio_id, teamspace_id).in_use.sync_in_progress
147
-
148
143
  def start_studio(
149
144
  self, studio_id: str, teamspace_id: str, machine: Union[Machine, str], interruptible: False
150
145
  ) -> None:
@@ -165,9 +160,6 @@ class StudioApi:
165
160
  studio_id,
166
161
  )
167
162
 
168
- while self._check_code_status_sync_in_progress(studio_id, teamspace_id):
169
- time.sleep(1)
170
-
171
163
  while True:
172
164
  if self._check_code_status_top_up_restore_finished(studio_id, teamspace_id):
173
165
  break
@@ -4,17 +4,19 @@ from pathlib import Path
4
4
  from typing import Optional, Union
5
5
 
6
6
  import click
7
- import docker
8
7
  from rich.console import Console
9
8
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
10
9
  from rich.prompt import Confirm
11
10
 
11
+ from lightning_sdk import Machine, Teamspace
12
12
  from lightning_sdk.api.deployment_api import DeploymentApi
13
13
  from lightning_sdk.api.lit_container_api import LitContainerApi
14
14
  from lightning_sdk.cli.exceptions import StudioCliError
15
15
  from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
16
16
  from lightning_sdk.serve import _LitServeDeployer
17
17
 
18
+ _MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
19
+
18
20
 
19
21
  @click.group("serve")
20
22
  def serve() -> None:
@@ -24,11 +26,10 @@ def serve() -> None:
24
26
  lightning serve api server.py # serve locally
25
27
 
26
28
  Example:
27
- lightning serve api server.py --cloud # deploy to the cloud
29
+ lightning serve api server.py --cloud --name litserve-api # deploy to the cloud
28
30
 
29
31
  You can deploy the API to the cloud by running `lightning serve api server.py --cloud`.
30
- This will generate a Dockerfile, build the image, and push it to the image registry.
31
- Deploying to the cloud requires pre-login to the docker registry.
32
+ This will build a docker container for the server.py script and deploy it to the Lightning AI platform.
32
33
  """
33
34
 
34
35
 
@@ -48,7 +49,6 @@ def serve() -> None:
48
49
  flag_value=True,
49
50
  help="Deploy the model to the Lightning AI platform",
50
51
  )
51
- @click.option("--gpu", is_flag=True, default=False, flag_value=True, help="Use GPU for serving")
52
52
  @click.option("--name", default=None, help="Name of the deployed API (e.g., 'classification-api', 'Llama-api')")
53
53
  @click.option(
54
54
  "--non-interactive",
@@ -58,17 +58,88 @@ def serve() -> None:
58
58
  flag_value=True,
59
59
  help="Do not prompt for confirmation",
60
60
  )
61
+ @click.option(
62
+ "--machine",
63
+ default="CPU",
64
+ show_default=True,
65
+ type=click.Choice(_MACHINE_VALUES),
66
+ help="The machine type to deploy the API on.",
67
+ )
68
+ @click.option(
69
+ "--interruptible",
70
+ is_flag=True,
71
+ default=False,
72
+ flag_value=True,
73
+ help="Whether the machine should be interruptible (spot) or not.",
74
+ )
75
+ @click.option(
76
+ "--teamspace",
77
+ default=None,
78
+ help="The teamspace the deployment should be associated with. Defaults to the current teamspace.",
79
+ )
80
+ @click.option(
81
+ "--org",
82
+ default=None,
83
+ help="The organization owning the teamspace (if any). Defaults to the current organization.",
84
+ )
85
+ @click.option("--user", default=None, help="The user owning the teamspace (if any). Defaults to the current user.")
86
+ @click.option(
87
+ "--cloud-account",
88
+ "--cloud_account",
89
+ default=None,
90
+ help=(
91
+ "The cloud account to run the deployment on. "
92
+ "Defaults to the studio cloud account if running with studio compute env. "
93
+ "If not provided will fall back to the teamspaces default cloud account."
94
+ ),
95
+ )
96
+ @click.option("--port", default=8000, help="The port to expose the API on.")
97
+ @click.option("--min_replica", "--min-replica", default=0, help="Number of replicas to start with.")
98
+ @click.option("--max_replica", "--max-replica", default=1, help="Number of replicas to scale up to.")
99
+ @click.option("--replicas", "--replicas", default=1, help="Deployment will start with this many replicas.")
100
+ @click.option(
101
+ "--no_credentials",
102
+ "--no-credentials",
103
+ is_flag=True,
104
+ default=False,
105
+ flag_value=True,
106
+ help="Whether to include credentials in the deployment.",
107
+ )
61
108
  def api(
62
109
  script_path: str,
63
110
  easy: bool,
64
111
  cloud: bool,
65
- gpu: bool,
66
112
  name: str,
67
113
  non_interactive: bool,
114
+ machine: str,
115
+ interruptible: bool,
116
+ teamspace: Optional[str],
117
+ org: Optional[str],
118
+ user: Optional[str],
119
+ cloud_account: Optional[str],
120
+ port: Optional[int],
121
+ min_replica: Optional[int],
122
+ max_replica: Optional[int],
123
+ replicas: Optional[int],
124
+ no_credentials: Optional[bool],
68
125
  ) -> None:
69
126
  """Deploy a LitServe model script."""
70
127
  return api_impl(
71
- script_path=script_path, easy=easy, cloud=cloud, gpu=gpu, repository=name, non_interactive=non_interactive
128
+ script_path=script_path,
129
+ easy=easy,
130
+ cloud=cloud,
131
+ repository=name,
132
+ non_interactive=non_interactive,
133
+ machine=machine,
134
+ interruptible=interruptible,
135
+ teamspace=teamspace,
136
+ org=org,
137
+ user=user,
138
+ cloud_account=cloud_account,
139
+ port=port,
140
+ min_replica=min_replica,
141
+ max_replica=max_replica,
142
+ include_credentials=not no_credentials,
72
143
  )
73
144
 
74
145
 
@@ -76,10 +147,20 @@ def api_impl(
76
147
  script_path: Union[str, Path],
77
148
  easy: bool = False,
78
149
  cloud: bool = False,
79
- gpu: bool = False,
80
150
  repository: [str] = None,
81
151
  tag: Optional[str] = None,
82
152
  non_interactive: bool = False,
153
+ machine: str = "CPU",
154
+ interruptible: bool = False,
155
+ teamspace: Optional[str] = None,
156
+ org: Optional[str] = None,
157
+ user: Optional[str] = None,
158
+ cloud_account: Optional[str] = None,
159
+ port: Optional[int] = 8000,
160
+ min_replica: Optional[int] = 0,
161
+ max_replica: Optional[int] = 1,
162
+ replicas: Optional[int] = 1,
163
+ include_credentials: Optional[bool] = True,
83
164
  ) -> None:
84
165
  """Deploy a LitServe model script."""
85
166
  console = Console()
@@ -93,8 +174,24 @@ def api_impl(
93
174
 
94
175
  if cloud:
95
176
  repository = repository or "litserve-model"
177
+ machine = Machine.from_str(machine)
96
178
  return _handle_cloud(
97
- script_path, console, gpu=gpu, repository=repository, tag=tag, non_interactive=non_interactive
179
+ script_path,
180
+ console,
181
+ repository=repository,
182
+ tag=tag,
183
+ non_interactive=non_interactive,
184
+ machine=machine,
185
+ interruptible=interruptible,
186
+ teamspace=teamspace,
187
+ org=org,
188
+ user=user,
189
+ cloud_account=cloud_account,
190
+ port=port,
191
+ min_replica=min_replica,
192
+ max_replica=max_replica,
193
+ replicas=replicas,
194
+ include_credentials=include_credentials,
98
195
  )
99
196
 
100
197
  try:
@@ -111,22 +208,30 @@ def api_impl(
111
208
  def _handle_cloud(
112
209
  script_path: Union[str, Path],
113
210
  console: Console,
114
- gpu: bool,
115
211
  repository: str = "litserve-model",
116
212
  tag: Optional[str] = None,
117
- teamspace: Optional[str] = None,
118
213
  non_interactive: bool = False,
214
+ machine: Machine = "CPU",
215
+ interruptible: bool = False,
216
+ teamspace: Optional[str] = None,
217
+ org: Optional[str] = None,
218
+ user: Optional[str] = None,
219
+ cloud_account: Optional[str] = None,
220
+ port: Optional[int] = 8000,
221
+ min_replica: Optional[int] = 0,
222
+ max_replica: Optional[int] = 1,
223
+ replicas: Optional[int] = 1,
224
+ include_credentials: Optional[bool] = True,
119
225
  ) -> None:
120
- menu = _TeamspacesMenu()
121
- teamspace = menu._resolve_teamspace(teamspace)
122
- try:
123
- client = docker.from_env()
124
- client.ping()
125
- except docker.errors.DockerException as e:
126
- raise StudioCliError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
226
+ if teamspace is None:
227
+ menu = _TeamspacesMenu()
228
+ resolved_teamspace = menu._resolve_teamspace(teamspace)
229
+ else:
230
+ resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
127
231
 
232
+ port = port or 8000
128
233
  ls_deployer = _LitServeDeployer()
129
- path = ls_deployer.dockerize_api(script_path, port=8000, gpu=gpu, tag=tag)
234
+ path = ls_deployer.dockerize_api(script_path, port=port, gpu=not machine.is_cpu(), tag=tag, print_success=False)
130
235
  console.clear()
131
236
  if non_interactive:
132
237
  console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
@@ -142,7 +247,8 @@ def _handle_cloud(
142
247
  lit_cr = LitContainerApi()
143
248
  deployment_name = os.path.basename(repository)
144
249
 
145
- if DeploymentApi().get_deployment_by_name(deployment_name, teamspace.id):
250
+ ls_deployer.authenticate()
251
+ if DeploymentApi().get_deployment_by_name(deployment_name, resolved_teamspace.id):
146
252
  raise StudioCliError(f"Deployment {deployment_name} already exists. Please choose a different name.") from None
147
253
 
148
254
  with Progress(
@@ -154,7 +260,7 @@ def _handle_cloud(
154
260
  ) as progress:
155
261
  try:
156
262
  ls_deployer.build_container(path, repository, tag, console, progress)
157
- push_status = ls_deployer.push_container(repository, tag, teamspace, lit_cr, progress)
263
+ push_status = ls_deployer.push_container(repository, tag, resolved_teamspace, lit_cr, progress)
158
264
  except Exception as e:
159
265
  console.print(f"❌ Deployment failed: {e}", style="red")
160
266
  return
@@ -163,6 +269,17 @@ def _handle_cloud(
163
269
  repository = push_status.get("repository")
164
270
 
165
271
  deployment_status = ls_deployer._run_on_cloud(
166
- deployment_name=deployment_name, image=repository, teamspace=teamspace, ports=[8000], gpu=gpu, metric=None
272
+ deployment_name=deployment_name,
273
+ image=repository,
274
+ teamspace=resolved_teamspace,
275
+ metric=None,
276
+ machine=machine,
277
+ spot=interruptible,
278
+ cloud_account=cloud_account,
279
+ port=port,
280
+ min_replica=min_replica,
281
+ max_replica=max_replica,
282
+ replicas=replicas,
283
+ include_credentials=include_credentials,
167
284
  )
168
285
  console.print(f"🚀 Deployment started, access at [i]{deployment_status.get('url')}[/i]")
@@ -32,7 +32,7 @@ from lightning_sdk.organization import Organization
32
32
  from lightning_sdk.services.utilities import _get_cluster
33
33
  from lightning_sdk.teamspace import Teamspace
34
34
  from lightning_sdk.user import User
35
- from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_teamspace, _resolve_user
35
+ from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_org, _resolve_teamspace, _resolve_user
36
36
 
37
37
 
38
38
  class Deployment:
@@ -74,6 +74,7 @@ class Deployment:
74
74
 
75
75
  self._name = name
76
76
  self._user = _resolve_user(self._user or user)
77
+ self._org = _resolve_org(org)
77
78
 
78
79
  self._teamspace = _resolve_teamspace(
79
80
  teamspace=teamspace,
@@ -96,6 +97,8 @@ class Deployment:
96
97
  self._name = deployment.name
97
98
  self._is_created = True
98
99
  self._deployment = deployment
100
+ else:
101
+ self._deployment = None
99
102
 
100
103
  def start(
101
104
  self,
@@ -235,6 +238,13 @@ class Deployment:
235
238
  """All the deployment replicas will be stopped and all their traffic blocked."""
236
239
  self._deployment = self._deployment_api.stop(self._deployment)
237
240
 
241
+ @property
242
+ def name(self) -> Optional[str]:
243
+ if self._deployment:
244
+ self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
245
+ self._name = self._deployment.name
246
+ return self._name
247
+
238
248
  @property
239
249
  def replicas(self) -> Optional[int]:
240
250
  """The default number of replicas the release starts with."""
@@ -248,7 +258,7 @@ class Deployment:
248
258
  """The minimum number of replicas."""
249
259
  if self._deployment:
250
260
  self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
251
- return self._deployment.autoscaling.min_replicas
261
+ return int(self._deployment.autoscaling.min_replicas)
252
262
  return None
253
263
 
254
264
  @property
@@ -256,7 +266,7 @@ class Deployment:
256
266
  """The maximum number of replicas."""
257
267
  if self._deployment:
258
268
  self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
259
- return self._deployment.autoscaling.max_replicas
269
+ return int(self._deployment.autoscaling.max_replicas)
260
270
  return None
261
271
 
262
272
  @property
@@ -371,9 +381,13 @@ class Deployment:
371
381
  return self._deployment.spec.include_credentials
372
382
  return None
373
383
 
384
+ @property
385
+ def org(self) -> Optional[Organization]:
386
+ return self._org
387
+
374
388
  @property
375
389
  def user(self) -> Optional[User]:
376
- """The teamspace of the deployment."""
390
+ """The user of the deployment."""
377
391
  return self._user
378
392
 
379
393
  @property
@@ -385,6 +399,20 @@ class Deployment:
385
399
  def is_started(self) -> bool:
386
400
  return self._is_created
387
401
 
402
+ @property
403
+ def is_stopped(self) -> Optional[bool]:
404
+ if self._deployment:
405
+ self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
406
+ return self._deployment.autoscaling.max_replicas == 0
407
+ return None
408
+
409
+ @property
410
+ def image(self) -> Optional[str]:
411
+ if self._deployment:
412
+ self._deployment = self._deployment_api.get_deployment_by_name(self._name, self._teamspace.id)
413
+ return self._deployment.spec.image
414
+ return None
415
+
388
416
  @property
389
417
  def _session(self) -> Any:
390
418
  if self._request_session is None: