lightning-sdk 0.2.21rc1__py3-none-any.whl → 0.2.23__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/license_api.py +2 -2
- lightning_sdk/api/llm_api.py +69 -27
- lightning_sdk/api/pipeline_api.py +49 -9
- lightning_sdk/api/studio_api.py +2 -0
- lightning_sdk/cli/configure.py +34 -27
- lightning_sdk/cli/connect.py +2 -2
- lightning_sdk/cli/download.py +38 -2
- lightning_sdk/cli/entrypoint.py +15 -13
- lightning_sdk/cli/start.py +5 -2
- lightning_sdk/lightning_cloud/openapi/__init__.py +9 -0
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +206 -0
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +98 -5
- lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +113 -0
- lightning_sdk/lightning_cloud/openapi/api/pipelines_service_api.py +118 -1
- lightning_sdk/lightning_cloud/openapi/api/schedules_service_api.py +5 -1
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +9 -0
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/cloudspace_id_visibility_body.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/metricsstream_create_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/pipelines_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/project_id_pipelines_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/project_id_schedules_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/schedules_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_check_cluster_name_availability_request.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_check_cluster_name_availability_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_session.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_registry_repository_image_artifact_version_by_digest_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster.py +253 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster_spec.py +827 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_project_storage_metadata_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_storage_breakdown_response.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_instance_overprovisioning_spec.py +29 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_lightning_run.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_list_clusters_response.py +6 -6
- lightning_sdk/lightning_cloud/openapi/models/v1_list_project_clusters_response.py +6 -6
- lightning_sdk/lightning_cloud/openapi/models/v1_lite_published_cloud_space_response.py +513 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_metrics_stream.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_pipeline.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_schedule.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_shared_filesystem.py +131 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_update_cloud_space_visibility_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +53 -183
- lightning_sdk/lightning_cloud/openapi/models/v1_volume.py +78 -104
- lightning_sdk/lightning_cloud/openapi/models/v1_volume_state.py +104 -0
- lightning_sdk/llm/llm.py +6 -1
- lightning_sdk/pipeline/__init__.py +12 -2
- lightning_sdk/pipeline/pipeline.py +59 -17
- lightning_sdk/pipeline/printer.py +121 -0
- lightning_sdk/pipeline/schedule.py +8 -0
- lightning_sdk/pipeline/{types.py → steps.py} +77 -59
- lightning_sdk/pipeline/utils.py +39 -17
- lightning_sdk/sandbox.py +157 -0
- lightning_sdk/services/license.py +12 -6
- lightning_sdk/studio.py +7 -1
- {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/RECORD +70 -58
- {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
lightning_sdk/api/license_api.py
CHANGED
|
@@ -10,8 +10,8 @@ LICENSE_CODE = os.environ.get("LICENSE_CODE", "d9s79g79ss")
|
|
|
10
10
|
LICENSE_SIGNING_URL = f"{env.LIGHTNING_CLOUD_URL}?settings=licenses"
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def generate_url_user_settings(redirect_to: str = LICENSE_SIGNING_URL) -> str:
|
|
14
|
-
params = urlencode({"redirectTo": redirect_to, "okbhrt": LICENSE_CODE})
|
|
13
|
+
def generate_url_user_settings(name: str, redirect_to: str = LICENSE_SIGNING_URL) -> str:
|
|
14
|
+
params = urlencode({"redirectTo": redirect_to, "okbhrt": LICENSE_CODE, "licenseName": name})
|
|
15
15
|
return f"{env.LIGHTNING_CLOUD_URL}/sign-in?{params}"
|
|
16
16
|
|
|
17
17
|
|
lightning_sdk/api/llm_api.py
CHANGED
|
@@ -2,7 +2,9 @@ import asyncio
|
|
|
2
2
|
import base64
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
-
|
|
5
|
+
import threading
|
|
6
|
+
import warnings
|
|
7
|
+
from typing import Any, AsyncGenerator, Dict, Generator, List, Optional, Union
|
|
6
8
|
|
|
7
9
|
from pip._vendor.urllib3 import HTTPResponse
|
|
8
10
|
|
|
@@ -28,35 +30,40 @@ class LLMApi:
|
|
|
28
30
|
result = self._client.assistants_service_list_assistants(user_id=user_id)
|
|
29
31
|
return result.assistants
|
|
30
32
|
|
|
33
|
+
def _parse_stream_line(self, decoded_line: str) -> Optional[V1ConversationResponseChunk]:
|
|
34
|
+
try:
|
|
35
|
+
payload = json.loads(decoded_line)
|
|
36
|
+
result_data = payload.get("result", {})
|
|
37
|
+
|
|
38
|
+
choices = []
|
|
39
|
+
for choice in result_data.get("choices", []):
|
|
40
|
+
delta = choice.get("delta", {})
|
|
41
|
+
choices.append(
|
|
42
|
+
V1ResponseChoice(
|
|
43
|
+
delta=V1ResponseChoiceDelta(**delta),
|
|
44
|
+
finish_reason=choice.get("finishReason"),
|
|
45
|
+
index=choice.get("index"),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return V1ConversationResponseChunk(
|
|
50
|
+
choices=choices,
|
|
51
|
+
conversation_id=result_data.get("conversationId"),
|
|
52
|
+
executable=result_data.get("executable"),
|
|
53
|
+
id=result_data.get("id"),
|
|
54
|
+
throughput=result_data.get("throughput"),
|
|
55
|
+
)
|
|
56
|
+
except json.JSONDecodeError:
|
|
57
|
+
warnings.warn("Error decoding JSON:", decoded_line)
|
|
58
|
+
return None
|
|
59
|
+
|
|
31
60
|
def _stream_chat_response(self, result: HTTPResponse) -> Generator[V1ConversationResponseChunk, None, None]:
|
|
32
61
|
for line in result.stream():
|
|
33
62
|
decoded_lines = line.decode("utf-8").strip()
|
|
34
63
|
for decoded_line in decoded_lines.splitlines():
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
choices = []
|
|
40
|
-
for choice in result_data.get("choices", []):
|
|
41
|
-
delta = choice.get("delta", {})
|
|
42
|
-
choices.append(
|
|
43
|
-
V1ResponseChoice(
|
|
44
|
-
delta=V1ResponseChoiceDelta(**delta),
|
|
45
|
-
finish_reason=choice.get("finishReason"),
|
|
46
|
-
index=choice.get("index"),
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
yield V1ConversationResponseChunk(
|
|
51
|
-
choices=choices,
|
|
52
|
-
conversation_id=result_data.get("conversationId"),
|
|
53
|
-
executable=result_data.get("executable"),
|
|
54
|
-
id=result_data.get("id"),
|
|
55
|
-
throughput=result_data.get("throughput"),
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
except json.JSONDecodeError:
|
|
59
|
-
print("Error decoding JSON:", decoded_line)
|
|
64
|
+
chunk = self._parse_stream_line(decoded_line)
|
|
65
|
+
if chunk:
|
|
66
|
+
yield chunk
|
|
60
67
|
|
|
61
68
|
def _encode_image_bytes_to_data_url(self, image: str) -> str:
|
|
62
69
|
with open(image, "rb") as image_file:
|
|
@@ -92,6 +99,7 @@ class LLMApi:
|
|
|
92
99
|
"stream": stream,
|
|
93
100
|
"metadata": metadata or {},
|
|
94
101
|
"internal_conversation": is_internal_conversation,
|
|
102
|
+
"system_prompt": system_prompt,
|
|
95
103
|
}
|
|
96
104
|
if images:
|
|
97
105
|
for image in images:
|
|
@@ -139,6 +147,7 @@ class LLMApi:
|
|
|
139
147
|
"stream": stream,
|
|
140
148
|
"metadata": metadata or {},
|
|
141
149
|
"internal_conversation": is_internal_conversation,
|
|
150
|
+
"system_prompt": system_prompt,
|
|
142
151
|
}
|
|
143
152
|
if images:
|
|
144
153
|
for image in images:
|
|
@@ -160,7 +169,40 @@ class LLMApi:
|
|
|
160
169
|
result = await asyncio.to_thread(thread.get)
|
|
161
170
|
return result.result
|
|
162
171
|
|
|
163
|
-
|
|
172
|
+
conversation_thread = await asyncio.to_thread(
|
|
173
|
+
self._client.assistants_service_start_conversation,
|
|
174
|
+
body,
|
|
175
|
+
assistant_id,
|
|
176
|
+
async_req=True,
|
|
177
|
+
_preload_content=False,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return self.stream_response(conversation_thread)
|
|
181
|
+
|
|
182
|
+
async def stream_response(self, thread: Any) -> AsyncGenerator[V1ConversationResponseChunk, None]:
|
|
183
|
+
loop = asyncio.get_event_loop()
|
|
184
|
+
response = await asyncio.to_thread(thread.get)
|
|
185
|
+
|
|
186
|
+
queue = asyncio.Queue()
|
|
187
|
+
|
|
188
|
+
def enqueue() -> None:
|
|
189
|
+
try:
|
|
190
|
+
for line in response:
|
|
191
|
+
decoded_lines = line.decode("utf-8").strip()
|
|
192
|
+
for decoded_line in decoded_lines.splitlines():
|
|
193
|
+
chunk = self._parse_stream_line(decoded_line)
|
|
194
|
+
if chunk:
|
|
195
|
+
asyncio.run_coroutine_threadsafe(queue.put(chunk), loop)
|
|
196
|
+
finally:
|
|
197
|
+
asyncio.run_coroutine_threadsafe(queue.put(None), loop)
|
|
198
|
+
|
|
199
|
+
threading.Thread(target=enqueue, daemon=True).start()
|
|
200
|
+
|
|
201
|
+
while True:
|
|
202
|
+
item = await queue.get()
|
|
203
|
+
if item is None:
|
|
204
|
+
break
|
|
205
|
+
yield item
|
|
164
206
|
|
|
165
207
|
def list_conversations(self, assistant_id: str) -> List[str]:
|
|
166
208
|
result = self._client.assistants_service_list_conversations(assistant_id)
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
from typing import List
|
|
1
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
2
2
|
|
|
3
3
|
from lightning_sdk.lightning_cloud.openapi.models import (
|
|
4
4
|
ProjectIdPipelinesBody,
|
|
5
|
+
ProjectIdSchedulesBody,
|
|
5
6
|
V1DeletePipelineResponse,
|
|
6
7
|
V1Pipeline,
|
|
7
8
|
V1PipelineStep,
|
|
9
|
+
V1ScheduleResourceType,
|
|
8
10
|
V1SharedFilesystem,
|
|
9
11
|
)
|
|
10
12
|
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
11
13
|
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
12
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from lightning_sdk.pipeline.schedule import Schedule
|
|
17
|
+
|
|
13
18
|
|
|
14
19
|
class PipelineApi:
|
|
15
20
|
"""Internal API client for Pipeline requests (mainly http requests)."""
|
|
@@ -17,13 +22,23 @@ class PipelineApi:
|
|
|
17
22
|
def __init__(self) -> None:
|
|
18
23
|
self._client = LightningClient(retry=False, max_tries=0)
|
|
19
24
|
|
|
20
|
-
def get_pipeline_by_id(self, project_id: str,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
def get_pipeline_by_id(self, project_id: str, pipeline_id_or_name: str) -> Optional[V1Pipeline]:
|
|
26
|
+
if pipeline_id_or_name.startswith("pip_"):
|
|
27
|
+
try:
|
|
28
|
+
return self._client.pipelines_service_get_pipeline(project_id=project_id, id=pipeline_id_or_name)
|
|
29
|
+
except ApiException as ex:
|
|
30
|
+
if "not found" in str(ex):
|
|
31
|
+
return None
|
|
32
|
+
raise ex
|
|
33
|
+
else:
|
|
34
|
+
try:
|
|
35
|
+
return self._client.pipelines_service_get_pipeline_by_name(
|
|
36
|
+
project_id=project_id, name=pipeline_id_or_name
|
|
37
|
+
)
|
|
38
|
+
except ApiException as ex:
|
|
39
|
+
if "not found" in str(ex):
|
|
40
|
+
return None
|
|
41
|
+
raise ex
|
|
27
42
|
|
|
28
43
|
def create_pipeline(
|
|
29
44
|
self,
|
|
@@ -31,15 +46,40 @@ class PipelineApi:
|
|
|
31
46
|
project_id: str,
|
|
32
47
|
steps: List["V1PipelineStep"],
|
|
33
48
|
shared_filesystem: bool,
|
|
49
|
+
schedules: List["Schedule"],
|
|
50
|
+
parent_pipeline_id: Optional[str],
|
|
34
51
|
) -> V1Pipeline:
|
|
35
52
|
body = ProjectIdPipelinesBody(
|
|
36
53
|
name=name,
|
|
37
54
|
steps=steps,
|
|
38
55
|
shared_filesystem=V1SharedFilesystem(
|
|
39
56
|
enabled=shared_filesystem,
|
|
57
|
+
s3_folder=True,
|
|
40
58
|
),
|
|
59
|
+
parent_pipeline_id=parent_pipeline_id or "",
|
|
41
60
|
)
|
|
42
|
-
|
|
61
|
+
|
|
62
|
+
pipeline = self._client.pipelines_service_create_pipeline(body, project_id)
|
|
63
|
+
|
|
64
|
+
# Delete the previous schedules
|
|
65
|
+
if parent_pipeline_id is not None:
|
|
66
|
+
current_schedules = self._client.schedules_service_list_schedules(project_id).schedules
|
|
67
|
+
for schedule in current_schedules:
|
|
68
|
+
self._client.schedules_service_delete_schedule(project_id, schedule.id)
|
|
69
|
+
|
|
70
|
+
if len(schedules):
|
|
71
|
+
for schedule in schedules:
|
|
72
|
+
body = ProjectIdSchedulesBody(
|
|
73
|
+
cron_expression=schedule.cron_expression,
|
|
74
|
+
display_name=schedule.name,
|
|
75
|
+
resource_id=pipeline.id,
|
|
76
|
+
parent_resource_id=parent_pipeline_id or "",
|
|
77
|
+
resource_type=V1ScheduleResourceType.PIPELINE,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self._client.schedules_service_create_schedule(body, project_id)
|
|
81
|
+
|
|
82
|
+
return pipeline
|
|
43
83
|
|
|
44
84
|
def stop(self, pipeline: V1Pipeline) -> V1Pipeline:
|
|
45
85
|
body = pipeline
|
lightning_sdk/api/studio_api.py
CHANGED
|
@@ -123,6 +123,7 @@ class StudioApi:
|
|
|
123
123
|
teamspace_id: str,
|
|
124
124
|
cloud_account: Optional[str] = None,
|
|
125
125
|
source: Optional[V1CloudSpaceSourceType] = None,
|
|
126
|
+
disable_secrets: bool = False,
|
|
126
127
|
) -> V1CloudSpace:
|
|
127
128
|
"""Create a Studio with a given name in a given Teamspace on a possibly given cloud_account."""
|
|
128
129
|
body = ProjectIdCloudspacesBody(
|
|
@@ -131,6 +132,7 @@ class StudioApi:
|
|
|
131
132
|
display_name=name,
|
|
132
133
|
seed_files=[V1CloudSpaceSeedFile(path="main.py", contents="print('Hello, Lightning World!')\n")],
|
|
133
134
|
source=source,
|
|
135
|
+
disable_secrets=disable_secrets,
|
|
134
136
|
)
|
|
135
137
|
studio = self._client.cloud_space_service_create_cloud_space(body, teamspace_id)
|
|
136
138
|
|
lightning_sdk/cli/configure.py
CHANGED
|
@@ -17,33 +17,10 @@ def configure() -> None:
|
|
|
17
17
|
"""Configure access to resources on the Lightning AI platform."""
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
help=(
|
|
25
|
-
"The name of the studio to obtain SSH config. "
|
|
26
|
-
"If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
27
|
-
),
|
|
28
|
-
)
|
|
29
|
-
@click.option(
|
|
30
|
-
"--teamspace",
|
|
31
|
-
default=None,
|
|
32
|
-
help=(
|
|
33
|
-
"The teamspace the studio is part of. "
|
|
34
|
-
"Should be of format <OWNER>/<TEAMSPACE_NAME>. "
|
|
35
|
-
"If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
36
|
-
),
|
|
37
|
-
)
|
|
38
|
-
@click.option(
|
|
39
|
-
"--overwrite",
|
|
40
|
-
is_flag=True,
|
|
41
|
-
flag_value=True,
|
|
42
|
-
default=False,
|
|
43
|
-
help="Whether to overwrite the SSH key and config if they already exist.",
|
|
44
|
-
)
|
|
45
|
-
def ssh(name: Optional[str] = None, teamspace: Optional[str] = None, overwrite: bool = False) -> None:
|
|
46
|
-
"""Get SSH config entry for a studio."""
|
|
20
|
+
def _configure_ssh_internal(
|
|
21
|
+
name: Optional[str] = None, teamspace: Optional[str] = None, overwrite: bool = False
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Internal function to configure SSH without Click decorators."""
|
|
47
24
|
auth = Auth()
|
|
48
25
|
auth.authenticate()
|
|
49
26
|
console = Console()
|
|
@@ -78,6 +55,36 @@ def ssh(name: Optional[str] = None, teamspace: Optional[str] = None, overwrite:
|
|
|
78
55
|
console.print(f"SSH config updated at {config_path}")
|
|
79
56
|
|
|
80
57
|
|
|
58
|
+
@configure.command(name="ssh")
|
|
59
|
+
@click.option(
|
|
60
|
+
"--name",
|
|
61
|
+
default=None,
|
|
62
|
+
help=(
|
|
63
|
+
"The name of the studio to obtain SSH config. "
|
|
64
|
+
"If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
"--teamspace",
|
|
69
|
+
default=None,
|
|
70
|
+
help=(
|
|
71
|
+
"The teamspace the studio is part of. "
|
|
72
|
+
"Should be of format <OWNER>/<TEAMSPACE_NAME>. "
|
|
73
|
+
"If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
@click.option(
|
|
77
|
+
"--overwrite",
|
|
78
|
+
is_flag=True,
|
|
79
|
+
flag_value=True,
|
|
80
|
+
default=False,
|
|
81
|
+
help="Whether to overwrite the SSH key and config if they already exist.",
|
|
82
|
+
)
|
|
83
|
+
def ssh(name: Optional[str] = None, teamspace: Optional[str] = None, overwrite: bool = False) -> None:
|
|
84
|
+
"""Get SSH config entry for a studio."""
|
|
85
|
+
_configure_ssh_internal(name=name, teamspace=teamspace, overwrite=overwrite)
|
|
86
|
+
|
|
87
|
+
|
|
81
88
|
def _download_file(url: str, local_path: Path, overwrite: bool = True, chmod: Optional[int] = None) -> None:
|
|
82
89
|
"""Download a file from a URL."""
|
|
83
90
|
import requests
|
lightning_sdk/cli/connect.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from lightning_sdk.cli.configure import
|
|
7
|
+
from lightning_sdk.cli.configure import _configure_ssh_internal
|
|
8
8
|
from lightning_sdk.cli.studios_menu import _StudiosMenu
|
|
9
9
|
|
|
10
10
|
|
|
@@ -22,7 +22,7 @@ def connect() -> None:
|
|
|
22
22
|
)
|
|
23
23
|
def studio(name: Optional[str], teamspace: Optional[str]) -> None:
|
|
24
24
|
"""Connect to a studio via SSH."""
|
|
25
|
-
|
|
25
|
+
_configure_ssh_internal(name=name, teamspace=teamspace, overwrite=False)
|
|
26
26
|
|
|
27
27
|
menu = _StudiosMenu()
|
|
28
28
|
studio = menu._get_studio(name=name, teamspace=teamspace)
|
lightning_sdk/cli/download.py
CHANGED
|
@@ -241,8 +241,8 @@ def _resolve_studio(studio: Optional[str]) -> Studio:
|
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
@download.command(name="licenses")
|
|
244
|
-
def
|
|
245
|
-
"""Download licenses for all products/packages.
|
|
244
|
+
def download_licenses() -> None:
|
|
245
|
+
"""Download licenses for all user's products/packages.
|
|
246
246
|
|
|
247
247
|
Example:
|
|
248
248
|
lightning download licenses
|
|
@@ -261,3 +261,39 @@ def licenses() -> None:
|
|
|
261
261
|
with licenses_file.open("w") as fp:
|
|
262
262
|
json.dump(licenses_short, fp, indent=4)
|
|
263
263
|
Console().print(f"Licenses downloaded to {licenses_file}", style="green")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@download.command(name="license")
|
|
267
|
+
@click.argument("name")
|
|
268
|
+
def download_license(name: str) -> None:
|
|
269
|
+
"""Download license for specific products/packages.
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
lightning download license NAME
|
|
273
|
+
|
|
274
|
+
NAME: The name of the product/package to download the license for.
|
|
275
|
+
"""
|
|
276
|
+
user = _get_authed_user()
|
|
277
|
+
api = LicenseApi()
|
|
278
|
+
licenses = api.list_user_licenses(user.id)
|
|
279
|
+
licenses_short = {ll.product_name: ll.license_key for ll in licenses if ll.is_valid}
|
|
280
|
+
|
|
281
|
+
if name not in licenses_short:
|
|
282
|
+
Console().print(f"Missing valid license for {name}", style="red")
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
user_home = Path.home()
|
|
286
|
+
lit_dir = user_home / ".lightning"
|
|
287
|
+
lit_dir.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
licenses_file = lit_dir / "licenses.json"
|
|
289
|
+
|
|
290
|
+
licenses_loaded = {}
|
|
291
|
+
if licenses_file.exists():
|
|
292
|
+
with licenses_file.open("r") as fp:
|
|
293
|
+
licenses_loaded = json.load(fp)
|
|
294
|
+
|
|
295
|
+
licenses_loaded[name] = licenses_short[name]
|
|
296
|
+
|
|
297
|
+
with licenses_file.open("w") as fp:
|
|
298
|
+
json.dump(licenses_loaded, fp, indent=4)
|
|
299
|
+
Console().print(f"Updated license for {name} in {licenses_file}", style="green")
|
lightning_sdk/cli/entrypoint.py
CHANGED
|
@@ -4,8 +4,9 @@ from types import TracebackType
|
|
|
4
4
|
from typing import Type
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
|
-
from rich.console import Console
|
|
7
|
+
from rich.console import Console, Group
|
|
8
8
|
from rich.panel import Panel
|
|
9
|
+
from rich.syntax import Syntax
|
|
9
10
|
from rich.text import Text
|
|
10
11
|
|
|
11
12
|
from lightning_sdk import __version__
|
|
@@ -34,23 +35,24 @@ from lightning_sdk.lightning_cloud.login import Auth
|
|
|
34
35
|
|
|
35
36
|
def _notify_exception(exception_type: Type[BaseException], value: BaseException, tb: TracebackType) -> None:
|
|
36
37
|
"""CLI won't show tracebacks, just print the exception message."""
|
|
37
|
-
# if debug mode, print the traceback using rich
|
|
38
38
|
console = Console()
|
|
39
|
-
if value.args:
|
|
40
|
-
message = str(value.args[0]) if value.args[0] else str(value)
|
|
41
|
-
else:
|
|
42
|
-
message = str(value) or "An unknown error occurred"
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
message = str(value.args[0]) if value.args else str(value) or "An unknown error occurred"
|
|
41
|
+
|
|
42
|
+
error_text = Text()
|
|
43
|
+
error_text.append(f"{exception_type.__name__}: ", style="bold red")
|
|
44
|
+
error_text.append(message, style="white")
|
|
45
|
+
|
|
46
|
+
renderables = [error_text]
|
|
47
47
|
|
|
48
48
|
if _LIGHTNING_DEBUG:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
tb_text = "".join(traceback.format_exception(exception_type, value, tb))
|
|
50
|
+
renderables.append(Text("\n\nFull traceback:\n", style="bold yellow"))
|
|
51
|
+
renderables.append(Syntax(tb_text, "python", theme="monokai", line_numbers=False, word_wrap=True))
|
|
52
|
+
else:
|
|
53
|
+
renderables.append(Text("\n\nTo see the full traceback, set the LIGHTNING_DEBUG environment variable to 1."))
|
|
52
54
|
|
|
53
|
-
console.print(Panel(
|
|
55
|
+
console.print(Panel(Group(*renderables), title="⚡ Lightning CLI Error", border_style="red"))
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@click.group(name="lightning", help="Command line interface (CLI) to interact with/manage Lightning AI Studios.")
|
lightning_sdk/cli/start.py
CHANGED
|
@@ -60,8 +60,11 @@ def studio(name: str, teamspace: Optional[str] = None, machine: str = "CPU", pro
|
|
|
60
60
|
|
|
61
61
|
try:
|
|
62
62
|
studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False, provider=provider)
|
|
63
|
-
except (RuntimeError, ValueError, ApiException):
|
|
64
|
-
|
|
63
|
+
except (RuntimeError, ValueError, ApiException) as first_error:
|
|
64
|
+
try:
|
|
65
|
+
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False, provider=provider)
|
|
66
|
+
except (RuntimeError, ValueError, ApiException) as second_error:
|
|
67
|
+
raise first_error from second_error
|
|
65
68
|
|
|
66
69
|
try:
|
|
67
70
|
resolved_machine = getattr(Machine, machine.upper(), Machine(machine, machine))
|
|
@@ -89,6 +89,7 @@ from lightning_sdk.lightning_cloud.openapi.models.cloud_space_id_versions_body i
|
|
|
89
89
|
from lightning_sdk.lightning_cloud.openapi.models.cloudspace_id_metric_body import CloudspaceIdMetricBody
|
|
90
90
|
from lightning_sdk.lightning_cloud.openapi.models.cloudspace_id_runs_body import CloudspaceIdRunsBody
|
|
91
91
|
from lightning_sdk.lightning_cloud.openapi.models.cloudspace_id_systemmetrics_body import CloudspaceIdSystemmetricsBody
|
|
92
|
+
from lightning_sdk.lightning_cloud.openapi.models.cloudspace_id_visibility_body import CloudspaceIdVisibilityBody
|
|
92
93
|
from lightning_sdk.lightning_cloud.openapi.models.cloudspaces_id_body import CloudspacesIdBody
|
|
93
94
|
from lightning_sdk.lightning_cloud.openapi.models.cluster_id_capacityblock_body import ClusterIdCapacityblockBody
|
|
94
95
|
from lightning_sdk.lightning_cloud.openapi.models.cluster_id_capacityreservations_body import ClusterIdCapacityreservationsBody
|
|
@@ -275,6 +276,8 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_cpu_system_metrics import V
|
|
|
275
276
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cancel_cloud_space_instance_switch_response import V1CancelCloudSpaceInstanceSwitchResponse
|
|
276
277
|
from lightning_sdk.lightning_cloud.openapi.models.v1_cancellation_metadata import V1CancellationMetadata
|
|
277
278
|
from lightning_sdk.lightning_cloud.openapi.models.v1_capacity_block_offering import V1CapacityBlockOffering
|
|
279
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_check_cluster_name_availability_request import V1CheckClusterNameAvailabilityRequest
|
|
280
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_check_cluster_name_availability_response import V1CheckClusterNameAvailabilityResponse
|
|
278
281
|
from lightning_sdk.lightning_cloud.openapi.models.v1_check_external_service_status_response import V1CheckExternalServiceStatusResponse
|
|
279
282
|
from lightning_sdk.lightning_cloud.openapi.models.v1_check_snowflake_connection_response import V1CheckSnowflakeConnectionResponse
|
|
280
283
|
from lightning_sdk.lightning_cloud.openapi.models.v1_checkbox import V1Checkbox
|
|
@@ -420,6 +423,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningwork_respon
|
|
|
420
423
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_dataset_response import V1DeleteLitDatasetResponse
|
|
421
424
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_dataset_version_response import V1DeleteLitDatasetVersionResponse
|
|
422
425
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_page_response import V1DeleteLitPageResponse
|
|
426
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_registry_repository_image_artifact_version_by_digest_response import V1DeleteLitRegistryRepositoryImageArtifactVersionByDigestResponse
|
|
423
427
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_repository_response import V1DeleteLitRepositoryResponse
|
|
424
428
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_logger_artifact_response import V1DeleteLoggerArtifactResponse
|
|
425
429
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_managed_endpoint_response import V1DeleteManagedEndpointResponse
|
|
@@ -498,6 +502,8 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_execute_in_cloud_space_sess
|
|
|
498
502
|
from lightning_sdk.lightning_cloud.openapi.models.v1_execute_snowflake_query_response import V1ExecuteSnowflakeQueryResponse
|
|
499
503
|
from lightning_sdk.lightning_cloud.openapi.models.v1_experiment import V1Experiment
|
|
500
504
|
from lightning_sdk.lightning_cloud.openapi.models.v1_export_snowflake_query_response import V1ExportSnowflakeQueryResponse
|
|
505
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_external_cluster import V1ExternalCluster
|
|
506
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_external_cluster_spec import V1ExternalClusterSpec
|
|
501
507
|
from lightning_sdk.lightning_cloud.openapi.models.v1_external_search_user import V1ExternalSearchUser
|
|
502
508
|
from lightning_sdk.lightning_cloud.openapi.models.v1_file_endpoint import V1FileEndpoint
|
|
503
509
|
from lightning_sdk.lightning_cloud.openapi.models.v1_filestore_data_connection import V1FilestoreDataConnection
|
|
@@ -718,6 +724,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_lit_page_type import V1LitP
|
|
|
718
724
|
from lightning_sdk.lightning_cloud.openapi.models.v1_lit_registry_artifact import V1LitRegistryArtifact
|
|
719
725
|
from lightning_sdk.lightning_cloud.openapi.models.v1_lit_registry_project import V1LitRegistryProject
|
|
720
726
|
from lightning_sdk.lightning_cloud.openapi.models.v1_lit_repository import V1LitRepository
|
|
727
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_lite_published_cloud_space_response import V1LitePublishedCloudSpaceResponse
|
|
721
728
|
from lightning_sdk.lightning_cloud.openapi.models.v1_locked_resource import V1LockedResource
|
|
722
729
|
from lightning_sdk.lightning_cloud.openapi.models.v1_log_page import V1LogPage
|
|
723
730
|
from lightning_sdk.lightning_cloud.openapi.models.v1_logger_artifact import V1LoggerArtifact
|
|
@@ -910,6 +917,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_update_agent_status_respons
|
|
|
910
917
|
from lightning_sdk.lightning_cloud.openapi.models.v1_update_billing_subscription_request import V1UpdateBillingSubscriptionRequest
|
|
911
918
|
from lightning_sdk.lightning_cloud.openapi.models.v1_update_cloud_space_collab_response import V1UpdateCloudSpaceCollabResponse
|
|
912
919
|
from lightning_sdk.lightning_cloud.openapi.models.v1_update_cloud_space_publication_response import V1UpdateCloudSpacePublicationResponse
|
|
920
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_update_cloud_space_visibility_response import V1UpdateCloudSpaceVisibilityResponse
|
|
913
921
|
from lightning_sdk.lightning_cloud.openapi.models.v1_update_cluster_accelerators_request import V1UpdateClusterAcceleratorsRequest
|
|
914
922
|
from lightning_sdk.lightning_cloud.openapi.models.v1_update_cluster_accelerators_response import V1UpdateClusterAcceleratorsResponse
|
|
915
923
|
from lightning_sdk.lightning_cloud.openapi.models.v1_update_cluster_availability_request import V1UpdateClusterAvailabilityRequest
|
|
@@ -958,6 +966,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_validate_managed_model_resp
|
|
|
958
966
|
from lightning_sdk.lightning_cloud.openapi.models.v1_verify_verification_response import V1VerifyVerificationResponse
|
|
959
967
|
from lightning_sdk.lightning_cloud.openapi.models.v1_voltage_park_direct_v1 import V1VoltageParkDirectV1
|
|
960
968
|
from lightning_sdk.lightning_cloud.openapi.models.v1_volume import V1Volume
|
|
969
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_volume_state import V1VolumeState
|
|
961
970
|
from lightning_sdk.lightning_cloud.openapi.models.v1_vultr_direct_v1 import V1VultrDirectV1
|
|
962
971
|
from lightning_sdk.lightning_cloud.openapi.models.v1_weka_data_connection import V1WekaDataConnection
|
|
963
972
|
from lightning_sdk.lightning_cloud.openapi.models.v1_work import V1Work
|