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.
Files changed (40) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/job_api.py +12 -1
  3. lightning_sdk/api/lit_container_api.py +19 -0
  4. lightning_sdk/api/utils.py +1 -1
  5. lightning_sdk/cli/delete.py +58 -0
  6. lightning_sdk/cli/entrypoint.py +6 -0
  7. lightning_sdk/cli/inspect.py +31 -0
  8. lightning_sdk/cli/job_and_mmt_action.py +37 -0
  9. lightning_sdk/cli/jobs_menu.py +57 -0
  10. lightning_sdk/cli/list.py +2 -2
  11. lightning_sdk/cli/mmts_menu.py +57 -0
  12. lightning_sdk/cli/run.py +12 -0
  13. lightning_sdk/cli/stop.py +37 -0
  14. lightning_sdk/job/base.py +5 -1
  15. lightning_sdk/job/job.py +34 -9
  16. lightning_sdk/job/v2.py +11 -3
  17. lightning_sdk/lightning_cloud/openapi/__init__.py +3 -1
  18. lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +124 -23
  19. lightning_sdk/lightning_cloud/openapi/api/models_store_api.py +250 -8
  20. lightning_sdk/lightning_cloud/openapi/models/__init__.py +3 -1
  21. lightning_sdk/lightning_cloud/openapi/models/v1_aws_direct_v1.py +27 -1
  22. lightning_sdk/lightning_cloud/openapi/models/{v1_delete_container_response.py → v1_delete_lit_repository_response.py} +6 -6
  23. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +29 -1
  24. lightning_sdk/lightning_cloud/openapi/models/v1_model.py +27 -1
  25. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +1 -53
  26. lightning_sdk/lightning_cloud/openapi/models/v1_vultr_direct_v1.py +55 -3
  27. lightning_sdk/lightning_cloud/openapi/models/version_default_body.py +149 -0
  28. lightning_sdk/lightning_cloud/openapi/models/versions_version_body.py +123 -0
  29. lightning_sdk/lightning_cloud/utils/data_connection.py +1 -0
  30. lightning_sdk/{lit_registry.py → lit_container.py} +22 -4
  31. lightning_sdk/mmt/mmt.py +35 -14
  32. lightning_sdk/mmt/v1.py +5 -1
  33. lightning_sdk/mmt/v2.py +3 -2
  34. {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/METADATA +1 -1
  35. {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/RECORD +39 -31
  36. lightning_sdk/api/lit_registry_api.py +0 -12
  37. {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/LICENSE +0 -0
  38. {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/WHEEL +0 -0
  39. {lightning_sdk-0.1.47.dist-info → lightning_sdk-0.1.48.dist-info}/entry_points.txt +0 -0
  40. {lightning_sdk-0.1.47.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.47"
30
+ __version__ = "0.1.48"
31
31
  _check_version_and_prompt_upgrade(__version__)
@@ -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.v2_job_state_stopped)
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
@@ -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,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.lit_registry import LitRegistry
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 = LitRegistry()
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 f"https://lightning.ai/{self.teamspace.owner.name}/{self.teamspace.name}/studios/{studio_name}/app?app_id=jobs&job_name={self.name}"
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
- internal_job_cls = _JobV2 if _has_jobs_v2() and not self._force_v1 else _JobV1
54
-
55
- self._internal_job = internal_job_cls(
56
- name=name,
57
- teamspace=teamspace,
58
- org=org,
59
- user=user,
60
- _fetch_job=_fetch_job,
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
- return (
189
- f"https://lightning.ai/{self.teamspace.owner.name}/{self.teamspace.name}/jobs/{self.name}?app_id=jobs"
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