lightning-sdk 0.2.6__py3-none-any.whl → 0.2.8__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/ai_hub_api.py +1 -0
- lightning_sdk/api/lit_container_api.py +83 -30
- lightning_sdk/api/teamspace_api.py +8 -9
- lightning_sdk/api/utils.py +0 -1
- lightning_sdk/cli/docker.py +1 -1
- lightning_sdk/cli/download.py +10 -2
- lightning_sdk/cli/serve.py +51 -41
- lightning_sdk/cli/upload.py +41 -6
- lightning_sdk/lightning_cloud/openapi/__init__.py +8 -0
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +202 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +8 -0
- lightning_sdk/lightning_cloud/openapi/models/alerts_config_billing.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/alerts_config_studios.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/server_id_alerts_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_alert_method.py +102 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_alerts_config.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_cold_start_metrics.py +617 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_conversation_response_chunk.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_create_project_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_list_cloud_space_cold_start_metrics_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_message.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_report_cloud_space_instance_stop_at_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_server_alert.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_phase.py +104 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +53 -1
- lightning_sdk/lightning_cloud/rest_client.py +42 -44
- lightning_sdk/lit_container.py +19 -4
- lightning_sdk/models.py +1 -1
- lightning_sdk/serve.py +107 -41
- lightning_sdk/teamspace.py +32 -18
- {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.8.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.8.dist-info}/RECORD +42 -34
- {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.8.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.8.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.8.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.6.dist-info → lightning_sdk-0.2.8.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
lightning_sdk/api/ai_hub_api.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import time
|
|
3
|
-
from typing import Any, Callable, Dict, Generator, Iterator, List
|
|
3
|
+
from typing import Any, Callable, Dict, Generator, Iterator, List, Optional
|
|
4
4
|
|
|
5
5
|
import docker
|
|
6
6
|
import requests
|
|
7
|
+
from rich.console import Console
|
|
7
8
|
|
|
8
9
|
from lightning_sdk.api.utils import _get_registry_url
|
|
9
|
-
from lightning_sdk.lightning_cloud.env import LIGHTNING_CLOUD_URL
|
|
10
10
|
from lightning_sdk.lightning_cloud.openapi.models import V1DeleteLitRepositoryResponse
|
|
11
11
|
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
12
12
|
from lightning_sdk.teamspace import Teamspace
|
|
@@ -21,6 +21,21 @@ class DockerPushError(Exception):
|
|
|
21
21
|
pass
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class DockerNotRunningError(Exception):
|
|
25
|
+
def __init__(self, message: str = "Failed to connect to Docker") -> None:
|
|
26
|
+
self.message = message
|
|
27
|
+
super().__init__(self.message)
|
|
28
|
+
|
|
29
|
+
def print_help(self) -> None:
|
|
30
|
+
console = Console()
|
|
31
|
+
console.print("[bold red]Docker Error: Failed to connect to Docker. Is it running?[/bold red]")
|
|
32
|
+
console.print("[yellow]Troubleshooting:[/yellow]")
|
|
33
|
+
console.print("1. Check if Docker daemon is running")
|
|
34
|
+
console.print("2. Verify your user has proper permissions")
|
|
35
|
+
console.print("3. Try restarting Docker service")
|
|
36
|
+
console.print("4. Read more here: https://docs.docker.com/engine/daemon/start/")
|
|
37
|
+
|
|
38
|
+
|
|
24
39
|
def retry_on_lcr_auth_failure(func: Callable) -> Callable:
|
|
25
40
|
def generator_wrapper(self: "LitContainerApi", *args: Any, **kwargs: Any) -> Callable:
|
|
26
41
|
try:
|
|
@@ -54,9 +69,7 @@ class LitContainerApi:
|
|
|
54
69
|
self._docker_client.ping()
|
|
55
70
|
self._docker_auth_config = {}
|
|
56
71
|
except docker.errors.DockerException:
|
|
57
|
-
raise
|
|
58
|
-
"Failed to connect to Docker, follow these steps to start it: https://docs.docker.com/engine/daemon/start/"
|
|
59
|
-
) from None
|
|
72
|
+
raise DockerNotRunningError() from None
|
|
60
73
|
|
|
61
74
|
def authenticate(self, reauth: bool = False) -> bool:
|
|
62
75
|
resp = None
|
|
@@ -85,45 +98,73 @@ class LitContainerApi:
|
|
|
85
98
|
print(f"Authentication error: {e} resp: {resp}")
|
|
86
99
|
return False
|
|
87
100
|
|
|
88
|
-
def list_containers(self, project_id: str) -> List:
|
|
89
|
-
project
|
|
101
|
+
def list_containers(self, project_id: str, cloud_account: Optional[str] = None) -> List:
|
|
102
|
+
"""Lists containers of the project ID.
|
|
103
|
+
|
|
104
|
+
:param project_id: The non-human readable project ID used internally to identify projects.
|
|
105
|
+
:param cloud_account: The cluster ID of the cloud account. If None, will use the default cluster.
|
|
106
|
+
:return:
|
|
107
|
+
"""
|
|
108
|
+
project = self._client.lit_registry_service_get_lit_project_registry(
|
|
109
|
+
project_id, cluster_id="" if cloud_account is None else cloud_account
|
|
110
|
+
) # cloud account on the CLI is cluster_id
|
|
90
111
|
return project.repositories
|
|
91
112
|
|
|
92
113
|
def delete_container(self, project_id: str, container: str) -> V1DeleteLitRepositoryResponse:
|
|
114
|
+
"""Deletes the container from LitCR. Garbage collection will come in and do the final sweep of the blob.
|
|
115
|
+
|
|
116
|
+
:param project_id: The non-human readable project ID used internally to identify projects.
|
|
117
|
+
:param container: The name of the docker container a user wants to push up, ie, nginx, vllm, etc
|
|
118
|
+
:return:
|
|
119
|
+
"""
|
|
93
120
|
try:
|
|
94
121
|
return self._client.lit_registry_service_delete_lit_repository(project_id, container)
|
|
95
122
|
except Exception as e:
|
|
96
123
|
raise ValueError(f"Could not delete container {container} from project {project_id}: {e!s}") from e
|
|
97
124
|
|
|
98
125
|
@retry_on_lcr_auth_failure
|
|
99
|
-
def upload_container(
|
|
126
|
+
def upload_container(
|
|
127
|
+
self, container: str, teamspace: Teamspace, tag: str, cloud_account: str, platform: str
|
|
128
|
+
) -> Generator[dict, None, None]:
|
|
129
|
+
"""Upload container will push the container to LitCR.
|
|
130
|
+
|
|
131
|
+
It uses docker push API to interact with docker daemon which will then push the container to a storage
|
|
132
|
+
location defined by teamspace + cloud_account. Will retry pushes if not authenticated or if push errors happen
|
|
133
|
+
|
|
134
|
+
:param container: The name of the docker container a user wants to push up, ie, nginx, vllm, etc
|
|
135
|
+
:param teamspace: The teamspace he container is going to appear in. This is <OWNER_ID>/<TEAMSPACE_NAME>
|
|
136
|
+
:param tag: The container tag, default will latest.
|
|
137
|
+
:param cloud_account: If empty will be pushed to Lightning SaaS storage. Otherwise, this will be cluster_id.
|
|
138
|
+
Named cloud-account in the CLI options.
|
|
139
|
+
:param platform: If empty will be linux/amd64. This is important because our entire deployment infra runs on
|
|
140
|
+
linux/amd64. Will show user a warning otherwise.
|
|
141
|
+
:return: Generator[dict, None, dict]
|
|
142
|
+
"""
|
|
100
143
|
try:
|
|
101
|
-
self._docker_client.images.get(container)
|
|
144
|
+
self._docker_client.images.get(f"{container}:{tag}")
|
|
102
145
|
except docker.errors.ImageNotFound:
|
|
103
146
|
try:
|
|
104
|
-
self._docker_client.images.pull(container, tag)
|
|
105
|
-
self._docker_client.images.get(container)
|
|
147
|
+
self._docker_client.images.pull(repository=container, tag=tag, platform=platform)
|
|
148
|
+
self._docker_client.images.get(f"{container}:{tag}")
|
|
149
|
+
except docker.errors.ImageNotFound as e:
|
|
150
|
+
raise ValueError(f"Container {container}:{tag} does not exist") from e
|
|
106
151
|
except docker.errors.APIError as e:
|
|
107
152
|
raise ValueError(f"Could not pull container {container}") from e
|
|
108
|
-
except docker.errors.ImageNotFound as e:
|
|
109
|
-
raise ValueError(f"Container {container} does not exist") from e
|
|
110
153
|
except Exception as e:
|
|
111
|
-
raise ValueError(f"Unable to upload {container}") from e
|
|
154
|
+
raise ValueError(f"Unable to upload {container}:{tag}") from e
|
|
112
155
|
|
|
113
156
|
registry_url = _get_registry_url()
|
|
114
157
|
container_basename = container.split("/")[-1]
|
|
115
|
-
repository =
|
|
116
|
-
|
|
158
|
+
repository = (
|
|
159
|
+
f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
|
|
160
|
+
f"{teamspace.owner.name}/{teamspace.name}/{container_basename}"
|
|
161
|
+
)
|
|
162
|
+
tagged = self._docker_client.api.tag(f"{container}:{tag}", repository, tag)
|
|
117
163
|
if not tagged:
|
|
118
|
-
raise ValueError(f"Could not tag container {container} with {repository}:{tag}")
|
|
119
|
-
yield from self._push_with_retry(repository)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"url": f"{LIGHTNING_CLOUD_URL}/{teamspace.owner.name}/{teamspace.name}/containers/{container_basename}",
|
|
123
|
-
"repository": repository,
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
def _push_with_retry(self, repository: str, max_retries: int = 3) -> Iterator[Dict[str, Any]]:
|
|
164
|
+
raise ValueError(f"Could not tag container {container}:{tag} with {repository}:{tag}")
|
|
165
|
+
yield from self._push_with_retry(repository, tag=tag)
|
|
166
|
+
|
|
167
|
+
def _push_with_retry(self, repository: str, tag: str, max_retries: int = 3) -> Iterator[Dict[str, Any]]:
|
|
127
168
|
def is_auth_error(error_msg: str) -> bool:
|
|
128
169
|
auth_errors = ["unauthorized", "authentication required", "unauth"]
|
|
129
170
|
return any(err in error_msg.lower() for err in auth_errors)
|
|
@@ -141,7 +182,7 @@ class LitContainerApi:
|
|
|
141
182
|
time.sleep(2)
|
|
142
183
|
|
|
143
184
|
lines = self._docker_client.api.push(
|
|
144
|
-
repository, stream=True, decode=True, auth_config=self._docker_auth_config
|
|
185
|
+
repository, tag=tag, stream=True, decode=True, auth_config=self._docker_auth_config
|
|
145
186
|
)
|
|
146
187
|
for line in lines:
|
|
147
188
|
if isinstance(line, dict) and "error" in line:
|
|
@@ -149,7 +190,7 @@ class LitContainerApi:
|
|
|
149
190
|
if is_auth_error(error) or is_timeout_error(error):
|
|
150
191
|
if attempt < max_retries - 1:
|
|
151
192
|
break
|
|
152
|
-
raise DockerPushError(f"Max retries reached: {error}")
|
|
193
|
+
raise DockerPushError(f"Max retries reached while pushing: {error}")
|
|
153
194
|
raise DockerPushError(f"Push error: {error}")
|
|
154
195
|
yield line
|
|
155
196
|
else:
|
|
@@ -158,14 +199,26 @@ class LitContainerApi:
|
|
|
158
199
|
except docker.errors.APIError as e:
|
|
159
200
|
if (is_auth_error(str(e)) or is_timeout_error(str(e))) and attempt < max_retries - 1:
|
|
160
201
|
continue
|
|
161
|
-
raise DockerPushError(
|
|
202
|
+
raise DockerPushError("Pushing the container failed") from e
|
|
162
203
|
|
|
163
204
|
raise DockerPushError("Max push retries reached")
|
|
164
205
|
|
|
165
206
|
@retry_on_lcr_auth_failure
|
|
166
|
-
def download_container(
|
|
207
|
+
def download_container(
|
|
208
|
+
self, container: str, teamspace: Teamspace, tag: str, cloud_account: Optional[str] = None
|
|
209
|
+
) -> Generator[str, None, None]:
|
|
210
|
+
"""Will download container from LitCR. Optionally from a BYOC cluster.
|
|
211
|
+
|
|
212
|
+
:param container: The name of the container to download
|
|
213
|
+
:param teamspace: The teamspace containing the container
|
|
214
|
+
:param tag: The tag of the container to download
|
|
215
|
+
:return: Generator yielding download status
|
|
216
|
+
"""
|
|
167
217
|
registry_url = _get_registry_url()
|
|
168
|
-
repository =
|
|
218
|
+
repository = (
|
|
219
|
+
f"{registry_url}/lit-container{f'-{cloud_account}' if cloud_account is not None else ''}/"
|
|
220
|
+
f"{teamspace.owner.name}/{teamspace.name}/{container}"
|
|
221
|
+
)
|
|
169
222
|
try:
|
|
170
223
|
self._docker_client.images.pull(repository, tag=tag, auth_config=self._docker_auth_config)
|
|
171
224
|
except requests.exceptions.HTTPError as e:
|
|
@@ -215,16 +215,15 @@ class TeamspaceApi:
|
|
|
215
215
|
version: str,
|
|
216
216
|
local_path: Path,
|
|
217
217
|
remote_path: str,
|
|
218
|
-
cloud_account: str,
|
|
219
218
|
teamspace_id: str,
|
|
220
219
|
progress_bar: bool = True,
|
|
221
220
|
) -> None:
|
|
221
|
+
"""Upload a file to the model store."""
|
|
222
222
|
uploader = _ModelFileUploader(
|
|
223
223
|
client=self._client,
|
|
224
224
|
model_id=model_id,
|
|
225
225
|
version=version,
|
|
226
226
|
teamspace_id=teamspace_id,
|
|
227
|
-
cloud_account=cloud_account,
|
|
228
227
|
file_path=str(local_path),
|
|
229
228
|
remote_path=str(remote_path),
|
|
230
229
|
progress_bar=progress_bar,
|
|
@@ -235,20 +234,20 @@ class TeamspaceApi:
|
|
|
235
234
|
self,
|
|
236
235
|
model_id: str,
|
|
237
236
|
version: str,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
cloud_account: str,
|
|
237
|
+
file_paths: List[Path],
|
|
238
|
+
remote_paths: List[str],
|
|
241
239
|
teamspace_id: str,
|
|
242
240
|
progress_bar: bool = True,
|
|
243
241
|
) -> None:
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
"""Upload files to the model store."""
|
|
243
|
+
main_pbar = tqdm(total=len(file_paths), desc="Uploading files...", position=0) if progress_bar else None
|
|
244
|
+
assert len(file_paths) == len(remote_paths), "File paths and remote paths must have the same length"
|
|
245
|
+
for filepath, remote_path in zip(file_paths, remote_paths):
|
|
246
246
|
self.upload_model_file(
|
|
247
247
|
model_id=model_id,
|
|
248
248
|
version=version,
|
|
249
249
|
local_path=filepath,
|
|
250
|
-
remote_path=
|
|
251
|
-
cloud_account=cloud_account,
|
|
250
|
+
remote_path=remote_path,
|
|
252
251
|
teamspace_id=teamspace_id,
|
|
253
252
|
progress_bar=progress_bar, # TODO: Global progress bar
|
|
254
253
|
)
|
lightning_sdk/api/utils.py
CHANGED
lightning_sdk/cli/docker.py
CHANGED
|
@@ -19,4 +19,4 @@ def api(server_filename: str, port: int = 8000, gpu: bool = False, tag: str = "l
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _api(server_filename: str, port: int = 8000, gpu: bool = False, tag: str = "litserve-model") -> str:
|
|
22
|
-
return _LitServeDeployer().dockerize_api(server_filename=server_filename, port=port, gpu=gpu, tag=tag)
|
|
22
|
+
return _LitServeDeployer(None, None).dockerize_api(server_filename=server_filename, port=port, gpu=gpu, tag=tag)
|
lightning_sdk/cli/download.py
CHANGED
|
@@ -143,7 +143,15 @@ def file(path: str = "", studio: Optional[str] = None, local_path: str = ".") ->
|
|
|
143
143
|
@click.argument("container")
|
|
144
144
|
@click.option("--teamspace", default=None, help="The name of the teamspace to download the container from")
|
|
145
145
|
@click.option("--tag", default="latest", show_default=True, help="The tag of the container to download.")
|
|
146
|
-
|
|
146
|
+
@click.option(
|
|
147
|
+
"--cloud-account",
|
|
148
|
+
"--cloud_account", # The UI will present the above variant, using this as a secondary to be consistent w/ models
|
|
149
|
+
default=None,
|
|
150
|
+
help="The name of the cloud account to download the Container from.",
|
|
151
|
+
)
|
|
152
|
+
def download_container(
|
|
153
|
+
container: str, teamspace: Optional[str] = None, tag: str = "latest", cloud_account: Optional[str] = None
|
|
154
|
+
) -> None:
|
|
147
155
|
"""Download a docker container from a teamspace.
|
|
148
156
|
|
|
149
157
|
Example:
|
|
@@ -156,7 +164,7 @@ def download_container(container: str, teamspace: Optional[str] = None, tag: str
|
|
|
156
164
|
resolved_teamspace = menu._resolve_teamspace(teamspace)
|
|
157
165
|
with console.status("Downloading container..."):
|
|
158
166
|
api = LitContainerApi()
|
|
159
|
-
api.download_container(container, resolved_teamspace, tag)
|
|
167
|
+
api.download_container(container, resolved_teamspace, tag, cloud_account)
|
|
160
168
|
console.print("Container downloaded successfully", style="green")
|
|
161
169
|
|
|
162
170
|
|
lightning_sdk/cli/serve.py
CHANGED
|
@@ -8,28 +8,35 @@ import click
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
|
10
10
|
from rich.prompt import Confirm
|
|
11
|
-
from rich.syntax import Syntax
|
|
12
11
|
|
|
13
12
|
from lightning_sdk import Machine, Teamspace
|
|
14
|
-
from lightning_sdk.api.deployment_api import DeploymentApi
|
|
15
13
|
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
16
14
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
17
|
-
from lightning_sdk.serve import _LitServeDeployer
|
|
15
|
+
from lightning_sdk.serve import _LitServeDeployer, authenticate
|
|
18
16
|
|
|
19
17
|
_MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
20
18
|
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
class _ServeGroup(click.Group):
|
|
21
|
+
def parse_args(self, ctx: click.Context, args: list) -> click.Group:
|
|
22
|
+
# Check if first arg is a file path and not a command name
|
|
23
|
+
if args and os.path.exists(args[0]) and args[0] not in self.commands:
|
|
24
|
+
# Insert the 'api' command before the file path
|
|
25
|
+
args.insert(0, "api")
|
|
26
|
+
return super().parse_args(ctx, args)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.group("serve", cls=_ServeGroup)
|
|
23
30
|
def serve() -> None:
|
|
24
31
|
"""Serve a LitServe model.
|
|
25
32
|
|
|
26
33
|
Example:
|
|
27
|
-
lightning serve
|
|
34
|
+
lightning serve server.py # serve locally
|
|
28
35
|
|
|
29
36
|
Example:
|
|
30
|
-
lightning serve
|
|
37
|
+
lightning serve server.py --cloud # deploy to the cloud
|
|
31
38
|
|
|
32
|
-
You can deploy the API to the cloud by running `lightning serve
|
|
39
|
+
You can deploy the API to the cloud by running `lightning serve server.py --cloud`.
|
|
33
40
|
This will build a docker container for the server.py script and deploy it to the Lightning AI platform.
|
|
34
41
|
"""
|
|
35
42
|
|
|
@@ -44,11 +51,11 @@ def serve() -> None:
|
|
|
44
51
|
help="Generate a client for the model",
|
|
45
52
|
)
|
|
46
53
|
@click.option(
|
|
47
|
-
"--
|
|
54
|
+
"--local",
|
|
48
55
|
is_flag=True,
|
|
49
56
|
default=False,
|
|
50
57
|
flag_value=True,
|
|
51
|
-
help="
|
|
58
|
+
help="Run the model locally",
|
|
52
59
|
)
|
|
53
60
|
@click.option("--name", default=None, help="Name of the deployed API (e.g., 'classification-api', 'Llama-api')")
|
|
54
61
|
@click.option(
|
|
@@ -109,7 +116,7 @@ def serve() -> None:
|
|
|
109
116
|
def api(
|
|
110
117
|
script_path: str,
|
|
111
118
|
easy: bool,
|
|
112
|
-
|
|
119
|
+
local: bool,
|
|
113
120
|
name: Optional[str],
|
|
114
121
|
non_interactive: bool,
|
|
115
122
|
machine: str,
|
|
@@ -128,7 +135,7 @@ def api(
|
|
|
128
135
|
return api_impl(
|
|
129
136
|
script_path=script_path,
|
|
130
137
|
easy=easy,
|
|
131
|
-
|
|
138
|
+
local=local,
|
|
132
139
|
repository=name,
|
|
133
140
|
non_interactive=non_interactive,
|
|
134
141
|
machine=machine,
|
|
@@ -148,7 +155,7 @@ def api(
|
|
|
148
155
|
def api_impl(
|
|
149
156
|
script_path: Union[str, Path],
|
|
150
157
|
easy: bool = False,
|
|
151
|
-
|
|
158
|
+
local: bool = False,
|
|
152
159
|
repository: [str] = None,
|
|
153
160
|
tag: Optional[str] = None,
|
|
154
161
|
non_interactive: bool = False,
|
|
@@ -178,7 +185,7 @@ def api_impl(
|
|
|
178
185
|
timestr = datetime.now().strftime("%b-%d-%H_%M")
|
|
179
186
|
repository = f"litserve-{timestr}".lower()
|
|
180
187
|
|
|
181
|
-
if
|
|
188
|
+
if not local:
|
|
182
189
|
repository = repository or "litserve-model"
|
|
183
190
|
machine = Machine.from_str(machine)
|
|
184
191
|
return _handle_cloud(
|
|
@@ -231,34 +238,21 @@ def _handle_cloud(
|
|
|
231
238
|
) -> None:
|
|
232
239
|
deployment_name = os.path.basename(repository)
|
|
233
240
|
tag = tag if tag else "latest"
|
|
241
|
+
|
|
242
|
+
if non_interactive:
|
|
243
|
+
console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
|
|
244
|
+
|
|
245
|
+
# Authenticate with LitServe affiliate
|
|
246
|
+
authenticate()
|
|
234
247
|
if teamspace is None:
|
|
235
248
|
menu = _TeamspacesMenu()
|
|
236
249
|
resolved_teamspace = menu._resolve_teamspace(teamspace)
|
|
237
250
|
else:
|
|
238
251
|
resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
|
|
239
252
|
|
|
240
|
-
if DeploymentApi().get_deployment_by_name(deployment_name, resolved_teamspace.id):
|
|
241
|
-
syntax = Syntax(
|
|
242
|
-
"from lightning_sdk import Deployment\n\n"
|
|
243
|
-
f"deployment = Deployment('{deployment_name}', teamspace={resolved_teamspace.name})\n"
|
|
244
|
-
"deployment.update(...)",
|
|
245
|
-
"python",
|
|
246
|
-
line_numbers=True,
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
console.print(
|
|
250
|
-
f"\nDeployment with name [b]{deployment_name}[/b] already running. "
|
|
251
|
-
"To update the deployment, use the Deployment API:\n",
|
|
252
|
-
syntax,
|
|
253
|
-
)
|
|
254
|
-
return
|
|
255
|
-
|
|
256
253
|
port = port or 8000
|
|
257
|
-
ls_deployer = _LitServeDeployer()
|
|
254
|
+
ls_deployer = _LitServeDeployer(name=deployment_name, teamspace=resolved_teamspace)
|
|
258
255
|
path = ls_deployer.dockerize_api(script_path, port=port, gpu=not machine.is_cpu(), tag=tag, print_success=False)
|
|
259
|
-
console.clear()
|
|
260
|
-
if non_interactive:
|
|
261
|
-
console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
|
|
262
256
|
|
|
263
257
|
console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
|
|
264
258
|
correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
|
|
@@ -266,10 +260,9 @@ def _handle_cloud(
|
|
|
266
260
|
console.print("Please fix the Dockerfile and try again.", style="red")
|
|
267
261
|
return
|
|
268
262
|
|
|
269
|
-
ls_deployer.authenticate()
|
|
270
263
|
# list containers to create the project if it doesn't exist
|
|
271
264
|
lit_cr = LitContainerApi()
|
|
272
|
-
lit_cr.list_containers(resolved_teamspace.id)
|
|
265
|
+
lit_cr.list_containers(resolved_teamspace.id, cloud_account=cloud_account)
|
|
273
266
|
|
|
274
267
|
with Progress(
|
|
275
268
|
SpinnerColumn(),
|
|
@@ -279,18 +272,35 @@ def _handle_cloud(
|
|
|
279
272
|
transient=True,
|
|
280
273
|
) as progress:
|
|
281
274
|
try:
|
|
282
|
-
|
|
283
|
-
|
|
275
|
+
# Build the container
|
|
276
|
+
build_task = progress.add_task("Building Docker image", total=None)
|
|
277
|
+
for line in ls_deployer.build_container(path, repository, tag):
|
|
278
|
+
console.print(line.strip())
|
|
279
|
+
progress.update(build_task, advance=1)
|
|
280
|
+
progress.update(build_task, description="[green]Build completed![/green]", completed=1.0)
|
|
281
|
+
progress.remove_task(build_task)
|
|
282
|
+
|
|
283
|
+
# Push the container to the registry
|
|
284
|
+
console.print("\nPushing image to registry. It may take a while...", style="bold")
|
|
285
|
+
push_task = progress.add_task("Pushing to registry", total=None)
|
|
286
|
+
push_status = {}
|
|
287
|
+
for line in ls_deployer.push_container(
|
|
288
|
+
repository, tag, resolved_teamspace, lit_cr, cloud_account=cloud_account
|
|
289
|
+
):
|
|
290
|
+
push_status = line
|
|
291
|
+
progress.update(push_task, advance=1)
|
|
292
|
+
if not ("Pushing" in line["status"] or "Waiting" in line["status"]):
|
|
293
|
+
console.print(line["status"])
|
|
294
|
+
progress.update(push_task, description="[green]Push completed![/green]")
|
|
284
295
|
except Exception as e:
|
|
285
296
|
console.print(f"❌ Deployment failed: {e}", style="red")
|
|
286
297
|
return
|
|
287
298
|
console.print(f"\n✅ Image pushed to {repository}:{tag}")
|
|
288
|
-
|
|
289
|
-
repository = push_status.get("repository")
|
|
299
|
+
image = push_status.get("image")
|
|
290
300
|
|
|
291
|
-
deployment_status = ls_deployer.
|
|
301
|
+
deployment_status = ls_deployer.run_on_cloud(
|
|
292
302
|
deployment_name=deployment_name,
|
|
293
|
-
image=
|
|
303
|
+
image=image,
|
|
294
304
|
teamspace=resolved_teamspace,
|
|
295
305
|
metric=None,
|
|
296
306
|
machine=machine,
|
lightning_sdk/cli/upload.py
CHANGED
|
@@ -11,11 +11,12 @@ from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
|
|
11
11
|
from simple_term_menu import TerminalMenu
|
|
12
12
|
from tqdm import tqdm
|
|
13
13
|
|
|
14
|
-
from lightning_sdk.api.lit_container_api import LCRAuthFailedError, LitContainerApi
|
|
14
|
+
from lightning_sdk.api.lit_container_api import DockerNotRunningError, LCRAuthFailedError, LitContainerApi
|
|
15
15
|
from lightning_sdk.api.utils import _get_cloud_url
|
|
16
16
|
from lightning_sdk.cli.exceptions import StudioCliError
|
|
17
17
|
from lightning_sdk.cli.studios_menu import _StudiosMenu
|
|
18
18
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
19
|
+
from lightning_sdk.constants import _LIGHTNING_DEBUG
|
|
19
20
|
from lightning_sdk.models import upload_model as _upload_model
|
|
20
21
|
from lightning_sdk.studio import Studio
|
|
21
22
|
from lightning_sdk.utils.resolve import _get_authed_user, skip_studio_init
|
|
@@ -116,11 +117,28 @@ def file(path: str, studio: Optional[str] = None, remote_path: Optional[str] = N
|
|
|
116
117
|
"If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
117
118
|
),
|
|
118
119
|
)
|
|
119
|
-
|
|
120
|
+
@click.option(
|
|
121
|
+
"--cloud-account",
|
|
122
|
+
"--cloud_account", # The UI will present the above variant, using this as a secondary to be consistent w/ models
|
|
123
|
+
default=None,
|
|
124
|
+
help="The name of the cloud account to store the Container in.",
|
|
125
|
+
)
|
|
126
|
+
@click.option(
|
|
127
|
+
"--platform",
|
|
128
|
+
"--platform",
|
|
129
|
+
default="linux/amd64",
|
|
130
|
+
help="This is the platform the container pulled and push to Lightning AI will run on.",
|
|
131
|
+
)
|
|
132
|
+
def upload_container(
|
|
133
|
+
container: str,
|
|
134
|
+
tag: str = "latest",
|
|
135
|
+
teamspace: Optional[str] = None,
|
|
136
|
+
cloud_account: Optional[str] = None,
|
|
137
|
+
platform: Optional[str] = "linux/amd64",
|
|
138
|
+
) -> None:
|
|
120
139
|
"""Upload a container to Lightning AI's container registry."""
|
|
121
140
|
menu = _TeamspacesMenu()
|
|
122
141
|
teamspace = menu._resolve_teamspace(teamspace)
|
|
123
|
-
api = LitContainerApi()
|
|
124
142
|
console = Console()
|
|
125
143
|
with Progress(
|
|
126
144
|
SpinnerColumn(),
|
|
@@ -129,8 +147,13 @@ def upload_container(container: str, tag: str = "latest", teamspace: Optional[st
|
|
|
129
147
|
console=console,
|
|
130
148
|
transient=False,
|
|
131
149
|
) as progress:
|
|
132
|
-
push_task = progress.add_task("Pushing Docker image", total=None)
|
|
133
150
|
try:
|
|
151
|
+
if platform != "linux/amd64":
|
|
152
|
+
console.print(
|
|
153
|
+
"[yellow]Warning: The platform you selected (" + platform + ") may not deploy successfully[/yellow]"
|
|
154
|
+
)
|
|
155
|
+
api = LitContainerApi()
|
|
156
|
+
push_task = progress.add_task("Pushing Docker image", total=None)
|
|
134
157
|
console.print("Authenticating with Lightning Container Registry...")
|
|
135
158
|
try:
|
|
136
159
|
api.authenticate()
|
|
@@ -139,15 +162,27 @@ def upload_container(container: str, tag: str = "latest", teamspace: Optional[st
|
|
|
139
162
|
# let the push with retry take control of auth moving forward
|
|
140
163
|
pass
|
|
141
164
|
|
|
142
|
-
lines = api.upload_container(container, teamspace, tag)
|
|
165
|
+
lines = api.upload_container(container, teamspace, tag, cloud_account, platform)
|
|
143
166
|
_print_docker_push(lines, console, progress, push_task)
|
|
167
|
+
except DockerNotRunningError as e:
|
|
168
|
+
e.print_help()
|
|
169
|
+
return
|
|
144
170
|
except LCRAuthFailedError:
|
|
145
171
|
console.print("Re-authenticating with Lightning Container Registry...")
|
|
146
172
|
if not api.authenticate(reauth=True):
|
|
147
173
|
raise StudioCliError("Failed to authenticate with Lightning Container Registry") from None
|
|
148
174
|
console.print("Authenticated with Lightning Container Registry", style="green")
|
|
149
|
-
lines = api.upload_container(container, teamspace, tag)
|
|
175
|
+
lines = api.upload_container(container, teamspace, tag, cloud_account, platform)
|
|
150
176
|
_print_docker_push(lines, console, progress, push_task)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
if _LIGHTNING_DEBUG:
|
|
179
|
+
print(e)
|
|
180
|
+
if e.__cause__:
|
|
181
|
+
print(e.__cause__)
|
|
182
|
+
|
|
183
|
+
progress.update(push_task, description=f"[bold red]Error: {e!s}[/]")
|
|
184
|
+
progress.stop()
|
|
185
|
+
return
|
|
151
186
|
progress.update(push_task, description="[green]Container pushed![/green]")
|
|
152
187
|
|
|
153
188
|
|
|
@@ -66,6 +66,8 @@ from lightning_sdk.lightning_cloud.openapi.configuration import Configuration
|
|
|
66
66
|
from lightning_sdk.lightning_cloud.openapi.models.affiliatelinks_id_body import AffiliatelinksIdBody
|
|
67
67
|
from lightning_sdk.lightning_cloud.openapi.models.agentmanagedendpoints_id_body import AgentmanagedendpointsIdBody
|
|
68
68
|
from lightning_sdk.lightning_cloud.openapi.models.agents_id_body import AgentsIdBody
|
|
69
|
+
from lightning_sdk.lightning_cloud.openapi.models.alerts_config_billing import AlertsConfigBilling
|
|
70
|
+
from lightning_sdk.lightning_cloud.openapi.models.alerts_config_studios import AlertsConfigStudios
|
|
69
71
|
from lightning_sdk.lightning_cloud.openapi.models.app_id_works_body import AppIdWorksBody
|
|
70
72
|
from lightning_sdk.lightning_cloud.openapi.models.appinstances_id_body import AppinstancesIdBody
|
|
71
73
|
from lightning_sdk.lightning_cloud.openapi.models.approveautojoindomain_domain_body import ApproveautojoindomainDomainBody
|
|
@@ -233,6 +235,8 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_agent_job import V1AgentJob
|
|
|
233
235
|
from lightning_sdk.lightning_cloud.openapi.models.v1_agent_job_artifact import V1AgentJobArtifact
|
|
234
236
|
from lightning_sdk.lightning_cloud.openapi.models.v1_agent_upload_multipart_url import V1AgentUploadMultipartUrl
|
|
235
237
|
from lightning_sdk.lightning_cloud.openapi.models.v1_agent_upload_part_response import V1AgentUploadPartResponse
|
|
238
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_alert_method import V1AlertMethod
|
|
239
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_alerts_config import V1AlertsConfig
|
|
236
240
|
from lightning_sdk.lightning_cloud.openapi.models.v1_api_pricing_spec import V1ApiPricingSpec
|
|
237
241
|
from lightning_sdk.lightning_cloud.openapi.models.v1_app_type import V1AppType
|
|
238
242
|
from lightning_sdk.lightning_cloud.openapi.models.v1_append_logger_metrics_response import V1AppendLoggerMetricsResponse
|
|
@@ -272,6 +276,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event
|
|
|
272
276
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_artifact_event_type import V1CloudSpaceArtifactEventType
|
|
273
277
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version import V1CloudSpaceCodeVersion
|
|
274
278
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_code_version_status import V1CloudSpaceCodeVersionStatus
|
|
279
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_cold_start_metrics import V1CloudSpaceColdStartMetrics
|
|
275
280
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_engagement_response import V1CloudSpaceEngagementResponse
|
|
276
281
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template import V1CloudSpaceEnvironmentTemplate
|
|
277
282
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cloud_space_environment_template_config import V1CloudSpaceEnvironmentTemplateConfig
|
|
@@ -574,6 +579,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_list_agent_job_artifacts_re
|
|
|
574
579
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_agent_jobs_response import V1ListAgentJobsResponse
|
|
575
580
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_assistants_response import V1ListAssistantsResponse
|
|
576
581
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_apps_response import V1ListCloudSpaceAppsResponse
|
|
582
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_cold_start_metrics_response import V1ListCloudSpaceColdStartMetricsResponse
|
|
577
583
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_environment_templates_response import V1ListCloudSpaceEnvironmentTemplatesResponse
|
|
578
584
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_instances_response import V1ListCloudSpaceInstancesResponse
|
|
579
585
|
from lightning_sdk.lightning_cloud.openapi.models.v1_list_cloud_space_publications_response import V1ListCloudSpacePublicationsResponse
|
|
@@ -768,6 +774,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_request import V1Re
|
|
|
768
774
|
from lightning_sdk.lightning_cloud.openapi.models.v1_refresh_response import V1RefreshResponse
|
|
769
775
|
from lightning_sdk.lightning_cloud.openapi.models.v1_region_state import V1RegionState
|
|
770
776
|
from lightning_sdk.lightning_cloud.openapi.models.v1_regional_load_balancer import V1RegionalLoadBalancer
|
|
777
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_report_cloud_space_instance_stop_at_response import V1ReportCloudSpaceInstanceStopAtResponse
|
|
771
778
|
from lightning_sdk.lightning_cloud.openapi.models.v1_report_logs_activity_response import V1ReportLogsActivityResponse
|
|
772
779
|
from lightning_sdk.lightning_cloud.openapi.models.v1_report_restart_timings_response import V1ReportRestartTimingsResponse
|
|
773
780
|
from lightning_sdk.lightning_cloud.openapi.models.v1_request_cluster_access_request import V1RequestClusterAccessRequest
|
|
@@ -802,6 +809,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_secret import V1Secret
|
|
|
802
809
|
from lightning_sdk.lightning_cloud.openapi.models.v1_secret_type import V1SecretType
|
|
803
810
|
from lightning_sdk.lightning_cloud.openapi.models.v1_select import V1Select
|
|
804
811
|
from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert import V1ServerAlert
|
|
812
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_phase import V1ServerAlertPhase
|
|
805
813
|
from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_severity import V1ServerAlertSeverity
|
|
806
814
|
from lightning_sdk.lightning_cloud.openapi.models.v1_server_alert_type import V1ServerAlertType
|
|
807
815
|
from lightning_sdk.lightning_cloud.openapi.models.v1_server_check_in_response import V1ServerCheckInResponse
|