lightning-sdk 0.1.47__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 +12 -1
- lightning_sdk/api/lit_container_api.py +19 -0
- lightning_sdk/api/utils.py +1 -1
- lightning_sdk/cli/delete.py +58 -0
- lightning_sdk/cli/entrypoint.py +6 -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 +2 -2
- lightning_sdk/cli/mmts_menu.py +57 -0
- lightning_sdk/cli/run.py +12 -0
- lightning_sdk/cli/stop.py +37 -0
- lightning_sdk/job/base.py +5 -1
- lightning_sdk/job/job.py +34 -9
- lightning_sdk/job/v2.py +11 -3
- lightning_sdk/lightning_cloud/openapi/__init__.py +3 -1
- lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +124 -23
- lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +250 -8
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +3 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_aws_direct_v1.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/{v1_delete_container_response.py → v1_delete_lit_repository_response.py} +6 -6
- lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +29 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_model.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +1 -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 -0
- lightning_sdk/{lit_registry.py → lit_container.py} +22 -4
- lightning_sdk/mmt/mmt.py +35 -14
- lightning_sdk/mmt/v1.py +5 -1
- lightning_sdk/mmt/v2.py +3 -2
- {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/METADATA +1 -1
- {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/RECORD +39 -31
- lightning_sdk/api/lit_registry_api.py +0 -12
- {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.1.47.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
|
@@ -182,6 +182,7 @@ class JobApiV1:
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
class JobApiV2:
|
|
185
|
+
# these are stages the job can be in.
|
|
185
186
|
v2_job_state_pending = "pending"
|
|
186
187
|
v2_job_state_running = "running"
|
|
187
188
|
v2_job_state_stopped = "stopped"
|
|
@@ -189,6 +190,9 @@ class JobApiV2:
|
|
|
189
190
|
v2_job_state_failed = "failed"
|
|
190
191
|
v2_job_state_stopping = "stopping"
|
|
191
192
|
|
|
193
|
+
# this is the user action to stop the job.
|
|
194
|
+
v2_job_state_stop = "stop"
|
|
195
|
+
|
|
192
196
|
def __init__(self) -> None:
|
|
193
197
|
self._cloud_url = _cloud_url()
|
|
194
198
|
self._client = LightningClient(max_tries=7)
|
|
@@ -262,7 +266,7 @@ class JobApiV2:
|
|
|
262
266
|
return
|
|
263
267
|
|
|
264
268
|
if current_state != Status.Stopping:
|
|
265
|
-
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)
|
|
266
270
|
self._client.jobs_service_update_job(body=update_body, project_id=teamspace_id, id=job_id)
|
|
267
271
|
|
|
268
272
|
while True:
|
|
@@ -302,6 +306,13 @@ class JobApiV2:
|
|
|
302
306
|
def get_command(self, job: V1Job) -> str:
|
|
303
307
|
return job.spec.command
|
|
304
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 ""
|
|
315
|
+
|
|
305
316
|
def _job_state_to_external(self, state: str) -> "Status":
|
|
306
317
|
from lightning_sdk.status import Status
|
|
307
318
|
|
|
@@ -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/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,11 +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
|
|
8
10
|
from lightning_sdk.cli.list import _List
|
|
9
11
|
from lightning_sdk.cli.run import _Run
|
|
10
12
|
from lightning_sdk.cli.serve import _Docker, _LitServe
|
|
13
|
+
from lightning_sdk.cli.stop import _Stop
|
|
11
14
|
from lightning_sdk.cli.upload import _Uploads
|
|
12
15
|
from lightning_sdk.lightning_cloud.login import Auth
|
|
13
16
|
|
|
@@ -25,6 +28,9 @@ class StudioCLI:
|
|
|
25
28
|
self.serve = _LitServe()
|
|
26
29
|
self.dockerize = _Docker()
|
|
27
30
|
self.list = _List()
|
|
31
|
+
self.delete = _Delete()
|
|
32
|
+
self.inspect = _Inspect()
|
|
33
|
+
self.stop = _Stop()
|
|
28
34
|
|
|
29
35
|
def login(self) -> None:
|
|
30
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
|
lightning_sdk/cli/list.py
CHANGED
|
@@ -4,7 +4,7 @@ from rich.console import Console
|
|
|
4
4
|
from rich.table import Table
|
|
5
5
|
|
|
6
6
|
from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
|
|
7
|
-
from lightning_sdk.
|
|
7
|
+
from lightning_sdk.lit_container import LitContainer
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class _List(_TeamspacesMenu):
|
|
@@ -41,7 +41,7 @@ class _List(_TeamspacesMenu):
|
|
|
41
41
|
teamspace: The teamspace to list containers from. Should be specified as {owner}/{name}
|
|
42
42
|
If not provided, can be selected in an interactive menu.
|
|
43
43
|
"""
|
|
44
|
-
api =
|
|
44
|
+
api = LitContainer()
|
|
45
45
|
resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
|
|
46
46
|
result = api.list_containers(teamspace=resolved_teamspace.name, org=resolved_teamspace.owner.name)
|
|
47
47
|
table = Table(pad_edge=True, box=None)
|
|
@@ -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}!")
|
lightning_sdk/job/base.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import TYPE_CHECKING, Any, Dict, Optional, TypedDict, Union
|
|
3
3
|
|
|
4
|
+
from lightning_sdk.api.utils import _get_cloud_url
|
|
4
5
|
from lightning_sdk.utils.resolve import _resolve_deprecated_cluster, _resolve_teamspace
|
|
5
6
|
|
|
6
7
|
if TYPE_CHECKING:
|
|
@@ -345,7 +346,10 @@ class _BaseJob(ABC):
|
|
|
345
346
|
studio_name = self._job_api.get_studio_name(self._guaranteed_job)
|
|
346
347
|
if not studio_name:
|
|
347
348
|
raise RuntimeError("Cannot extract studio name from job")
|
|
348
|
-
return
|
|
349
|
+
return (
|
|
350
|
+
f"{_get_cloud_url()}/{self.teamspace.owner.name}/{self.teamspace.name}/studios/"
|
|
351
|
+
f"{studio_name}/app?app_id=jobs&job_name={self.name}"
|
|
352
|
+
)
|
|
349
353
|
|
|
350
354
|
@property
|
|
351
355
|
def _guaranteed_job(self) -> Any:
|
lightning_sdk/job/job.py
CHANGED
|
@@ -50,15 +50,40 @@ class Job(_BaseJob):
|
|
|
50
50
|
user: the name of the user owning the :param`teamspace`
|
|
51
51
|
in case it is owned directly by a user instead of an org.
|
|
52
52
|
"""
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
self.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
54
|
+
|
|
55
|
+
if _has_jobs_v2() and not self._force_v1:
|
|
56
|
+
# try with v2 and fall back to v1
|
|
57
|
+
try:
|
|
58
|
+
job = _JobV2(
|
|
59
|
+
name=name,
|
|
60
|
+
teamspace=teamspace,
|
|
61
|
+
org=org,
|
|
62
|
+
user=user,
|
|
63
|
+
_fetch_job=_fetch_job,
|
|
64
|
+
)
|
|
65
|
+
except ApiException as e:
|
|
66
|
+
try:
|
|
67
|
+
job = _JobV1(
|
|
68
|
+
name=name,
|
|
69
|
+
teamspace=teamspace,
|
|
70
|
+
org=org,
|
|
71
|
+
user=user,
|
|
72
|
+
_fetch_job=_fetch_job,
|
|
73
|
+
)
|
|
74
|
+
except ApiException:
|
|
75
|
+
raise e from e
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
job = _JobV1(
|
|
79
|
+
name=name,
|
|
80
|
+
teamspace=teamspace,
|
|
81
|
+
org=org,
|
|
82
|
+
user=user,
|
|
83
|
+
_fetch_job=_fetch_job,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
self._internal_job = job
|
|
62
87
|
|
|
63
88
|
@classmethod
|
|
64
89
|
def run(
|
lightning_sdk/job/v2.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
|
2
2
|
|
|
3
3
|
from lightning_sdk.api.job_api import JobApiV2
|
|
4
|
+
from lightning_sdk.api.utils import _get_cloud_url
|
|
4
5
|
from lightning_sdk.job.base import _BaseJob
|
|
5
6
|
|
|
6
7
|
if TYPE_CHECKING:
|
|
@@ -184,11 +185,18 @@ class _JobV2(_BaseJob):
|
|
|
184
185
|
|
|
185
186
|
@property
|
|
186
187
|
def link(self) -> str:
|
|
188
|
+
mmt_name = self._job_api.get_mmt_name(self._guaranteed_job)
|
|
189
|
+
|
|
187
190
|
if self._job_api.get_image_name(self._guaranteed_job):
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
if mmt_name:
|
|
192
|
+
# don't go via the studio unless we use studio env
|
|
193
|
+
return (
|
|
194
|
+
f"{_get_cloud_url()}/{self.teamspace.owner.name}/{self.teamspace.name}/"
|
|
195
|
+
f"jobs/{mmt_name}?app_id=mmt&machine_name={self.name}"
|
|
196
|
+
)
|
|
197
|
+
return f"{_get_cloud_url()}/{self.teamspace.owner.name}/{self.teamspace.name}/jobs/{self.name}?app_id=jobs"
|
|
191
198
|
|
|
199
|
+
# TODO: MMT env with studio
|
|
192
200
|
return super().link
|
|
193
201
|
|
|
194
202
|
@property
|
|
@@ -327,7 +327,6 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_delete_cluster_capacity_res
|
|
|
327
327
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_cluster_encryption_key_response import V1DeleteClusterEncryptionKeyResponse
|
|
328
328
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_cluster_proxy_response import V1DeleteClusterProxyResponse
|
|
329
329
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_cluster_response import V1DeleteClusterResponse
|
|
330
|
-
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_container_response import V1DeleteContainerResponse
|
|
331
330
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_conversation_response import V1DeleteConversationResponse
|
|
332
331
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_data_connection_response import V1DeleteDataConnectionResponse
|
|
333
332
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_dataset_response import V1DeleteDatasetResponse
|
|
@@ -342,6 +341,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningapp_instanc
|
|
|
342
341
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningapp_instance_response import V1DeleteLightningappInstanceResponse
|
|
343
342
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningwork_response import V1DeleteLightningworkResponse
|
|
344
343
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_page_response import V1DeleteLitPageResponse
|
|
344
|
+
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lit_repository_response import V1DeleteLitRepositoryResponse
|
|
345
345
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_logger_artifact_response import V1DeleteLoggerArtifactResponse
|
|
346
346
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_managed_endpoint_response import V1DeleteManagedEndpointResponse
|
|
347
347
|
from lightning_sdk.lightning_cloud.openapi.models.v1_delete_metrics_stream_response import V1DeleteMetricsStreamResponse
|
|
@@ -794,8 +794,10 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_vultr_direct_v1 import V1Vu
|
|
|
794
794
|
from lightning_sdk.lightning_cloud.openapi.models.v1_work import V1Work
|
|
795
795
|
from lightning_sdk.lightning_cloud.openapi.models.validate import Validate
|
|
796
796
|
from lightning_sdk.lightning_cloud.openapi.models.validateautojoindomain_domain_body import ValidateautojoindomainDomainBody
|
|
797
|
+
from lightning_sdk.lightning_cloud.openapi.models.version_default_body import VersionDefaultBody
|
|
797
798
|
from lightning_sdk.lightning_cloud.openapi.models.version_uploads_body import VersionUploadsBody
|
|
798
799
|
from lightning_sdk.lightning_cloud.openapi.models.versions_id_body import VersionsIdBody
|
|
800
|
+
from lightning_sdk.lightning_cloud.openapi.models.versions_version_body import VersionsVersionBody
|
|
799
801
|
from lightning_sdk.lightning_cloud.openapi.models.works_id_body import WorksIdBody
|
|
800
802
|
from lightning_sdk.lightning_cloud.openapi.models.v1_image_spec import V1ImageSpec as Gridv1ImageSpec
|
|
801
803
|
from lightning_sdk.lightning_cloud.openapi.models.clusters_id_body import ClustersIdBody as Body
|