lightning-sdk 0.1.46__py3-none-any.whl → 0.1.48__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/job_api.py +37 -6
- lightning_sdk/api/lit_container_api.py +19 -0
- lightning_sdk/api/mmt_api.py +18 -1
- lightning_sdk/api/teamspace_api.py +19 -1
- lightning_sdk/api/utils.py +1 -1
- lightning_sdk/cli/delete.py +58 -0
- lightning_sdk/cli/entrypoint.py +8 -0
- lightning_sdk/cli/inspect.py +31 -0
- lightning_sdk/cli/job_and_mmt_action.py +37 -0
- lightning_sdk/cli/jobs_menu.py +57 -0
- lightning_sdk/cli/list.py +54 -0
- lightning_sdk/cli/mmts_menu.py +57 -0
- lightning_sdk/cli/run.py +12 -0
- lightning_sdk/cli/stop.py +37 -0
- lightning_sdk/cli/teamspace_menu.py +94 -0
- lightning_sdk/job/base.py +70 -2
- lightning_sdk/job/job.py +59 -9
- lightning_sdk/job/v1.py +28 -0
- lightning_sdk/job/v2.py +40 -4
- lightning_sdk/job/work.py +10 -1
- lightning_sdk/lightning_cloud/openapi/__init__.py +3 -0
- lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +202 -0
- lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +250 -8
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +3 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_aws_direct_v1.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_repository_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_api.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_state.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +29 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_job.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_model.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_resource_visibility.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +27 -53
- lightning_sdk/lightning_cloud/openapi/models/v1_vultr_direct_v1.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/version_default_body.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/versions_version_body.py +123 -0
- lightning_sdk/lightning_cloud/utils/data_connection.py +1 -1
- lightning_sdk/lit_container.py +57 -0
- lightning_sdk/machine.py +4 -0
- lightning_sdk/mmt/base.py +39 -1
- lightning_sdk/mmt/mmt.py +59 -14
- lightning_sdk/mmt/v1.py +30 -2
- lightning_sdk/mmt/v2.py +31 -2
- lightning_sdk/status.py +11 -7
- lightning_sdk/teamspace.py +51 -1
- {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/METADATA +1 -1
- {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/RECORD +52 -39
- {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py
CHANGED
lightning_sdk/api/job_api.py
CHANGED
|
@@ -171,8 +171,18 @@ class JobApiV1:
|
|
|
171
171
|
data = urlopen(resp.url).read().decode("utf-8")
|
|
172
172
|
return remove_datetime_prefix(str(data))
|
|
173
173
|
|
|
174
|
+
def get_command(self, job: Externalv1LightningappInstance) -> str:
|
|
175
|
+
env = job.spec.env
|
|
176
|
+
|
|
177
|
+
for e in env:
|
|
178
|
+
if e.name == "COMMAND":
|
|
179
|
+
return e.value
|
|
180
|
+
|
|
181
|
+
raise RuntimeError("Could not extract command from app")
|
|
182
|
+
|
|
174
183
|
|
|
175
184
|
class JobApiV2:
|
|
185
|
+
# these are stages the job can be in.
|
|
176
186
|
v2_job_state_pending = "pending"
|
|
177
187
|
v2_job_state_running = "running"
|
|
178
188
|
v2_job_state_stopped = "stopped"
|
|
@@ -180,6 +190,9 @@ class JobApiV2:
|
|
|
180
190
|
v2_job_state_failed = "failed"
|
|
181
191
|
v2_job_state_stopping = "stopping"
|
|
182
192
|
|
|
193
|
+
# this is the user action to stop the job.
|
|
194
|
+
v2_job_state_stop = "stop"
|
|
195
|
+
|
|
183
196
|
def __init__(self) -> None:
|
|
184
197
|
self._cloud_url = _cloud_url()
|
|
185
198
|
self._client = LightningClient(max_tries=7)
|
|
@@ -199,6 +212,7 @@ class JobApiV2:
|
|
|
199
212
|
cloud_account_auth: bool,
|
|
200
213
|
artifacts_local: Optional[str],
|
|
201
214
|
artifacts_remote: Optional[str],
|
|
215
|
+
entrypoint: str,
|
|
202
216
|
) -> V1Job:
|
|
203
217
|
env_vars = []
|
|
204
218
|
if env is not None:
|
|
@@ -213,6 +227,7 @@ class JobApiV2:
|
|
|
213
227
|
cloudspace_id=studio_id or "",
|
|
214
228
|
cluster_id=cloud_account or "",
|
|
215
229
|
command=command or "",
|
|
230
|
+
entrypoint=entrypoint,
|
|
216
231
|
env=env_vars,
|
|
217
232
|
image=image or "",
|
|
218
233
|
instance_name=instance_name,
|
|
@@ -251,7 +266,7 @@ class JobApiV2:
|
|
|
251
266
|
return
|
|
252
267
|
|
|
253
268
|
if current_state != Status.Stopping:
|
|
254
|
-
update_body = JobsIdBody1(cloudspace_id=current_job.spec.cloudspace_id, state=self.
|
|
269
|
+
update_body = JobsIdBody1(cloudspace_id=current_job.spec.cloudspace_id, state=self.v2_job_state_stop)
|
|
255
270
|
self._client.jobs_service_update_job(body=update_body, project_id=teamspace_id, id=job_id)
|
|
256
271
|
|
|
257
272
|
while True:
|
|
@@ -276,11 +291,27 @@ class JobApiV2:
|
|
|
276
291
|
data = urlopen(resp.url).read().decode("utf-8")
|
|
277
292
|
return remove_datetime_prefix(str(data))
|
|
278
293
|
|
|
279
|
-
def get_studio_name(self, job: V1Job) -> str:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
def get_studio_name(self, job: V1Job) -> Optional[str]:
|
|
295
|
+
if job.spec.cloudspace_id:
|
|
296
|
+
cs: V1CloudSpace = self._client.cloud_space_service_get_cloud_space(
|
|
297
|
+
project_id=job.project_id, id=job.spec.cloudspace_id
|
|
298
|
+
)
|
|
299
|
+
return cs.name
|
|
300
|
+
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
def get_image_name(self, job: V1Job) -> Optional[str]:
|
|
304
|
+
return job.spec.image or None
|
|
305
|
+
|
|
306
|
+
def get_command(self, job: V1Job) -> str:
|
|
307
|
+
return job.spec.command
|
|
308
|
+
|
|
309
|
+
def get_mmt_name(self, job: V1Job) -> str:
|
|
310
|
+
if job.multi_machine_job_id:
|
|
311
|
+
splits = job.name.rsplit("-", 1)
|
|
312
|
+
if len(splits) == 2:
|
|
313
|
+
return splits[0]
|
|
314
|
+
return ""
|
|
284
315
|
|
|
285
316
|
def _job_state_to_external(self, state: str) -> "Status":
|
|
286
317
|
from lightning_sdk.status import Status
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.lightning_cloud.openapi.models import V1DeleteLitRepositoryResponse
|
|
4
|
+
from lightning_sdk.lightning_cloud.rest_client import LightningClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LitContainerApi:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self._client = LightningClient(max_tries=3)
|
|
10
|
+
|
|
11
|
+
def list_containers(self, project_id: str) -> List:
|
|
12
|
+
project = self._client.lit_registry_service_get_lit_project_registry(project_id)
|
|
13
|
+
return project.repositories
|
|
14
|
+
|
|
15
|
+
def delete_container(self, project_id: str, container: str) -> V1DeleteLitRepositoryResponse:
|
|
16
|
+
try:
|
|
17
|
+
return self._client.lit_registry_service_delete_lit_repository(project_id, container)
|
|
18
|
+
except Exception as ex:
|
|
19
|
+
raise ValueError(f"Could not delete container {container} from project {project_id}") from ex
|
lightning_sdk/api/mmt_api.py
CHANGED
|
@@ -16,6 +16,7 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
16
16
|
Externalv1LightningappInstance,
|
|
17
17
|
MultimachinejobsIdBody,
|
|
18
18
|
ProjectIdMultimachinejobsBody,
|
|
19
|
+
V1CloudSpace,
|
|
19
20
|
V1EnvVar,
|
|
20
21
|
V1Job,
|
|
21
22
|
V1JobSpec,
|
|
@@ -86,6 +87,7 @@ class MMTApiV2:
|
|
|
86
87
|
cloud_account_auth: bool,
|
|
87
88
|
artifacts_local: Optional[str],
|
|
88
89
|
artifacts_remote: Optional[str],
|
|
90
|
+
entrypoint: str,
|
|
89
91
|
) -> V1MultiMachineJob:
|
|
90
92
|
env_vars = []
|
|
91
93
|
if env is not None:
|
|
@@ -100,7 +102,7 @@ class MMTApiV2:
|
|
|
100
102
|
cloudspace_id=studio_id or "",
|
|
101
103
|
cluster_id=cloud_account or "",
|
|
102
104
|
command=command or "",
|
|
103
|
-
entrypoint=
|
|
105
|
+
entrypoint=entrypoint,
|
|
104
106
|
env=env_vars,
|
|
105
107
|
image=image or "",
|
|
106
108
|
instance_name=instance_name,
|
|
@@ -179,6 +181,21 @@ class MMTApiV2:
|
|
|
179
181
|
return Status.Failed
|
|
180
182
|
return Status.Pending
|
|
181
183
|
|
|
184
|
+
def get_studio_name(self, job: V1MultiMachineJob) -> Optional[str]:
|
|
185
|
+
if job.spec.cloudspace_id:
|
|
186
|
+
cs: V1CloudSpace = self._client.cloud_space_service_get_cloud_space(
|
|
187
|
+
project_id=job.project_id, id=job.spec.cloudspace_id
|
|
188
|
+
)
|
|
189
|
+
return cs.name
|
|
190
|
+
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def get_image_name(self, job: V1MultiMachineJob) -> Optional[str]:
|
|
194
|
+
return job.spec.image or None
|
|
195
|
+
|
|
196
|
+
def get_command(self, job: V1MultiMachineJob) -> str:
|
|
197
|
+
return job.spec.command
|
|
198
|
+
|
|
182
199
|
def _get_job_machine_from_spec(self, spec: V1JobSpec) -> "Machine":
|
|
183
200
|
instance_name = spec.instance_name
|
|
184
201
|
instance_type = spec.instance_type
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Dict, List, Optional
|
|
3
|
+
from typing import Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from tqdm.auto import tqdm
|
|
6
6
|
|
|
7
7
|
from lightning_sdk.api.utils import _download_model_files, _DummyBody, _get_model_version, _ModelFileUploader
|
|
8
8
|
from lightning_sdk.lightning_cloud.login import Auth
|
|
9
9
|
from lightning_sdk.lightning_cloud.openapi import (
|
|
10
|
+
Externalv1LightningappInstance,
|
|
10
11
|
ModelIdVersionsBody,
|
|
11
12
|
ModelsStoreApi,
|
|
12
13
|
ProjectIdAgentsBody,
|
|
@@ -14,7 +15,9 @@ from lightning_sdk.lightning_cloud.openapi import (
|
|
|
14
15
|
V1Assistant,
|
|
15
16
|
V1CloudSpace,
|
|
16
17
|
V1Endpoint,
|
|
18
|
+
V1Job,
|
|
17
19
|
V1ModelVersionArchive,
|
|
20
|
+
V1MultiMachineJob,
|
|
18
21
|
V1Project,
|
|
19
22
|
V1ProjectClusterBinding,
|
|
20
23
|
V1PromptSuggestion,
|
|
@@ -277,3 +280,18 @@ class TeamspaceApi:
|
|
|
277
280
|
download_dir=download_dir,
|
|
278
281
|
progress_bar=progress_bar,
|
|
279
282
|
)
|
|
283
|
+
|
|
284
|
+
def list_jobs(self, teamspace_id: str) -> Tuple[List[Externalv1LightningappInstance], List[V1Job]]:
|
|
285
|
+
apps = self._client.lightningapp_instance_service_list_lightningapp_instances(
|
|
286
|
+
project_id=teamspace_id, source_app="job_run_plugin"
|
|
287
|
+
).lightningapps
|
|
288
|
+
jobs = self._client.jobs_service_list_jobs(project_id=teamspace_id, standalone=True).jobs
|
|
289
|
+
|
|
290
|
+
return apps, jobs
|
|
291
|
+
|
|
292
|
+
def list_mmts(self, teamspace_id: str) -> Tuple[List[Externalv1LightningappInstance], List[V1MultiMachineJob]]:
|
|
293
|
+
apps = self._client.lightningapp_instance_service_list_lightningapp_instances(
|
|
294
|
+
project_id=teamspace_id, source_app="distributed_plugin"
|
|
295
|
+
).lightningapps
|
|
296
|
+
jobs = self._client.jobs_service_list_multi_machine_jobs(project_id=teamspace_id).multi_machine_jobs
|
|
297
|
+
return apps, jobs
|
lightning_sdk/api/utils.py
CHANGED
|
@@ -353,7 +353,7 @@ def _machine_to_compute_name(machine: Union[Machine, str]) -> str:
|
|
|
353
353
|
|
|
354
354
|
_COMPUTE_NAME_TO_MACHINE: Dict[str, Machine] = {v: k for k, v in _MACHINE_TO_COMPUTE_NAME.items()}
|
|
355
355
|
|
|
356
|
-
_DEFAULT_CLOUD_URL = "https://lightning.ai
|
|
356
|
+
_DEFAULT_CLOUD_URL = "https://lightning.ai"
|
|
357
357
|
|
|
358
358
|
|
|
359
359
|
def _get_cloud_url() -> str:
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.cli.exceptions import StudioCliError
|
|
4
|
+
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
5
|
+
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
6
|
+
from lightning_sdk.lit_container import LitContainer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _Delete(_JobAndMMTAction, _TeamspacesMenu):
|
|
10
|
+
"""Delete resources on the Lightning AI platform."""
|
|
11
|
+
|
|
12
|
+
def container(self, container: str, teamspace: Optional[str] = None) -> None:
|
|
13
|
+
"""Delete a docker container.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
container: The name of the container to delete.
|
|
17
|
+
teamspace: The teamspace to delete the container from. Should be specified as {owner}/{name}
|
|
18
|
+
If not provided, can be selected in an interactive menu.
|
|
19
|
+
"""
|
|
20
|
+
api = LitContainer()
|
|
21
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
22
|
+
try:
|
|
23
|
+
api.delete_container(container, resolved_teamspace.name, resolved_teamspace.owner.name)
|
|
24
|
+
print(f"Container {container} deleted successfully.")
|
|
25
|
+
except Exception as e:
|
|
26
|
+
raise StudioCliError(
|
|
27
|
+
f"Could not delete container {container} from project {resolved_teamspace.name}: {e}"
|
|
28
|
+
) from None
|
|
29
|
+
|
|
30
|
+
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
31
|
+
"""Delete a job.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
35
|
+
teamspace: the name of the teamspace the job lives in.
|
|
36
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
37
|
+
If not specified can be selected interactively.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
job = super().job(name=name, teamspace=teamspace)
|
|
41
|
+
|
|
42
|
+
job.delete()
|
|
43
|
+
print(f"Successfully deleted {job.name}!")
|
|
44
|
+
|
|
45
|
+
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
46
|
+
"""Delete a multi-machine job.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
50
|
+
teamspace: the name of the teamspace the job lives in.
|
|
51
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
52
|
+
If not specified can be selected interactively.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
mmt = super().mmt(name=name, teamspace=teamspace)
|
|
56
|
+
|
|
57
|
+
mmt.delete()
|
|
58
|
+
print(f"Successfully deleted {mmt.name}!")
|
lightning_sdk/cli/entrypoint.py
CHANGED
|
@@ -3,10 +3,14 @@ from lightning_utilities.core.imports import RequirementCache
|
|
|
3
3
|
|
|
4
4
|
from lightning_sdk.api.studio_api import _cloud_url
|
|
5
5
|
from lightning_sdk.cli.ai_hub import _AIHub
|
|
6
|
+
from lightning_sdk.cli.delete import _Delete
|
|
6
7
|
from lightning_sdk.cli.download import _Downloads
|
|
8
|
+
from lightning_sdk.cli.inspect import _Inspect
|
|
7
9
|
from lightning_sdk.cli.legacy import _LegacyLightningCLI
|
|
10
|
+
from lightning_sdk.cli.list import _List
|
|
8
11
|
from lightning_sdk.cli.run import _Run
|
|
9
12
|
from lightning_sdk.cli.serve import _Docker, _LitServe
|
|
13
|
+
from lightning_sdk.cli.stop import _Stop
|
|
10
14
|
from lightning_sdk.cli.upload import _Uploads
|
|
11
15
|
from lightning_sdk.lightning_cloud.login import Auth
|
|
12
16
|
|
|
@@ -23,6 +27,10 @@ class StudioCLI:
|
|
|
23
27
|
self.run = _Run(legacy_run=_LegacyLightningCLI() if _LIGHTNING_AVAILABLE else None)
|
|
24
28
|
self.serve = _LitServe()
|
|
25
29
|
self.dockerize = _Docker()
|
|
30
|
+
self.list = _List()
|
|
31
|
+
self.delete = _Delete()
|
|
32
|
+
self.inspect = _Inspect()
|
|
33
|
+
self.stop = _Stop()
|
|
26
34
|
|
|
27
35
|
def login(self) -> None:
|
|
28
36
|
"""Login to Lightning AI Studios."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _Inspect(_JobAndMMTAction):
|
|
7
|
+
"""Inspect resources of the Lightning AI platform to get additional details as JSON."""
|
|
8
|
+
|
|
9
|
+
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
10
|
+
"""Inspect a job for further details as JSON.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
14
|
+
teamspace: the name of the teamspace the job lives in.
|
|
15
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
16
|
+
If not specified can be selected interactively.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
print(super().job(name=name, teamspace=teamspace).json())
|
|
20
|
+
|
|
21
|
+
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
22
|
+
"""Inspect a multi-machine job for further details as JSON.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
26
|
+
teamspace: the name of the teamspace the job lives in.
|
|
27
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
28
|
+
If not specified can be selected interactively.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
print(super().mmt(name=name, teamspace=teamspace).json())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.cli.jobs_menu import _JobsMenu
|
|
4
|
+
from lightning_sdk.cli.mmts_menu import _MMTsMenu
|
|
5
|
+
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
6
|
+
from lightning_sdk.job import Job
|
|
7
|
+
from lightning_sdk.mmt import MMT
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _JobAndMMTAction(_TeamspacesMenu, _JobsMenu, _MMTsMenu):
|
|
11
|
+
"""Inspect resources of the Lightning AI platform to get additional details as JSON."""
|
|
12
|
+
|
|
13
|
+
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> Job:
|
|
14
|
+
"""Fetch a job for further processing.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
18
|
+
teamspace: the name of the teamspace the job lives in.
|
|
19
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
20
|
+
If not specified can be selected interactively.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
resolved_teamspace = self._resolve_teamspace(teamspace)
|
|
24
|
+
return self._resolve_job(name, teamspace=resolved_teamspace)
|
|
25
|
+
|
|
26
|
+
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> MMT:
|
|
27
|
+
"""Fetch a multi-machine job for further processing.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
31
|
+
teamspace: the name of the teamspace the job lives in.
|
|
32
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
33
|
+
If not specified can be selected interactively.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
resolved_teamspace = self._resolve_teamspace(teamspace)
|
|
37
|
+
return self._resolve_mmt(name, teamspace=resolved_teamspace)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from simple_term_menu import TerminalMenu
|
|
4
|
+
|
|
5
|
+
from lightning_sdk.cli.exceptions import StudioCliError
|
|
6
|
+
from lightning_sdk.job import Job
|
|
7
|
+
from lightning_sdk.teamspace import Teamspace
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _JobsMenu:
|
|
11
|
+
def _get_job_from_interactive_menu(self, possible_jobs: Dict[str, Job]) -> Job:
|
|
12
|
+
job_ids = sorted(possible_jobs.keys())
|
|
13
|
+
terminal_menu = self._prepare_terminal_menu_jobs([possible_jobs[k] for k in job_ids])
|
|
14
|
+
terminal_menu.show()
|
|
15
|
+
|
|
16
|
+
return possible_jobs[terminal_menu.chosen_menu_entry]
|
|
17
|
+
|
|
18
|
+
def _get_job_from_name(self, job: str, possible_jobs: Dict[str, Job]) -> Job:
|
|
19
|
+
for _, j in possible_jobs.items():
|
|
20
|
+
if j.name == job:
|
|
21
|
+
return j
|
|
22
|
+
|
|
23
|
+
print("Could not find Job {job}, please select it from the list:")
|
|
24
|
+
return self._get_job_from_interactive_menu(possible_jobs)
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _prepare_terminal_menu_jobs(possible_jobs: List[Job], title: Optional[str] = None) -> TerminalMenu:
|
|
28
|
+
if title is None:
|
|
29
|
+
title = "Please select a Job of the following:"
|
|
30
|
+
|
|
31
|
+
return TerminalMenu([j.name for j in possible_jobs], title=title, clear_menu_on_exit=True)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _get_possible_jobs(teamspace: Teamspace) -> Dict[str, Job]:
|
|
35
|
+
jobs = {}
|
|
36
|
+
for j in teamspace.jobs:
|
|
37
|
+
jobs[j.name] = j
|
|
38
|
+
|
|
39
|
+
return jobs
|
|
40
|
+
|
|
41
|
+
def _resolve_job(self, job: Optional[str], teamspace: Teamspace) -> Job:
|
|
42
|
+
try:
|
|
43
|
+
possible_jobs = self._get_possible_jobs(teamspace)
|
|
44
|
+
if job is None:
|
|
45
|
+
resolved_job = self._get_job_from_interactive_menu(possible_jobs)
|
|
46
|
+
else:
|
|
47
|
+
resolved_job = self._get_job_from_name(job=job, possible_jobs=possible_jobs)
|
|
48
|
+
|
|
49
|
+
return resolved_job
|
|
50
|
+
except KeyboardInterrupt:
|
|
51
|
+
raise KeyboardInterrupt from None
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise StudioCliError(
|
|
55
|
+
f"Could not find the given Job {job} in Teamspace {teamspace.name}. "
|
|
56
|
+
"Please contact Lightning AI directly to resolve this issue."
|
|
57
|
+
) from e
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
7
|
+
from lightning_sdk.lit_container import LitContainer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _List(_TeamspacesMenu):
|
|
11
|
+
"""List resources on the Lightning AI platform."""
|
|
12
|
+
|
|
13
|
+
def jobs(self, teamspace: Optional[str] = None) -> None:
|
|
14
|
+
"""List jobs for a given teamspace.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
teamspace: the teamspace to list jobs from. Should be specified as {owner}/{name}
|
|
18
|
+
If not provided, can be selected in an interactive menu.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
22
|
+
|
|
23
|
+
print("Available Jobs:\n" + "\n".join([j.name for j in resolved_teamspace.jobs]))
|
|
24
|
+
|
|
25
|
+
def mmts(self, teamspace: Optional[str] = None) -> None:
|
|
26
|
+
"""List multi-machine jobs for a given teamspace.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
teamspace: the teamspace to list jobs from. Should be specified as {owner}/{name}
|
|
30
|
+
If not provided, can be selected in an interactive menu.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
34
|
+
|
|
35
|
+
print("Available MMTs:\n" + "\n".join([j.name for j in resolved_teamspace.multi_machine_jobs]))
|
|
36
|
+
|
|
37
|
+
def containers(self, teamspace: Optional[str] = None) -> None:
|
|
38
|
+
"""Display the list of available containers.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
teamspace: The teamspace to list containers from. Should be specified as {owner}/{name}
|
|
42
|
+
If not provided, can be selected in an interactive menu.
|
|
43
|
+
"""
|
|
44
|
+
api = LitContainer()
|
|
45
|
+
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
46
|
+
result = api.list_containers(teamspace=resolved_teamspace.name, org=resolved_teamspace.owner.name)
|
|
47
|
+
table = Table(pad_edge=True, box=None)
|
|
48
|
+
table.add_column("REPOSITORY")
|
|
49
|
+
table.add_column("IMAGE ID")
|
|
50
|
+
table.add_column("CREATED")
|
|
51
|
+
for repo in result:
|
|
52
|
+
table.add_row(repo["REPOSITORY"], repo["IMAGE ID"], repo["CREATED"])
|
|
53
|
+
console = Console()
|
|
54
|
+
console.print(table)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from simple_term_menu import TerminalMenu
|
|
4
|
+
|
|
5
|
+
from lightning_sdk.cli.exceptions import StudioCliError
|
|
6
|
+
from lightning_sdk.mmt import MMT
|
|
7
|
+
from lightning_sdk.teamspace import Teamspace
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _MMTsMenu:
|
|
11
|
+
def _get_mmt_from_interactive_menu(self, possible_mmts: Dict[str, MMT]) -> MMT:
|
|
12
|
+
job_ids = sorted(possible_mmts.keys())
|
|
13
|
+
terminal_menu = self._prepare_terminal_menu_mmts([possible_mmts[k] for k in job_ids])
|
|
14
|
+
terminal_menu.show()
|
|
15
|
+
|
|
16
|
+
return possible_mmts[terminal_menu.chosen_menu_entry]
|
|
17
|
+
|
|
18
|
+
def _get_mmt_from_name(self, mmt: str, possible_mmts: Dict[str, MMT]) -> MMT:
|
|
19
|
+
for _, j in possible_mmts.items():
|
|
20
|
+
if j.name == mmt:
|
|
21
|
+
return j
|
|
22
|
+
|
|
23
|
+
print("Could not find Multi-Machine Job {mmt}, please select it from the list:")
|
|
24
|
+
return self._get_mmt_from_interactive_menu(possible_mmts)
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _prepare_terminal_menu_mmts(possible_mmts: List[MMT], title: Optional[str] = None) -> TerminalMenu:
|
|
28
|
+
if title is None:
|
|
29
|
+
title = "Please select a Multi-Machine Job of the following:"
|
|
30
|
+
|
|
31
|
+
return TerminalMenu([m.name for m in possible_mmts], title=title, clear_menu_on_exit=True)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _get_possible_mmts(teamspace: Teamspace) -> Dict[str, MMT]:
|
|
35
|
+
jobs = {}
|
|
36
|
+
for j in teamspace.multi_machine_jobs:
|
|
37
|
+
jobs[j.name] = j
|
|
38
|
+
|
|
39
|
+
return jobs
|
|
40
|
+
|
|
41
|
+
def _resolve_mmt(self, mmt: Optional[str], teamspace: Teamspace) -> MMT:
|
|
42
|
+
try:
|
|
43
|
+
possible_mmts = self._get_possible_mmts(teamspace)
|
|
44
|
+
if mmt is None:
|
|
45
|
+
resolved_mmt = self._get_mmt_from_interactive_menu(possible_mmts)
|
|
46
|
+
else:
|
|
47
|
+
resolved_mmt = self._get_mmt_from_name(mmt=mmt, possible_mmts=possible_mmts)
|
|
48
|
+
|
|
49
|
+
return resolved_mmt
|
|
50
|
+
except KeyboardInterrupt:
|
|
51
|
+
raise KeyboardInterrupt from None
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise StudioCliError(
|
|
55
|
+
f"Could not find the given Multi-Machine-Job {mmt} in Teamspace {teamspace.name}. "
|
|
56
|
+
"Please contact Lightning AI directly to resolve this issue."
|
|
57
|
+
) from e
|
lightning_sdk/cli/run.py
CHANGED
|
@@ -53,6 +53,10 @@ class _Run:
|
|
|
53
53
|
within it.
|
|
54
54
|
Note that the connection needs to be added to the teamspace already in order for it to be found.
|
|
55
55
|
Only supported for jobs with a docker image compute environment.
|
|
56
|
+
entrypoint: The entrypoint of your docker container. Defaults to `sh -c` which
|
|
57
|
+
just runs the provided command in a standard shell.
|
|
58
|
+
To use the pre-defined entrypoint of the provided image, set this to an empty string.
|
|
59
|
+
Only applicable when submitting docker jobs.
|
|
56
60
|
"""
|
|
57
61
|
# TODO: the docstrings from artifacts_local and artifacts_remote don't show up completely,
|
|
58
62
|
# might need to switch to explicit cli definition
|
|
@@ -93,6 +97,10 @@ class _Run:
|
|
|
93
97
|
within it.
|
|
94
98
|
Note that the connection needs to be added to the teamspace already in order for it to be found.
|
|
95
99
|
Only supported for jobs with a docker image compute environment.
|
|
100
|
+
entrypoint: The entrypoint of your docker container. Defaults to `sh -c` which
|
|
101
|
+
just runs the provided command in a standard shell.
|
|
102
|
+
To use the pre-defined entrypoint of the provided image, set this to an empty string.
|
|
103
|
+
Only applicable when submitting docker jobs.
|
|
96
104
|
"""
|
|
97
105
|
# TODO: the docstrings from artifacts_local and artifacts_remote don't show up completely,
|
|
98
106
|
# might need to switch to explicit cli definition
|
|
@@ -118,6 +126,7 @@ class _Run:
|
|
|
118
126
|
cloud_account_auth: bool = False,
|
|
119
127
|
artifacts_local: Optional[str] = None,
|
|
120
128
|
artifacts_remote: Optional[str] = None,
|
|
129
|
+
entrypoint: str = "sh -c",
|
|
121
130
|
) -> None:
|
|
122
131
|
if machine is None:
|
|
123
132
|
# TODO: infer from studio
|
|
@@ -145,6 +154,7 @@ class _Run:
|
|
|
145
154
|
cloud_account_auth=cloud_account_auth,
|
|
146
155
|
artifacts_local=artifacts_local,
|
|
147
156
|
artifacts_remote=artifacts_remote,
|
|
157
|
+
entrypoint=entrypoint,
|
|
148
158
|
)
|
|
149
159
|
|
|
150
160
|
# TODO: sadly, fire displays both Optional[type] and Union[type, None] as Optional[Optional]
|
|
@@ -167,6 +177,7 @@ class _Run:
|
|
|
167
177
|
cloud_account_auth: bool = False,
|
|
168
178
|
artifacts_local: Optional[str] = None,
|
|
169
179
|
artifacts_remote: Optional[str] = None,
|
|
180
|
+
entrypoint: str = "sh -c",
|
|
170
181
|
) -> None:
|
|
171
182
|
if name is None:
|
|
172
183
|
from datetime import datetime
|
|
@@ -203,4 +214,5 @@ class _Run:
|
|
|
203
214
|
cloud_account_auth=cloud_account_auth,
|
|
204
215
|
artifacts_local=artifacts_local,
|
|
205
216
|
artifacts_remote=artifacts_remote,
|
|
217
|
+
entrypoint=entrypoint,
|
|
206
218
|
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class _Stop(_JobAndMMTAction):
|
|
7
|
+
"""Stop resources on the Lightning AI platform."""
|
|
8
|
+
|
|
9
|
+
def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
10
|
+
"""Stop a job.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
14
|
+
teamspace: the name of the teamspace the job lives in.
|
|
15
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
16
|
+
If not specified can be selected interactively.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
job = super().job(name=name, teamspace=teamspace)
|
|
20
|
+
|
|
21
|
+
job.stop()
|
|
22
|
+
print(f"Successfully stopped {job.name}!")
|
|
23
|
+
|
|
24
|
+
def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
|
|
25
|
+
"""Stop a multi-machine job.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
name: the name of the job. If not specified can be selected interactively.
|
|
29
|
+
teamspace: the name of the teamspace the job lives in.
|
|
30
|
+
Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
|
|
31
|
+
If not specified can be selected interactively.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
mmt = super().mmt(name=name, teamspace=teamspace)
|
|
35
|
+
|
|
36
|
+
mmt.stop()
|
|
37
|
+
print(f"Successfully stopped {mmt.name}!")
|