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.
Files changed (52) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/job_api.py +37 -6
  3. lightning_sdk/api/lit_container_api.py +19 -0
  4. lightning_sdk/api/mmt_api.py +18 -1
  5. lightning_sdk/api/teamspace_api.py +19 -1
  6. lightning_sdk/api/utils.py +1 -1
  7. lightning_sdk/cli/delete.py +58 -0
  8. lightning_sdk/cli/entrypoint.py +8 -0
  9. lightning_sdk/cli/inspect.py +31 -0
  10. lightning_sdk/cli/job_and_mmt_action.py +37 -0
  11. lightning_sdk/cli/jobs_menu.py +57 -0
  12. lightning_sdk/cli/list.py +54 -0
  13. lightning_sdk/cli/mmts_menu.py +57 -0
  14. lightning_sdk/cli/run.py +12 -0
  15. lightning_sdk/cli/stop.py +37 -0
  16. lightning_sdk/cli/teamspace_menu.py +94 -0
  17. lightning_sdk/job/base.py +70 -2
  18. lightning_sdk/job/job.py +59 -9
  19. lightning_sdk/job/v1.py +28 -0
  20. lightning_sdk/job/v2.py +40 -4
  21. lightning_sdk/job/work.py +10 -1
  22. lightning_sdk/lightning_cloud/openapi/__init__.py +3 -0
  23. lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +202 -0
  24. lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +250 -8
  25. lightning_sdk/lightning_cloud/openapi/models/__init__.py +3 -0
  26. lightning_sdk/lightning_cloud/openapi/models/v1_aws_direct_v1.py +27 -1
  27. lightning_sdk/lightning_cloud/openapi/models/v1_delete_lit_repository_response.py +97 -0
  28. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_api.py +27 -1
  29. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_state.py +1 -0
  30. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +29 -27
  31. lightning_sdk/lightning_cloud/openapi/models/v1_job.py +27 -1
  32. lightning_sdk/lightning_cloud/openapi/models/v1_model.py +27 -1
  33. lightning_sdk/lightning_cloud/openapi/models/v1_resource_visibility.py +29 -3
  34. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +27 -53
  35. lightning_sdk/lightning_cloud/openapi/models/v1_vultr_direct_v1.py +55 -3
  36. lightning_sdk/lightning_cloud/openapi/models/version_default_body.py +149 -0
  37. lightning_sdk/lightning_cloud/openapi/models/versions_version_body.py +123 -0
  38. lightning_sdk/lightning_cloud/utils/data_connection.py +1 -1
  39. lightning_sdk/lit_container.py +57 -0
  40. lightning_sdk/machine.py +4 -0
  41. lightning_sdk/mmt/base.py +39 -1
  42. lightning_sdk/mmt/mmt.py +59 -14
  43. lightning_sdk/mmt/v1.py +30 -2
  44. lightning_sdk/mmt/v2.py +31 -2
  45. lightning_sdk/status.py +11 -7
  46. lightning_sdk/teamspace.py +51 -1
  47. {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/METADATA +1 -1
  48. {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/RECORD +52 -39
  49. {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/LICENSE +0 -0
  50. {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/WHEEL +0 -0
  51. {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/entry_points.txt +0 -0
  52. {lightning_sdk-0.1.46.dist-info → lightning_sdk-0.1.48.dist-info}/top_level.txt +0 -0
lightning_sdk/__init__.py CHANGED
@@ -27,5 +27,5 @@ __all__ = [
27
27
  "AIHub",
28
28
  ]
29
29
 
30
- __version__ = "0.1.46"
30
+ __version__ = "0.1.48"
31
31
  _check_version_and_prompt_upgrade(__version__)
@@ -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.v2_job_state_stopped)
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
- cs: V1CloudSpace = self._client.cloud_space_service_get_cloud_space(
281
- project_id=job.project_id, id=job.spec.cloudspace_id
282
- )
283
- return cs.name
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
@@ -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="sh -c",
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
@@ -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:443"
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}!")
@@ -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}!")