lightning-sdk 0.1.56__py3-none-any.whl → 0.1.58__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 +3 -2
- lightning_sdk/api/lit_container_api.py +82 -20
- lightning_sdk/cli/ai_hub.py +61 -10
- lightning_sdk/cli/configure.py +110 -65
- lightning_sdk/cli/connect.py +32 -16
- lightning_sdk/cli/delete.py +81 -32
- lightning_sdk/cli/download.py +177 -90
- lightning_sdk/cli/entrypoint.py +44 -16
- lightning_sdk/cli/generate.py +48 -16
- lightning_sdk/cli/inspect.py +43 -3
- lightning_sdk/cli/list.py +130 -41
- lightning_sdk/cli/run.py +0 -6
- lightning_sdk/cli/teamspace_menu.py +1 -1
- lightning_sdk/cli/upload.py +13 -3
- lightning_sdk/helpers.py +20 -0
- lightning_sdk/job/job.py +1 -1
- lightning_sdk/lightning_cloud/openapi/__init__.py +5 -0
- lightning_sdk/lightning_cloud/openapi/api/data_connection_service_api.py +105 -0
- lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +113 -0
- lightning_sdk/lightning_cloud/openapi/api/lit_logger_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +5 -0
- lightning_sdk/lightning_cloud/openapi/models/agents_id_body.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/id_visibility_body1.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/model_id_visibility_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/setup.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_assistant.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_gcp_data_connection_setup.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_setup_data_connection_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_deployment_visibility_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_metrics_stream_visibility_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_update_model_visibility_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +79 -1
- {lightning_sdk-0.1.56.dist-info → lightning_sdk-0.1.58.dist-info}/METADATA +2 -1
- {lightning_sdk-0.1.56.dist-info → lightning_sdk-0.1.58.dist-info}/RECORD +41 -36
- {lightning_sdk-0.1.56.dist-info → lightning_sdk-0.1.58.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.1.56.dist-info → lightning_sdk-0.1.58.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.1.56.dist-info → lightning_sdk-0.1.58.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.1.56.dist-info → lightning_sdk-0.1.58.dist-info}/top_level.txt +0 -0
lightning_sdk/cli/download.py
CHANGED
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
+
import click
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
|
|
8
9
|
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
@@ -25,51 +26,7 @@ class _Downloads(_StudiosMenu, _TeamspacesMenu):
|
|
|
25
26
|
This should have the format <ORGANIZATION-NAME>/<TEAMSPACE-NAME>/<MODEL-NAME>.
|
|
26
27
|
download_dir: The directory where the Model should be downloaded.
|
|
27
28
|
"""
|
|
28
|
-
|
|
29
|
-
name=name,
|
|
30
|
-
download_dir=download_dir,
|
|
31
|
-
progress_bar=True,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
def _resolve_studio(self, studio: Optional[str]) -> Studio:
|
|
35
|
-
user = _get_authed_user()
|
|
36
|
-
# if no studio specify suggest/filter only user's studios
|
|
37
|
-
possible_studios = self._get_possible_studios(user, is_owner=studio is None)
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
if studio:
|
|
41
|
-
team_name, studio_name = studio.split("/")
|
|
42
|
-
options = [st for st in possible_studios if st["teamspace"] == team_name and st["name"] == studio_name]
|
|
43
|
-
if len(options) == 1:
|
|
44
|
-
selected_studio = self._get_studio_from_name(studio, possible_studios)
|
|
45
|
-
# user can also use the partial studio name as secondary interactive selection
|
|
46
|
-
else:
|
|
47
|
-
# filter matching simple reg expressions or start with the team and studio name
|
|
48
|
-
possible_studios = filter(
|
|
49
|
-
lambda st: (re.match(team_name, st["teamspace"]) or team_name in st["teamspace"])
|
|
50
|
-
and (re.match(studio_name, st["name"]) or studio_name in st["name"]),
|
|
51
|
-
possible_studios,
|
|
52
|
-
)
|
|
53
|
-
if not possible_studios:
|
|
54
|
-
raise ValueError(
|
|
55
|
-
f"Could not find Studio like '{studio}', please consider update your filtering pattern."
|
|
56
|
-
)
|
|
57
|
-
selected_studio = self._get_studio_from_interactive_menu(list(possible_studios))
|
|
58
|
-
else:
|
|
59
|
-
selected_studio = self._get_studio_from_interactive_menu(possible_studios)
|
|
60
|
-
|
|
61
|
-
except KeyboardInterrupt:
|
|
62
|
-
raise KeyboardInterrupt from None
|
|
63
|
-
|
|
64
|
-
# give user friendlier error message
|
|
65
|
-
except Exception as e:
|
|
66
|
-
raise StudioCliError(
|
|
67
|
-
f"Could not find the given Studio {studio} to upload files to. "
|
|
68
|
-
"Please contact Lightning AI directly to resolve this issue."
|
|
69
|
-
) from e
|
|
70
|
-
|
|
71
|
-
with skip_studio_init():
|
|
72
|
-
return Studio(**selected_studio)
|
|
29
|
+
model(name=name, download_dir=download_dir)
|
|
73
30
|
|
|
74
31
|
def folder(self, path: str = "", studio: Optional[str] = None, local_path: str = ".") -> None:
|
|
75
32
|
"""Download a folder from a Studio.
|
|
@@ -84,27 +41,9 @@ class _Downloads(_StudiosMenu, _TeamspacesMenu):
|
|
|
84
41
|
with filtered studios will be shown for final selection.
|
|
85
42
|
local_path: The path to the directory you want to download the folder to.
|
|
86
43
|
"""
|
|
87
|
-
|
|
88
|
-
if not local_path.is_dir():
|
|
89
|
-
raise NotADirectoryError(f"'{local_path}' is not a directory")
|
|
90
|
-
|
|
91
|
-
resolved_studio = self._resolve_studio(studio)
|
|
44
|
+
folder(path=path, studio=studio, local_path=local_path)
|
|
92
45
|
|
|
93
|
-
|
|
94
|
-
local_path /= resolved_studio.name
|
|
95
|
-
path = ""
|
|
96
|
-
|
|
97
|
-
try:
|
|
98
|
-
if not path:
|
|
99
|
-
raise FileNotFoundError()
|
|
100
|
-
resolved_studio.download_folder(remote_path=path, target_path=str(local_path))
|
|
101
|
-
except Exception as e:
|
|
102
|
-
raise StudioCliError(
|
|
103
|
-
f"Could not download the folder from the given Studio {studio}. "
|
|
104
|
-
"Please contact Lightning AI directly to resolve this issue."
|
|
105
|
-
) from e
|
|
106
|
-
|
|
107
|
-
def file(self, path: str = "", studio: Optional[str] = None, local_path: str = ".") -> None:
|
|
46
|
+
def file(self, path: str, studio: Optional[str] = None, local_path: str = ".") -> None:
|
|
108
47
|
"""Download a file from a Studio.
|
|
109
48
|
|
|
110
49
|
Args:
|
|
@@ -115,25 +54,7 @@ class _Downloads(_StudiosMenu, _TeamspacesMenu):
|
|
|
115
54
|
with filtered studios will be shown for final selection.
|
|
116
55
|
local_path: The path to the directory you want to download the file to.
|
|
117
56
|
"""
|
|
118
|
-
|
|
119
|
-
if not local_path.is_dir():
|
|
120
|
-
raise NotADirectoryError(f"'{local_path}' is not a directory")
|
|
121
|
-
|
|
122
|
-
resolved_studio = self._resolve_studio(studio)
|
|
123
|
-
|
|
124
|
-
if not path:
|
|
125
|
-
local_path /= resolved_studio.name
|
|
126
|
-
path = ""
|
|
127
|
-
|
|
128
|
-
try:
|
|
129
|
-
if not path:
|
|
130
|
-
raise FileNotFoundError()
|
|
131
|
-
resolved_studio.download_file(remote_path=path, file_path=str(local_path / os.path.basename(path)))
|
|
132
|
-
except Exception as e:
|
|
133
|
-
raise StudioCliError(
|
|
134
|
-
f"Could not download the file from the given Studio {studio}. "
|
|
135
|
-
"Please contact Lightning AI directly to resolve this issue."
|
|
136
|
-
) from e
|
|
57
|
+
file(path=path, studio=studio, local_path=local_path)
|
|
137
58
|
|
|
138
59
|
def container(self, container: str, teamspace: Optional[str] = None, tag: str = "latest") -> None:
|
|
139
60
|
"""Download a docker container from a teamspace.
|
|
@@ -143,9 +64,175 @@ class _Downloads(_StudiosMenu, _TeamspacesMenu):
|
|
|
143
64
|
teamspace: The name of the teamspace to download the container from.
|
|
144
65
|
tag: The tag of the container to download.
|
|
145
66
|
"""
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
67
|
+
download_container(container=container, teamspace=teamspace, tag=tag)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@click.group(name="download")
|
|
71
|
+
def download() -> None:
|
|
72
|
+
"""Download resources from Lightning AI."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# @download.command(name="model")
|
|
76
|
+
# @click.option(
|
|
77
|
+
# "--name",
|
|
78
|
+
# help=(
|
|
79
|
+
# "The name of the Model you want to download. "
|
|
80
|
+
# "This should have the format <ORGANIZATION-NAME>/<TEAMSPACE-NAME>/<MODEL-NAME>."
|
|
81
|
+
# ),
|
|
82
|
+
# )
|
|
83
|
+
# @click.option("--download-dir", default=".", help="The directory where the Model should be downloaded.")
|
|
84
|
+
def model(name: str, download_dir: str = ".") -> None:
|
|
85
|
+
"""Download a Model."""
|
|
86
|
+
download_model(
|
|
87
|
+
name=name,
|
|
88
|
+
download_dir=download_dir,
|
|
89
|
+
progress_bar=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# @download.command(name="folder")
|
|
94
|
+
# @click.option(
|
|
95
|
+
# "--path",
|
|
96
|
+
# default="",
|
|
97
|
+
# help=(
|
|
98
|
+
# "The relative path within the Studio you want to download. "
|
|
99
|
+
# "If you leave it empty it will download whole studio and locally creates a "
|
|
100
|
+
# "new folder with the same name as the selected studio."
|
|
101
|
+
# ),
|
|
102
|
+
# )
|
|
103
|
+
# @click.option(
|
|
104
|
+
# "--studio",
|
|
105
|
+
# default=None,
|
|
106
|
+
# help=(
|
|
107
|
+
# "The name of the studio to upload to. "
|
|
108
|
+
# "Will show a menu with user's owned studios for selection if not specified. "
|
|
109
|
+
# "If provided, should be in the form of <TEAMSPACE-NAME>/<STUDIO-NAME> where the names are case-sensitive. "
|
|
110
|
+
# "The teamspace and studio names can be regular expressions to match, "
|
|
111
|
+
# "a menu filtered studios will be shown for final selection."
|
|
112
|
+
# ),
|
|
113
|
+
# )
|
|
114
|
+
# @click.option("--local-path", default=".", help="The path to the directory you want to download the folder to.")
|
|
115
|
+
def folder(path: str = "", studio: Optional[str] = None, local_path: str = ".") -> None:
|
|
116
|
+
"""Download a folder from a Studio."""
|
|
117
|
+
local_path = Path(local_path)
|
|
118
|
+
if not local_path.is_dir():
|
|
119
|
+
raise NotADirectoryError(f"'{local_path}' is not a directory")
|
|
120
|
+
|
|
121
|
+
menu = _StudiosMenu()
|
|
122
|
+
resolved_studio = menu._resolve_studio(studio)
|
|
123
|
+
|
|
124
|
+
if not path:
|
|
125
|
+
local_path /= resolved_studio.name
|
|
126
|
+
path = ""
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
if not path:
|
|
130
|
+
raise FileNotFoundError()
|
|
131
|
+
resolved_studio.download_folder(remote_path=path, target_path=str(local_path))
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise StudioCliError(
|
|
134
|
+
f"Could not download the folder from the given Studio {studio}. "
|
|
135
|
+
"Please contact Lightning AI directly to resolve this issue."
|
|
136
|
+
) from e
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# @download.command(name="file")
|
|
140
|
+
# @click.option(
|
|
141
|
+
# "--path",
|
|
142
|
+
# default="",
|
|
143
|
+
# help=(
|
|
144
|
+
# "The relative path within the Studio you want to download. "
|
|
145
|
+
# "If you leave it empty it will download whole studio and locally creates a new folder "
|
|
146
|
+
# "with the same name as the selected studio."
|
|
147
|
+
# ),
|
|
148
|
+
# )
|
|
149
|
+
# @click.option(
|
|
150
|
+
# "--studio",
|
|
151
|
+
# default=None,
|
|
152
|
+
# help=(
|
|
153
|
+
# "The name of the studio to upload to. "
|
|
154
|
+
# "Will show a menu with user's owned studios for selection if not specified. "
|
|
155
|
+
# "If provided, should be in the form of <TEAMSPACE-NAME>/<STUDIO-NAME> where the names are case-sensitive. "
|
|
156
|
+
# "The teamspace and studio names can be regular expressions to match, "
|
|
157
|
+
# "a menu filtered studios will be shown for final selection."
|
|
158
|
+
# ),
|
|
159
|
+
# )
|
|
160
|
+
# @click.option("--local-path", default=".", help="The path to the directory you want to download the folder to.")
|
|
161
|
+
def file(path: str = "", studio: Optional[str] = None, local_path: str = ".") -> None:
|
|
162
|
+
"""Download a file from a Studio."""
|
|
163
|
+
local_path = Path(local_path)
|
|
164
|
+
if not local_path.is_dir():
|
|
165
|
+
raise NotADirectoryError(f"'{local_path}' is not a directory")
|
|
166
|
+
|
|
167
|
+
resolved_studio = _resolve_studio(studio)
|
|
168
|
+
|
|
169
|
+
if not path:
|
|
170
|
+
local_path /= resolved_studio.name
|
|
171
|
+
path = ""
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
if not path:
|
|
175
|
+
raise FileNotFoundError()
|
|
176
|
+
resolved_studio.download_file(remote_path=path, file_path=str(local_path / os.path.basename(path)))
|
|
177
|
+
except Exception as e:
|
|
178
|
+
raise StudioCliError(
|
|
179
|
+
f"Could not download the file from the given Studio {studio}. "
|
|
180
|
+
"Please contact Lightning AI directly to resolve this issue."
|
|
181
|
+
) from e
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# @download.command(name="container")
|
|
185
|
+
# @click.argument("container")
|
|
186
|
+
# @click.argument("teamspace", default=None, help="The name of the teamspace to download the container from")
|
|
187
|
+
# @click.argument("tag", default="latest", show_default=True, help="The tag of the container to download.")
|
|
188
|
+
def download_container(container: str, teamspace: Optional[str] = None, tag: str = "latest") -> None:
|
|
189
|
+
"""Download the docker container CONTAINER from a teamspace."""
|
|
190
|
+
console = Console()
|
|
191
|
+
menu = _TeamspacesMenu()
|
|
192
|
+
resolved_teamspace = menu._resolve_teamspace(teamspace)
|
|
193
|
+
with console.status("Downloading container..."):
|
|
194
|
+
api = LitContainerApi()
|
|
195
|
+
api.download_container(container, resolved_teamspace, tag)
|
|
196
|
+
console.print("Container downloaded successfully", style="green")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _resolve_studio(studio: Optional[str]) -> Studio:
|
|
200
|
+
user = _get_authed_user()
|
|
201
|
+
# if no studio specify suggest/filter only user's studios
|
|
202
|
+
menu = _StudiosMenu()
|
|
203
|
+
possible_studios = menu._get_possible_studios(user, is_owner=studio is None)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
if studio:
|
|
207
|
+
team_name, studio_name = studio.split("/")
|
|
208
|
+
options = [st for st in possible_studios if st["teamspace"] == team_name and st["name"] == studio_name]
|
|
209
|
+
if len(options) == 1:
|
|
210
|
+
selected_studio = menu._get_studio_from_name(studio, possible_studios)
|
|
211
|
+
# user can also use the partial studio name as secondary interactive selection
|
|
212
|
+
else:
|
|
213
|
+
# filter matching simple reg expressions or start with the team and studio name
|
|
214
|
+
possible_studios = filter(
|
|
215
|
+
lambda st: (re.match(team_name, st["teamspace"]) or team_name in st["teamspace"])
|
|
216
|
+
and (re.match(studio_name, st["name"]) or studio_name in st["name"]),
|
|
217
|
+
possible_studios,
|
|
218
|
+
)
|
|
219
|
+
if not possible_studios:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"Could not find Studio like '{studio}', please consider update your filtering pattern."
|
|
222
|
+
)
|
|
223
|
+
selected_studio = menu._get_studio_from_interactive_menu(list(possible_studios))
|
|
224
|
+
else:
|
|
225
|
+
selected_studio = menu._get_studio_from_interactive_menu(possible_studios)
|
|
226
|
+
|
|
227
|
+
except KeyboardInterrupt:
|
|
228
|
+
raise KeyboardInterrupt from None
|
|
229
|
+
|
|
230
|
+
# give user friendlier error message
|
|
231
|
+
except Exception as e:
|
|
232
|
+
raise StudioCliError(
|
|
233
|
+
f"Could not find the given Studio {studio} to upload files to. "
|
|
234
|
+
"Please contact Lightning AI directly to resolve this issue."
|
|
235
|
+
) from e
|
|
236
|
+
|
|
237
|
+
with skip_studio_init():
|
|
238
|
+
return Studio(**selected_studio)
|
lightning_sdk/cli/entrypoint.py
CHANGED
|
@@ -2,19 +2,20 @@ import sys
|
|
|
2
2
|
from types import TracebackType
|
|
3
3
|
from typing import Type
|
|
4
4
|
|
|
5
|
+
import click
|
|
5
6
|
from fire import Fire
|
|
6
7
|
from lightning_utilities.core.imports import RequirementCache
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from rich.panel import Panel
|
|
9
10
|
|
|
10
11
|
from lightning_sdk.api.studio_api import _cloud_url
|
|
11
|
-
from lightning_sdk.cli.ai_hub import _AIHub
|
|
12
|
-
from lightning_sdk.cli.configure import _Configure
|
|
13
|
-
from lightning_sdk.cli.connect import _Connect
|
|
14
|
-
from lightning_sdk.cli.delete import _Delete
|
|
15
|
-
from lightning_sdk.cli.download import _Downloads
|
|
16
|
-
from lightning_sdk.cli.generate import _Generate
|
|
17
|
-
from lightning_sdk.cli.inspect import _Inspect
|
|
12
|
+
from lightning_sdk.cli.ai_hub import _AIHub, aihub
|
|
13
|
+
from lightning_sdk.cli.configure import _Configure, configure
|
|
14
|
+
from lightning_sdk.cli.connect import _Connect, connect
|
|
15
|
+
from lightning_sdk.cli.delete import _Delete, delete
|
|
16
|
+
from lightning_sdk.cli.download import _Downloads, download
|
|
17
|
+
from lightning_sdk.cli.generate import _Generate, generate
|
|
18
|
+
from lightning_sdk.cli.inspect import _Inspect, inspect
|
|
18
19
|
from lightning_sdk.cli.legacy import _LegacyLightningCLI
|
|
19
20
|
from lightning_sdk.cli.list import _List
|
|
20
21
|
from lightning_sdk.cli.run import _Run
|
|
@@ -52,18 +53,11 @@ class StudioCLI:
|
|
|
52
53
|
|
|
53
54
|
def login(self) -> None:
|
|
54
55
|
"""Login to Lightning AI Studios."""
|
|
55
|
-
|
|
56
|
-
auth.clear()
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
auth.authenticate()
|
|
60
|
-
except ConnectionError:
|
|
61
|
-
raise RuntimeError(f"Unable to connect to {_cloud_url()}. Please check your internet connection.") from None
|
|
56
|
+
return login()
|
|
62
57
|
|
|
63
58
|
def logout(self) -> None:
|
|
64
59
|
"""Logout from Lightning AI Studios."""
|
|
65
|
-
|
|
66
|
-
auth.clear()
|
|
60
|
+
return logout()
|
|
67
61
|
|
|
68
62
|
|
|
69
63
|
def _notify_exception(exception_type: Type[BaseException], value: BaseException, tb: TracebackType) -> None: # No
|
|
@@ -77,5 +71,39 @@ def main_cli() -> None:
|
|
|
77
71
|
Fire(StudioCLI(), name="lightning")
|
|
78
72
|
|
|
79
73
|
|
|
74
|
+
@click.group(name="lightning", help="Command line interface (CLI) to interact with/manage Lightning AI Studios.")
|
|
75
|
+
def main_cli_click() -> None:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# @main_cli_click.command
|
|
80
|
+
def login() -> None:
|
|
81
|
+
"""Login to Lightning AI Studios."""
|
|
82
|
+
auth = Auth()
|
|
83
|
+
auth.clear()
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
auth.authenticate()
|
|
87
|
+
except ConnectionError:
|
|
88
|
+
raise RuntimeError(f"Unable to connect to {_cloud_url()}. Please check your internet connection.") from None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# @main_cli_click.command
|
|
92
|
+
def logout() -> None:
|
|
93
|
+
"""Logout from Lightning AI Studios."""
|
|
94
|
+
auth = Auth()
|
|
95
|
+
auth.clear()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# TODO: handle exception hook registration
|
|
99
|
+
main_cli_click.add_command(aihub)
|
|
100
|
+
main_cli_click.add_command(configure)
|
|
101
|
+
main_cli_click.add_command(connect)
|
|
102
|
+
main_cli_click.add_command(delete)
|
|
103
|
+
main_cli_click.add_command(download)
|
|
104
|
+
main_cli_click.add_command(generate)
|
|
105
|
+
main_cli_click.add_command(inspect)
|
|
106
|
+
|
|
107
|
+
|
|
80
108
|
if __name__ == "__main__":
|
|
81
109
|
main_cli()
|
lightning_sdk/cli/generate.py
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
import click
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
|
|
5
6
|
from lightning_sdk.cli.studios_menu import _StudiosMenu
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class _Generate
|
|
9
|
+
class _Generate:
|
|
9
10
|
"""Generate configs (such as ssh for studio) and print them to commandline."""
|
|
10
11
|
|
|
11
|
-
@staticmethod
|
|
12
|
-
def _generate_ssh_config(key_path: str) -> str:
|
|
13
|
-
return f"""Host ssh.lightning.ai
|
|
14
|
-
IdentityFile {key_path}
|
|
15
|
-
IdentitiesOnly yes
|
|
16
|
-
ServerAliveInterval 15
|
|
17
|
-
ServerAliveCountMax 4
|
|
18
|
-
StrictHostKeyChecking no
|
|
19
|
-
UserKnownHostsFile=/dev/null
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
12
|
def ssh(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
23
13
|
"""Get SSH config entry for a studio.
|
|
24
14
|
|
|
@@ -28,8 +18,50 @@ class _Generate(_StudiosMenu):
|
|
|
28
18
|
teamspace: The teamspace the studio is part of. Should be of format <OWNER>/<TEAMSPACE_NAME>.
|
|
29
19
|
If not specified, tries to infer from the environment (e.g. when run from within a Studio.)
|
|
30
20
|
"""
|
|
31
|
-
|
|
21
|
+
ssh(name=name, teamspace=teamspace)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _generate_ssh_config(key_path: str, host: str, user: str) -> str:
|
|
25
|
+
return f"""Host {host}
|
|
26
|
+
User {user}
|
|
27
|
+
Hostname ssh.lightning.ai
|
|
28
|
+
IdentityFile {key_path}
|
|
29
|
+
IdentitiesOnly yes
|
|
30
|
+
ServerAliveInterval 15
|
|
31
|
+
ServerAliveCountMax 4
|
|
32
|
+
StrictHostKeyChecking no
|
|
33
|
+
UserKnownHostsFile=/dev/null
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group(name="generate")
|
|
38
|
+
def generate() -> None:
|
|
39
|
+
"""Generate configs (such as ssh for studio) and print them to commandline."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# @generate.command(name="ssh")
|
|
43
|
+
# @click.option(
|
|
44
|
+
# "--name",
|
|
45
|
+
# default=None,
|
|
46
|
+
# help=(
|
|
47
|
+
# "The name of the studio to obtain SSH config. "
|
|
48
|
+
# "If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
49
|
+
# ),
|
|
50
|
+
# )
|
|
51
|
+
# @click.option(
|
|
52
|
+
# "--teamspace",
|
|
53
|
+
# default=None,
|
|
54
|
+
# help=(
|
|
55
|
+
# "The teamspace the studio is part of. "
|
|
56
|
+
# "Should be of format <OWNER>/<TEAMSPACE_NAME>. "
|
|
57
|
+
# "If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
|
|
58
|
+
# ),
|
|
59
|
+
# )
|
|
60
|
+
def ssh(name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
61
|
+
"""Get SSH config entry for a studio."""
|
|
62
|
+
menu = _StudiosMenu()
|
|
63
|
+
studio = menu._get_studio(name=name, teamspace=teamspace)
|
|
32
64
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
conf = _generate_ssh_config(key_path="~/.ssh/lightning_rsa", user=f"s_{studio._studio.id}", host=studio.name)
|
|
66
|
+
# Print the SSH config
|
|
67
|
+
Console().print(f"# ssh s_{studio._studio.id}@ssh.lightning.ai\n\n" + conf)
|
lightning_sdk/cli/inspect.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
import click
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
|
|
5
6
|
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class _Inspect
|
|
9
|
+
class _Inspect:
|
|
9
10
|
"""Inspect resources of the Lightning AI platform to get additional details as JSON."""
|
|
10
11
|
|
|
11
12
|
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
@@ -18,7 +19,7 @@ class _Inspect(_JobAndMMTAction):
|
|
|
18
19
|
If not specified can be selected interactively.
|
|
19
20
|
|
|
20
21
|
"""
|
|
21
|
-
|
|
22
|
+
job(name=name, teamspace=teamspace)
|
|
22
23
|
|
|
23
24
|
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
24
25
|
"""Inspect a multi-machine job for further details as JSON.
|
|
@@ -30,4 +31,43 @@ class _Inspect(_JobAndMMTAction):
|
|
|
30
31
|
If not specified can be selected interactively.
|
|
31
32
|
|
|
32
33
|
"""
|
|
33
|
-
|
|
34
|
+
mmt(name=name, teamspace=teamspace)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group(name="inspect")
|
|
38
|
+
def inspect() -> None:
|
|
39
|
+
"""Inspect resources of the Lightning AI platform to get additional details as JSON."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# @inspect.command(name="job")
|
|
43
|
+
# @click.option("--name", default=None, help="the name of the job. If not specified can be selected interactively.")
|
|
44
|
+
# @click.option(
|
|
45
|
+
# "--teamspace",
|
|
46
|
+
# default=None,
|
|
47
|
+
# help=(
|
|
48
|
+
# "the name of the teamspace the job lives in."
|
|
49
|
+
# "Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
|
|
50
|
+
# "If not specified can be selected interactively."
|
|
51
|
+
# ),
|
|
52
|
+
# )
|
|
53
|
+
def job(name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
54
|
+
"""Inspect a job for further details as JSON."""
|
|
55
|
+
menu = _JobAndMMTAction()
|
|
56
|
+
Console().print(menu.job(name=name, teamspace=teamspace).json())
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# @inspect.command(name="mmt")
|
|
60
|
+
# @click.option("--name", default=None, help="the name of the job. If not specified can be selected interactively.")
|
|
61
|
+
# @click.option(
|
|
62
|
+
# "--teamspace",
|
|
63
|
+
# default=None,
|
|
64
|
+
# help=(
|
|
65
|
+
# "the name of the teamspace the job lives in."
|
|
66
|
+
# "Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
|
|
67
|
+
# "If not specified can be selected interactively."
|
|
68
|
+
# ),
|
|
69
|
+
# )
|
|
70
|
+
def mmt(name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
71
|
+
"""Inspect a multi-machine job for further details as JSON."""
|
|
72
|
+
menu = _JobAndMMTAction()
|
|
73
|
+
Console().print(menu.mmt(name=name, teamspace=teamspace).json())
|