lightning-sdk 0.1.55__py3-none-any.whl → 0.1.57__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/ai_hub.py +22 -0
- lightning_sdk/api/ai_hub_api.py +21 -2
- lightning_sdk/api/deployment_api.py +4 -3
- lightning_sdk/api/job_api.py +5 -10
- lightning_sdk/api/mmt_api.py +1 -4
- lightning_sdk/api/studio_api.py +5 -7
- lightning_sdk/api/teamspace_api.py +7 -0
- lightning_sdk/api/utils.py +1 -27
- lightning_sdk/cli/ai_hub.py +61 -10
- lightning_sdk/cli/configure.py +137 -0
- lightning_sdk/cli/connect.py +47 -0
- lightning_sdk/cli/delete.py +83 -32
- lightning_sdk/cli/download.py +177 -90
- lightning_sdk/cli/entrypoint.py +50 -15
- lightning_sdk/cli/generate.py +51 -42
- lightning_sdk/cli/inspect.py +45 -3
- lightning_sdk/cli/jobs_menu.py +2 -1
- lightning_sdk/cli/list.py +139 -55
- lightning_sdk/cli/mmts_menu.py +2 -1
- lightning_sdk/cli/run.py +3 -9
- lightning_sdk/cli/serve.py +1 -2
- lightning_sdk/cli/start.py +2 -2
- lightning_sdk/cli/stop.py +5 -3
- lightning_sdk/cli/studios_menu.py +24 -1
- lightning_sdk/cli/switch.py +2 -2
- lightning_sdk/cli/teamspace_menu.py +2 -1
- lightning_sdk/cli/upload.py +6 -4
- lightning_sdk/helpers.py +20 -0
- lightning_sdk/job/job.py +1 -1
- lightning_sdk/lightning_cloud/openapi/__init__.py +9 -0
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +105 -0
- lightning_sdk/lightning_cloud/openapi/api/data_connection_service_api.py +105 -0
- lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +226 -0
- lightning_sdk/lightning_cloud/openapi/api/lit_logger_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +7 -3
- lightning_sdk/lightning_cloud/openapi/api/projects_service_api.py +1 -5
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +9 -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_reportrestarttimings_body.py +123 -0
- 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/project_id_litregistry_body.py +2 -0
- 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_cluster_accelerator.py +27 -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_get_cluster_accelerator_demand_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_job.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_registry_artifact.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_registry_project.py +8 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_lit_repository.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_report_restart_timings_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_restart_timing.py +175 -0
- 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/lightning_cloud/openapi/models/v1_validate_deployment_image_request.py +27 -1
- lightning_sdk/machine.py +59 -27
- lightning_sdk/studio.py +5 -1
- lightning_sdk/teamspace.py +25 -0
- {lightning_sdk-0.1.55.dist-info → lightning_sdk-0.1.57.dist-info}/METADATA +3 -1
- {lightning_sdk-0.1.55.dist-info → lightning_sdk-0.1.57.dist-info}/RECORD +72 -61
- {lightning_sdk-0.1.55.dist-info → lightning_sdk-0.1.57.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.1.55.dist-info → lightning_sdk-0.1.57.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.1.55.dist-info → lightning_sdk-0.1.57.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.1.55.dist-info → lightning_sdk-0.1.57.dist-info}/top_level.txt +0 -0
lightning_sdk/cli/inspect.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
3
6
|
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
4
7
|
|
|
5
8
|
|
|
6
|
-
class _Inspect
|
|
9
|
+
class _Inspect:
|
|
7
10
|
"""Inspect resources of the Lightning AI platform to get additional details as JSON."""
|
|
8
11
|
|
|
9
12
|
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
@@ -16,7 +19,7 @@ class _Inspect(_JobAndMMTAction):
|
|
|
16
19
|
If not specified can be selected interactively.
|
|
17
20
|
|
|
18
21
|
"""
|
|
19
|
-
|
|
22
|
+
job(name=name, teamspace=teamspace)
|
|
20
23
|
|
|
21
24
|
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
22
25
|
"""Inspect a multi-machine job for further details as JSON.
|
|
@@ -28,4 +31,43 @@ class _Inspect(_JobAndMMTAction):
|
|
|
28
31
|
If not specified can be selected interactively.
|
|
29
32
|
|
|
30
33
|
"""
|
|
31
|
-
|
|
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())
|
lightning_sdk/cli/jobs_menu.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
+
from rich.console import Console
|
|
3
4
|
from simple_term_menu import TerminalMenu
|
|
4
5
|
|
|
5
6
|
from lightning_sdk.cli.exceptions import StudioCliError
|
|
@@ -20,7 +21,7 @@ class _JobsMenu:
|
|
|
20
21
|
if j.name == job:
|
|
21
22
|
return j
|
|
22
23
|
|
|
23
|
-
print("Could not find Job {job}, please select it from the list:")
|
|
24
|
+
Console().print("Could not find Job {job}, please select it from the list:")
|
|
24
25
|
return self._get_job_from_interactive_menu(possible_jobs)
|
|
25
26
|
|
|
26
27
|
@staticmethod
|
lightning_sdk/cli/list.py
CHANGED
|
@@ -1,27 +1,58 @@
|
|
|
1
|
-
from
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from typing import Callable, Optional
|
|
2
3
|
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
from rich.table import Table
|
|
6
|
+
from typing_extensions import Literal
|
|
5
7
|
|
|
6
|
-
from lightning_sdk import Machine
|
|
8
|
+
from lightning_sdk import Job, Machine, Studio, Teamspace
|
|
7
9
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
10
|
+
from lightning_sdk.lightning_cloud.openapi import V1MultiMachineJob
|
|
8
11
|
from lightning_sdk.lit_container import LitContainer
|
|
12
|
+
from lightning_sdk.utils.resolve import _get_authed_user
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class _List(_TeamspacesMenu):
|
|
12
16
|
"""List resources on the Lightning AI platform."""
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _sort_studios_key(sort_by: str) -> Callable[[Studio], str]:
|
|
20
|
+
"""Return a key function to sort studios by a given attribute."""
|
|
21
|
+
sort_key_map = {
|
|
22
|
+
"name": lambda s: str(s.name or ""),
|
|
23
|
+
"teamspace": lambda s: str(s.teamspace.name or ""),
|
|
24
|
+
"status": lambda s: str(s.status or ""),
|
|
25
|
+
"machine": lambda s: str(s.machine or ""),
|
|
26
|
+
"cloud-account": lambda s: str(s.cloud_account or ""),
|
|
27
|
+
}
|
|
28
|
+
return sort_key_map.get(sort_by, lambda s: s.name)
|
|
29
|
+
|
|
30
|
+
def studios(
|
|
31
|
+
self,
|
|
32
|
+
teamspace: Optional[str] = None,
|
|
33
|
+
all: bool = False, # noqa: A002
|
|
34
|
+
sort_by: Optional[Literal["name", "teamspace", "status", "machine", "cloud-account"]] = None,
|
|
35
|
+
) -> None:
|
|
15
36
|
"""List studios for a given teamspace.
|
|
16
37
|
|
|
17
38
|
Args:
|
|
18
39
|
teamspace: the teamspace to list studios from. Should be specified as {owner}/{name}
|
|
19
40
|
If not provided, can be selected in an interactive menu.
|
|
41
|
+
all: if teamspece is not provided, list all studios in all teamspaces.
|
|
42
|
+
sort_by: the attribute to sort the studios by.
|
|
43
|
+
Can be one of "name", "teamspace", "status", "machine", "cloud-account".
|
|
20
44
|
|
|
21
45
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
46
|
+
studios = []
|
|
47
|
+
if all and not teamspace:
|
|
48
|
+
user = _get_authed_user()
|
|
49
|
+
possible_teamspaces = self._get_possible_teamspaces(user)
|
|
50
|
+
for ts in possible_teamspaces.values():
|
|
51
|
+
teamspace = Teamspace(**ts)
|
|
52
|
+
studios.extend(teamspace.studios)
|
|
53
|
+
else:
|
|
54
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
55
|
+
studios = resolved_teamspace.studios
|
|
25
56
|
|
|
26
57
|
table = Table(
|
|
27
58
|
pad_edge=True,
|
|
@@ -31,33 +62,59 @@ class _List(_TeamspacesMenu):
|
|
|
31
62
|
table.add_column("Status")
|
|
32
63
|
table.add_column("Machine")
|
|
33
64
|
table.add_column("Cloud account")
|
|
34
|
-
for studio in studios:
|
|
65
|
+
for studio in sorted(studios, key=self._sort_studios_key(sort_by)):
|
|
35
66
|
table.add_row(
|
|
36
67
|
studio.name,
|
|
37
68
|
f"{studio.teamspace.owner.name}/{studio.teamspace.name}",
|
|
38
69
|
str(studio.status),
|
|
39
|
-
str(studio.machine) if studio.machine is not None else None,
|
|
70
|
+
str(studio.machine) if studio.machine is not None else None, # when None the cell is empty
|
|
40
71
|
str(studio.cloud_account),
|
|
41
72
|
)
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def
|
|
74
|
+
Console().print(table)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _sort_jobs_key(sort_by: str) -> Callable[[Job], str]:
|
|
78
|
+
"""Return a key function to sort studios by a given attribute."""
|
|
79
|
+
sort_key_map = {
|
|
80
|
+
"name": lambda j: str(j.name or ""),
|
|
81
|
+
"teamspace": lambda j: str(j.teamspace.name or ""),
|
|
82
|
+
"status": lambda j: str(j.status or ""),
|
|
83
|
+
"machine": lambda j: str(j.machine or ""),
|
|
84
|
+
"studio": lambda j: str(j.studio or ""),
|
|
85
|
+
"image": lambda j: str(j.image or ""),
|
|
86
|
+
"cloud-account": lambda j: str(j.cloud_account or ""),
|
|
87
|
+
}
|
|
88
|
+
return sort_key_map.get(sort_by, lambda j: j.name)
|
|
89
|
+
|
|
90
|
+
def jobs(
|
|
91
|
+
self,
|
|
92
|
+
teamspace: Optional[str] = None,
|
|
93
|
+
all: bool = False, # noqa: A002
|
|
94
|
+
sort_by: Optional[Literal["name", "teamspace", "status", "studio", "machine", "image", "cloud-account"]] = None,
|
|
95
|
+
) -> None:
|
|
47
96
|
"""List jobs for a given teamspace.
|
|
48
97
|
|
|
49
98
|
Args:
|
|
50
99
|
teamspace: the teamspace to list jobs from. Should be specified as {owner}/{name}
|
|
51
100
|
If not provided, can be selected in an interactive menu.
|
|
101
|
+
all: if teamspece is not provided, list all jobs in all teamspaces.
|
|
102
|
+
sort_by: the attribute to sort the jobs by.
|
|
103
|
+
Can be one of "name", "teamspace", "status", "studio", "machine", "image", "cloud-account".
|
|
52
104
|
|
|
53
105
|
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
106
|
+
jobs = []
|
|
107
|
+
if all and not teamspace:
|
|
108
|
+
user = _get_authed_user()
|
|
109
|
+
possible_teamspaces = self._get_possible_teamspaces(user)
|
|
110
|
+
for ts in possible_teamspaces.values():
|
|
111
|
+
teamspace = Teamspace(**ts)
|
|
112
|
+
jobs.extend(teamspace.jobs)
|
|
113
|
+
else:
|
|
114
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
115
|
+
jobs = resolved_teamspace.jobs
|
|
57
116
|
|
|
58
|
-
table = Table(
|
|
59
|
-
pad_edge=True,
|
|
60
|
-
)
|
|
117
|
+
table = Table(pad_edge=True)
|
|
61
118
|
table.add_column("Name")
|
|
62
119
|
table.add_column("Teamspace")
|
|
63
120
|
table.add_column("Studio")
|
|
@@ -65,36 +122,64 @@ class _List(_TeamspacesMenu):
|
|
|
65
122
|
table.add_column("Status")
|
|
66
123
|
table.add_column("Machine")
|
|
67
124
|
table.add_column("Total Cost")
|
|
68
|
-
for j in jobs:
|
|
125
|
+
for j in sorted(jobs, key=self._sort_jobs_key(sort_by)):
|
|
69
126
|
# we know we just fetched these, so no need to refetch
|
|
70
127
|
j._prevent_refetch_latest = True
|
|
71
128
|
j._internal_job._prevent_refetch_latest = True
|
|
72
129
|
|
|
73
130
|
studio = j.studio
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def
|
|
131
|
+
with suppress(RuntimeError):
|
|
132
|
+
table.add_row(
|
|
133
|
+
j.name,
|
|
134
|
+
f"{j.teamspace.owner.name}/{j.teamspace.name}",
|
|
135
|
+
studio.name if studio else None,
|
|
136
|
+
j.image,
|
|
137
|
+
str(j.status) if j.status is not None else None,
|
|
138
|
+
str(j.machine),
|
|
139
|
+
f"{j.total_cost:.3f}",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
Console().print(table)
|
|
143
|
+
|
|
144
|
+
def _sort_mmts_key(self, sort_by: str) -> Callable[[V1MultiMachineJob], str]:
|
|
145
|
+
"""Return a key function to sort multi-machine jobs by a given attribute."""
|
|
146
|
+
sort_key_map = {
|
|
147
|
+
"name": lambda j: str(j.name or ""),
|
|
148
|
+
"teamspace": lambda j: str(j.teamspace.name or ""),
|
|
149
|
+
"studio": lambda j: str(j.studio.name or ""),
|
|
150
|
+
"image": lambda j: str(j.image or ""),
|
|
151
|
+
"status": lambda j: str(j.status or ""),
|
|
152
|
+
"machine": lambda j: str(j.machine or ""),
|
|
153
|
+
"cloud-account": lambda j: str(j.cloud_account or ""),
|
|
154
|
+
}
|
|
155
|
+
return sort_key_map.get(sort_by, lambda j: j.name)
|
|
156
|
+
|
|
157
|
+
def mmts(
|
|
158
|
+
self,
|
|
159
|
+
teamspace: Optional[str] = None,
|
|
160
|
+
all: bool = False, # noqa: A002
|
|
161
|
+
sort_by: Optional[Literal["name", "teamspace", "studio", "image", "status", "machine", "cloud-account"]] = None,
|
|
162
|
+
) -> None:
|
|
88
163
|
"""List multi-machine jobs for a given teamspace.
|
|
89
164
|
|
|
90
165
|
Args:
|
|
91
166
|
teamspace: the teamspace to list jobs from. Should be specified as {owner}/{name}
|
|
92
167
|
If not provided, can be selected in an interactive menu.
|
|
168
|
+
all: if teamspece is not provided, list all multi-machine jobs in all teamspaces.
|
|
169
|
+
sort_by: the attribute to sort the multi-machine jobs by.
|
|
170
|
+
Can be one of "name", "teamspace", "studio", "image", "status", "machine", "cloud-account".
|
|
93
171
|
|
|
94
172
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
173
|
+
jobs = []
|
|
174
|
+
if all and not teamspace:
|
|
175
|
+
user = _get_authed_user()
|
|
176
|
+
possible_teamspaces = self._get_possible_teamspaces(user)
|
|
177
|
+
for ts in possible_teamspaces.values():
|
|
178
|
+
teamspace = Teamspace(**ts)
|
|
179
|
+
jobs.extend(teamspace.multi_machine_jobs)
|
|
180
|
+
else:
|
|
181
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
182
|
+
jobs = resolved_teamspace.multi_machine_jobs
|
|
98
183
|
|
|
99
184
|
table = Table(pad_edge=True)
|
|
100
185
|
table.add_column("Name")
|
|
@@ -105,25 +190,26 @@ class _List(_TeamspacesMenu):
|
|
|
105
190
|
table.add_column("Machine")
|
|
106
191
|
table.add_column("Num Machines")
|
|
107
192
|
table.add_column("Total Cost")
|
|
108
|
-
for j in jobs:
|
|
193
|
+
for j in sorted(jobs, key=self._sort_mmts_key(sort_by)):
|
|
109
194
|
# we know we just fetched these, so no need to refetch
|
|
110
195
|
j._prevent_refetch_latest = True
|
|
111
|
-
|
|
196
|
+
with suppress(AttributeError):
|
|
197
|
+
j._internal_job._prevent_refetch_latest = True
|
|
112
198
|
|
|
113
199
|
studio = j.studio
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
200
|
+
with suppress(RuntimeError):
|
|
201
|
+
table.add_row(
|
|
202
|
+
j.name,
|
|
203
|
+
f"{j.teamspace.owner.name}/{j.teamspace.name}",
|
|
204
|
+
studio.name if studio else None,
|
|
205
|
+
j.image,
|
|
206
|
+
str(j.status),
|
|
207
|
+
str(j.machine),
|
|
208
|
+
str(j.num_machines),
|
|
209
|
+
str(j.total_cost),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
Console().print(table)
|
|
127
213
|
|
|
128
214
|
def containers(self, teamspace: Optional[str] = None) -> None:
|
|
129
215
|
"""Display the list of available containers.
|
|
@@ -141,8 +227,7 @@ class _List(_TeamspacesMenu):
|
|
|
141
227
|
table.add_column("CREATED")
|
|
142
228
|
for repo in result:
|
|
143
229
|
table.add_row(repo["REPOSITORY"], repo["IMAGE ID"], repo["CREATED"])
|
|
144
|
-
|
|
145
|
-
console.print(table)
|
|
230
|
+
Console().print(table)
|
|
146
231
|
|
|
147
232
|
def machines(self) -> None:
|
|
148
233
|
"""Display the list of available machines."""
|
|
@@ -156,5 +241,4 @@ class _List(_TeamspacesMenu):
|
|
|
156
241
|
for name in sorted(machine_types):
|
|
157
242
|
table.add_row(name)
|
|
158
243
|
|
|
159
|
-
|
|
160
|
-
console.print(table)
|
|
244
|
+
Console().print(table)
|
lightning_sdk/cli/mmts_menu.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
+
from rich.console import Console
|
|
3
4
|
from simple_term_menu import TerminalMenu
|
|
4
5
|
|
|
5
6
|
from lightning_sdk.cli.exceptions import StudioCliError
|
|
@@ -20,7 +21,7 @@ class _MMTsMenu:
|
|
|
20
21
|
if j.name == mmt:
|
|
21
22
|
return j
|
|
22
23
|
|
|
23
|
-
print("Could not find Multi-Machine Job {mmt}, please select it from the list:")
|
|
24
|
+
Console().print("Could not find Multi-Machine Job {mmt}, please select it from the list:")
|
|
24
25
|
return self._get_mmt_from_interactive_menu(possible_mmts)
|
|
25
26
|
|
|
26
27
|
@staticmethod
|
lightning_sdk/cli/run.py
CHANGED
|
@@ -8,7 +8,7 @@ from lightning_sdk.teamspace import Teamspace
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from lightning_sdk.cli.legacy import _LegacyLightningCLI
|
|
10
10
|
|
|
11
|
-
_MACHINE_VALUES = tuple([machine.
|
|
11
|
+
_MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class _Run:
|
|
@@ -156,16 +156,12 @@ class _Run:
|
|
|
156
156
|
machine = "CPU"
|
|
157
157
|
machine_enum: Union[str, Machine]
|
|
158
158
|
try:
|
|
159
|
-
machine_enum = Machine
|
|
159
|
+
machine_enum = getattr(Machine, machine.upper(), Machine(machine, machine))
|
|
160
160
|
except KeyError:
|
|
161
161
|
machine_enum = machine
|
|
162
162
|
|
|
163
163
|
resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
|
|
164
164
|
|
|
165
|
-
if cloud_account is None:
|
|
166
|
-
cloud_account = resolved_teamspace.default_cloud_account
|
|
167
|
-
machine_enum = Machine(machine.upper())
|
|
168
|
-
|
|
169
165
|
path_mappings_dict = self._resolve_path_mapping(path_mappings=path_mappings)
|
|
170
166
|
|
|
171
167
|
Job.run(
|
|
@@ -222,13 +218,11 @@ class _Run:
|
|
|
222
218
|
machine = "CPU"
|
|
223
219
|
machine_enum: Union[str, Machine]
|
|
224
220
|
try:
|
|
225
|
-
machine_enum = Machine
|
|
221
|
+
machine_enum = getattr(Machine, machine.upper(), Machine(machine, machine))
|
|
226
222
|
except KeyError:
|
|
227
223
|
machine_enum = machine
|
|
228
224
|
|
|
229
225
|
resolved_teamspace = Teamspace(name=teamspace, org=org, user=user)
|
|
230
|
-
if cloud_account is None:
|
|
231
|
-
cloud_account = resolved_teamspace.default_cloud_account
|
|
232
226
|
|
|
233
227
|
if image is None:
|
|
234
228
|
raise RuntimeError("Image needs to be specified to run a multi-machine job")
|
lightning_sdk/cli/serve.py
CHANGED
|
@@ -167,7 +167,6 @@ class _Docker:
|
|
|
167
167
|
import litserve as ls
|
|
168
168
|
from litserve import docker_builder
|
|
169
169
|
|
|
170
|
-
console = Console()
|
|
171
170
|
requirements = ""
|
|
172
171
|
if os.path.exists("requirements.txt"):
|
|
173
172
|
requirements = "-r requirements.txt"
|
|
@@ -210,5 +209,5 @@ Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additio
|
|
|
210
209
|
[bold]To push the container to a registry:[/bold]
|
|
211
210
|
> [underline]docker push {tag}[/underline]
|
|
212
211
|
"""
|
|
213
|
-
|
|
212
|
+
Console().print(success_msg)
|
|
214
213
|
return os.path.abspath("Dockerfile")
|
lightning_sdk/cli/start.py
CHANGED
|
@@ -7,7 +7,7 @@ class _Start:
|
|
|
7
7
|
"""Start resources on the Lightning AI platform."""
|
|
8
8
|
|
|
9
9
|
def __init__(self) -> None:
|
|
10
|
-
_machine_values = tuple([machine.
|
|
10
|
+
_machine_values = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
11
11
|
|
|
12
12
|
docstr_studio = f"""Start a studio on a given machine.
|
|
13
13
|
|
|
@@ -36,7 +36,7 @@ class _Start:
|
|
|
36
36
|
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
|
|
37
37
|
|
|
38
38
|
try:
|
|
39
|
-
resolved_machine = Machine
|
|
39
|
+
resolved_machine = getattr(Machine, machine.upper(), Machine(machine, machine))
|
|
40
40
|
except KeyError:
|
|
41
41
|
resolved_machine = machine
|
|
42
42
|
|
lightning_sdk/cli/stop.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
3
5
|
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
4
6
|
from lightning_sdk.studio import Studio
|
|
5
7
|
|
|
@@ -20,7 +22,7 @@ class _Stop(_JobAndMMTAction):
|
|
|
20
22
|
job = super().job(name=name, teamspace=teamspace)
|
|
21
23
|
|
|
22
24
|
job.stop()
|
|
23
|
-
print(f"Successfully stopped {job.name}!")
|
|
25
|
+
Console().print(f"Successfully stopped {job.name}!")
|
|
24
26
|
|
|
25
27
|
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
26
28
|
"""Stop a multi-machine job.
|
|
@@ -35,7 +37,7 @@ class _Stop(_JobAndMMTAction):
|
|
|
35
37
|
mmt = super().mmt(name=name, teamspace=teamspace)
|
|
36
38
|
|
|
37
39
|
mmt.stop()
|
|
38
|
-
print(f"Successfully stopped {mmt.name}!")
|
|
40
|
+
Console().print(f"Successfully stopped {mmt.name}!")
|
|
39
41
|
|
|
40
42
|
def studio(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
41
43
|
"""Stop a running studio.
|
|
@@ -60,4 +62,4 @@ class _Stop(_JobAndMMTAction):
|
|
|
60
62
|
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
|
|
61
63
|
|
|
62
64
|
studio.stop()
|
|
63
|
-
print("Studio successfully stopped")
|
|
65
|
+
Console().print("Studio successfully stopped")
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
+
from rich.console import Console
|
|
3
4
|
from simple_term_menu import TerminalMenu
|
|
4
5
|
|
|
6
|
+
from lightning_sdk import Studio
|
|
5
7
|
from lightning_sdk.api import OrgApi, TeamspaceApi
|
|
6
8
|
from lightning_sdk.user import User
|
|
7
9
|
|
|
@@ -18,7 +20,7 @@ class _StudiosMenu:
|
|
|
18
20
|
if st["teamspace"] == teamspace and name == st["name"]:
|
|
19
21
|
return st
|
|
20
22
|
|
|
21
|
-
print("Could not find Studio {studio}, please select it from the list:")
|
|
23
|
+
Console().print("Could not find Studio {studio}, please select it from the list:")
|
|
22
24
|
return self._get_studio_from_interactive_menu(possible_studios)
|
|
23
25
|
|
|
24
26
|
@staticmethod
|
|
@@ -76,3 +78,24 @@ class _StudiosMenu:
|
|
|
76
78
|
possible_studios.append({"name": st.name, **teamspaces[teamspace_id]})
|
|
77
79
|
|
|
78
80
|
return possible_studios
|
|
81
|
+
|
|
82
|
+
def _get_studio(self, name: str, teamspace: str) -> Studio:
|
|
83
|
+
"""Get studio object from name and teamspace.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
name: Name of the studio
|
|
87
|
+
teamspace: Name of the teamspace
|
|
88
|
+
"""
|
|
89
|
+
if teamspace:
|
|
90
|
+
ts_splits = teamspace.split("/")
|
|
91
|
+
if len(ts_splits) != 2:
|
|
92
|
+
raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
|
|
93
|
+
owner, teamspace = ts_splits
|
|
94
|
+
else:
|
|
95
|
+
owner, teamspace = None, None
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
|
|
99
|
+
except (RuntimeError, ValueError):
|
|
100
|
+
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
|
|
101
|
+
return studio
|
lightning_sdk/cli/switch.py
CHANGED
|
@@ -7,7 +7,7 @@ class _Switch:
|
|
|
7
7
|
"""Switch machines for resources on the Lightning AI platform."""
|
|
8
8
|
|
|
9
9
|
def __init__(self) -> None:
|
|
10
|
-
_machine_values = tuple([machine.
|
|
10
|
+
_machine_values = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
|
|
11
11
|
|
|
12
12
|
docstr_studio = f"""Switch a studio to a given machine.
|
|
13
13
|
|
|
@@ -36,7 +36,7 @@ class _Switch:
|
|
|
36
36
|
studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
|
|
37
37
|
|
|
38
38
|
try:
|
|
39
|
-
resolved_machine = Machine
|
|
39
|
+
resolved_machine = getattr(Machine, machine.upper(), Machine(machine, machine))
|
|
40
40
|
except KeyError:
|
|
41
41
|
resolved_machine = machine
|
|
42
42
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
+
from rich.console import Console
|
|
3
4
|
from simple_term_menu import TerminalMenu
|
|
4
5
|
|
|
5
6
|
from lightning_sdk.api import OrgApi
|
|
@@ -26,7 +27,7 @@ class _TeamspacesMenu:
|
|
|
26
27
|
if ts["name"] == name and (ts["user"] == owner or ts["org"] == owner):
|
|
27
28
|
return ts
|
|
28
29
|
|
|
29
|
-
print("Could not find Teamspace {teamspace}, please select it from the list:")
|
|
30
|
+
Console().print(f"Could not find Teamspace {teamspace}, please select it from the list:")
|
|
30
31
|
return self._get_teamspace_from_interactive_menu(possible_teamspaces)
|
|
31
32
|
|
|
32
33
|
@staticmethod
|
lightning_sdk/cli/upload.py
CHANGED
|
@@ -71,6 +71,7 @@ class _Uploads(_StudiosMenu, _TeamspacesMenu):
|
|
|
71
71
|
If not specified, will use the file or directory name of the path you want to upload
|
|
72
72
|
and place it in your home directory.
|
|
73
73
|
"""
|
|
74
|
+
console = Console()
|
|
74
75
|
if remote_path is None:
|
|
75
76
|
remote_path = os.path.basename(path)
|
|
76
77
|
|
|
@@ -81,7 +82,7 @@ class _Uploads(_StudiosMenu, _TeamspacesMenu):
|
|
|
81
82
|
|
|
82
83
|
selected_studio = self._resolve_studio(studio)
|
|
83
84
|
|
|
84
|
-
print(f"Uploading to {selected_studio.teamspace.name}/{selected_studio.name}")
|
|
85
|
+
console.print(f"Uploading to {selected_studio.teamspace.name}/{selected_studio.name}")
|
|
85
86
|
|
|
86
87
|
pairs = {}
|
|
87
88
|
for root, _, files in os.walk(path):
|
|
@@ -112,7 +113,7 @@ class _Uploads(_StudiosMenu, _TeamspacesMenu):
|
|
|
112
113
|
+ "/studios/"
|
|
113
114
|
+ selected_studio.name
|
|
114
115
|
)
|
|
115
|
-
print(f"See your files at {studio_url}")
|
|
116
|
+
console.print(f"See your files at {studio_url}")
|
|
116
117
|
|
|
117
118
|
def file(self, path: str, studio: Optional[str] = None, remote_path: Optional[str] = None) -> None:
|
|
118
119
|
"""Upload a file to a Studio.
|
|
@@ -126,6 +127,7 @@ class _Uploads(_StudiosMenu, _TeamspacesMenu):
|
|
|
126
127
|
If not specified, will use the name of the file you want to upload
|
|
127
128
|
and place it in your home directory.
|
|
128
129
|
"""
|
|
130
|
+
console = Console()
|
|
129
131
|
if remote_path is None:
|
|
130
132
|
remote_path = os.path.basename(path)
|
|
131
133
|
|
|
@@ -136,7 +138,7 @@ class _Uploads(_StudiosMenu, _TeamspacesMenu):
|
|
|
136
138
|
|
|
137
139
|
selected_studio = self._resolve_studio(studio)
|
|
138
140
|
|
|
139
|
-
print(f"Uploading to {selected_studio.teamspace.name}/{selected_studio.name}")
|
|
141
|
+
console.print(f"Uploading to {selected_studio.teamspace.name}/{selected_studio.name}")
|
|
140
142
|
|
|
141
143
|
self._single_file_upload(selected_studio, path, remote_path, True)
|
|
142
144
|
|
|
@@ -149,7 +151,7 @@ class _Uploads(_StudiosMenu, _TeamspacesMenu):
|
|
|
149
151
|
+ "/studios/"
|
|
150
152
|
+ selected_studio.name
|
|
151
153
|
)
|
|
152
|
-
print(f"See your file at {studio_url}")
|
|
154
|
+
console.print(f"See your file at {studio_url}")
|
|
153
155
|
|
|
154
156
|
def container(self, container: str, tag: str = "latest", teamspace: Optional[str] = None) -> None:
|
|
155
157
|
teamspace = self._resolve_teamspace(teamspace)
|
lightning_sdk/helpers.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import functools
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
2
5
|
import warnings
|
|
3
6
|
from typing import Optional
|
|
4
7
|
|
|
5
8
|
import requests
|
|
9
|
+
import tqdm
|
|
10
|
+
import tqdm.std
|
|
6
11
|
from packaging import version as packaging_version
|
|
7
12
|
|
|
8
13
|
__package_name__ = "lightning-sdk"
|
|
@@ -47,3 +52,18 @@ def _check_version_and_prompt_upgrade(curr_version: str) -> None:
|
|
|
47
52
|
UserWarning,
|
|
48
53
|
)
|
|
49
54
|
return
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _set_tqdm_envvars_noninteractive() -> None:
|
|
58
|
+
# note: stderr is the default stream tqdm writes progressbars to
|
|
59
|
+
# so we check that one.
|
|
60
|
+
if os.isatty(sys.stderr.fileno()):
|
|
61
|
+
os.unsetenv("TQDM_POSITION")
|
|
62
|
+
os.unsetenv("TQDM_MININTERVAL")
|
|
63
|
+
else:
|
|
64
|
+
# makes use of https://github.com/tqdm/tqdm/blob/master/tqdm/utils.py#L34 to set defaults
|
|
65
|
+
os.environ.update({"TQDM_POSITION": "-1", "TQDM_MININTERVAL": "1"})
|
|
66
|
+
|
|
67
|
+
# reload to make sure env vars are parsed again
|
|
68
|
+
importlib.reload(tqdm.std)
|
|
69
|
+
importlib.reload(tqdm)
|
lightning_sdk/job/job.py
CHANGED