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.
Files changed (70) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/license_api.py +2 -2
  3. lightning_sdk/api/llm_api.py +69 -27
  4. lightning_sdk/api/pipeline_api.py +49 -9
  5. lightning_sdk/api/studio_api.py +2 -0
  6. lightning_sdk/cli/configure.py +34 -27
  7. lightning_sdk/cli/connect.py +2 -2
  8. lightning_sdk/cli/download.py +38 -2
  9. lightning_sdk/cli/entrypoint.py +15 -13
  10. lightning_sdk/cli/start.py +5 -2
  11. lightning_sdk/lightning_cloud/openapi/__init__.py +9 -0
  12. lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +206 -0
  13. lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +98 -5
  14. lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +113 -0
  15. lightning_sdk/lightning_cloud/openapi/api/pipelines_service_api.py +118 -1
  16. lightning_sdk/lightning_cloud/openapi/api/schedules_service_api.py +5 -1
  17. lightning_sdk/lightning_cloud/openapi/models/__init__.py +9 -0
  18. lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +29 -3
  19. lightning_sdk/lightning_cloud/openapi/models/cloudspace_id_visibility_body.py +123 -0
  20. lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +27 -1
  21. lightning_sdk/lightning_cloud/openapi/models/metricsstream_create_body.py +27 -1
  22. lightning_sdk/lightning_cloud/openapi/models/pipelines_id_body.py +27 -1
  23. lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py +27 -1
  24. lightning_sdk/lightning_cloud/openapi/models/project_id_pipelines_body.py +27 -1
  25. lightning_sdk/lightning_cloud/openapi/models/project_id_schedules_body.py +27 -1
  26. lightning_sdk/lightning_cloud/openapi/models/schedules_id_body.py +27 -1
  27. lightning_sdk/lightning_cloud/openapi/models/v1_check_cluster_name_availability_request.py +123 -0
  28. lightning_sdk/lightning_cloud/openapi/models/v1_check_cluster_name_availability_response.py +123 -0
  29. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
  30. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +53 -1
  31. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_session.py +29 -3
  32. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
  33. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +79 -1
  34. lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +27 -1
  35. lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_registry_repository_image_artifact_version_by_digest_response.py +97 -0
  36. lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster.py +253 -0
  37. lightning_sdk/lightning_cloud/openapi/models/v1_external_cluster_spec.py +827 -0
  38. lightning_sdk/lightning_cloud/openapi/models/v1_get_project_storage_metadata_response.py +27 -1
  39. lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +105 -1
  40. lightning_sdk/lightning_cloud/openapi/models/v1_get_user_storage_breakdown_response.py +53 -1
  41. lightning_sdk/lightning_cloud/openapi/models/v1_instance_overprovisioning_spec.py +29 -1
  42. lightning_sdk/lightning_cloud/openapi/models/v1_lightning_run.py +53 -1
  43. lightning_sdk/lightning_cloud/openapi/models/v1_list_clusters_response.py +6 -6
  44. lightning_sdk/lightning_cloud/openapi/models/v1_list_project_clusters_response.py +6 -6
  45. lightning_sdk/lightning_cloud/openapi/models/v1_lite_published_cloud_space_response.py +513 -0
  46. lightning_sdk/lightning_cloud/openapi/models/v1_metrics_stream.py +27 -1
  47. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline.py +27 -1
  48. lightning_sdk/lightning_cloud/openapi/models/v1_schedule.py +27 -1
  49. lightning_sdk/lightning_cloud/openapi/models/v1_shared_filesystem.py +131 -1
  50. lightning_sdk/lightning_cloud/openapi/models/v1_update_cloud_space_visibility_response.py +97 -0
  51. lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +79 -1
  52. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +53 -183
  53. lightning_sdk/lightning_cloud/openapi/models/v1_volume.py +78 -104
  54. lightning_sdk/lightning_cloud/openapi/models/v1_volume_state.py +104 -0
  55. lightning_sdk/llm/llm.py +6 -1
  56. lightning_sdk/pipeline/__init__.py +12 -2
  57. lightning_sdk/pipeline/pipeline.py +59 -17
  58. lightning_sdk/pipeline/printer.py +121 -0
  59. lightning_sdk/pipeline/schedule.py +8 -0
  60. lightning_sdk/pipeline/{types.py → steps.py} +77 -59
  61. lightning_sdk/pipeline/utils.py +39 -17
  62. lightning_sdk/sandbox.py +157 -0
  63. lightning_sdk/services/license.py +12 -6
  64. lightning_sdk/studio.py +7 -1
  65. {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/METADATA +1 -1
  66. {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/RECORD +70 -58
  67. {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/LICENSE +0 -0
  68. {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/WHEEL +0 -0
  69. {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.dist-info}/entry_points.txt +0 -0
  70. {lightning_sdk-0.2.21rc1.dist-info → lightning_sdk-0.2.23.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.21.rc1"
34
+ __version__ = "0.2.23"
35
35
  _check_version_and_prompt_upgrade(__version__)
36
36
  _set_tqdm_envvars_noninteractive()
@@ -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
 
@@ -2,7 +2,9 @@ import asyncio
2
2
  import base64
3
3
  import json
4
4
  import os
5
- from typing import AsyncGenerator, Dict, Generator, List, Optional, Union
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
- try:
36
- payload = json.loads(decoded_line)
37
- result_data = payload.get("result", {})
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
- raise NotImplementedError("Streaming is not supported in this client.")
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, pipeline_id: str) -> V1Pipeline:
21
- try:
22
- return self._client.jobs_service_get_deployment(project_id=project_id, id=pipeline_id)
23
- except ApiException as ex:
24
- if "Reason: Not Found" in str(ex):
25
- return None
26
- raise ex
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
- return self._client.pipelines_service_create_pipeline(body, project_id)
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
@@ -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
 
@@ -17,33 +17,10 @@ def configure() -> None:
17
17
  """Configure access to resources on the Lightning AI platform."""
18
18
 
19
19
 
20
- @configure.command(name="ssh")
21
- @click.option(
22
- "--name",
23
- default=None,
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
@@ -4,7 +4,7 @@ from typing import Optional
4
4
 
5
5
  import click
6
6
 
7
- from lightning_sdk.cli.configure import ssh as configure_ssh
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
- configure_ssh(name=name, teamspace=teamspace, overwrite=False)
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)
@@ -241,8 +241,8 @@ def _resolve_studio(studio: Optional[str]) -> Studio:
241
241
 
242
242
 
243
243
  @download.command(name="licenses")
244
- def licenses() -> None:
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")
@@ -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
- error_content = Text()
45
- error_content.append(f"{exception_type.__name__}: ", style="bold red")
46
- error_content.append(message, style="white")
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
- error_content.append("\n\nFull traceback:\n", style="bold yellow")
50
- tb_lines = traceback.format_exception(exception_type, value, tb)
51
- error_content.append("".join(tb_lines), style="dim white")
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(error_content, title="⚡ Lightning CLI Error", border_style="red"))
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.")
@@ -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
- studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False, provider=provider)
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