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
@@ -0,0 +1,94 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from simple_term_menu import TerminalMenu
4
+
5
+ from lightning_sdk.api import OrgApi
6
+ from lightning_sdk.cli.exceptions import StudioCliError
7
+ from lightning_sdk.teamspace import Teamspace
8
+ from lightning_sdk.user import User
9
+ from lightning_sdk.utils.resolve import _get_authed_user
10
+
11
+
12
+ class _TeamspacesMenu:
13
+ def _get_teamspace_from_interactive_menu(self, possible_teamspaces: Dict[str, Dict[str, str]]) -> Dict[str, str]:
14
+ teamspace_ids = sorted(possible_teamspaces.keys())
15
+ terminal_menu = self._prepare_terminal_menu_teamspaces([possible_teamspaces[k] for k in teamspace_ids])
16
+ terminal_menu.show()
17
+
18
+ selected_id = teamspace_ids[terminal_menu.chosen_menu_index]
19
+ return possible_teamspaces[selected_id]
20
+
21
+ def _get_teamspace_from_name(
22
+ self, teamspace: str, possible_teamspaces: Dict[str, Dict[str, str]]
23
+ ) -> Dict[str, str]:
24
+ owner, name = teamspace.split("/", maxsplit=1)
25
+ for _, ts in possible_teamspaces.items():
26
+ if ts["name"] == name and (ts["user"] == owner or ts["org"] == owner):
27
+ return ts
28
+
29
+ print("Could not find Teamspace {teamspace}, please select it from the list:")
30
+ return self._get_teamspace_from_interactive_menu(possible_teamspaces)
31
+
32
+ @staticmethod
33
+ def _prepare_terminal_menu_teamspaces(
34
+ possible_teamspaces: List[Dict[str, str]], title: Optional[str] = None
35
+ ) -> TerminalMenu:
36
+ if title is None:
37
+ title = "Please select a Teamspace of the following:"
38
+
39
+ return TerminalMenu(
40
+ [f"{t['user'] or t['org']}/{t['name']}" for t in possible_teamspaces], title=title, clear_menu_on_exit=True
41
+ )
42
+
43
+ @staticmethod
44
+ def _get_possible_teamspaces(user: User, is_owner: bool = True) -> Dict[str, Dict[str, str]]:
45
+ org_api = OrgApi()
46
+ user_api = user._user_api
47
+
48
+ user_api._get_organizations_for_authed_user()
49
+ memberships = user_api._get_all_teamspace_memberships(user_id=user.id)
50
+
51
+ teamspaces = {}
52
+ # get all teamspace memberships
53
+ for membership in memberships:
54
+ teamspace_id = membership.project_id
55
+ teamspace_name = membership.name
56
+
57
+ # get organization if necessary
58
+ if membership.owner_type == "organization":
59
+ org_name = org_api._get_org_by_id(membership.owner_id).name
60
+ user_name = None
61
+ else:
62
+ org_name = None
63
+
64
+ # don't do a request if not necessary
65
+ if membership.owner_id == user.id:
66
+ user_name = user.name
67
+ else:
68
+ user_name = user_api._get_user_by_id(membership.owner_id).username
69
+
70
+ teamspaces[teamspace_id] = {"user": user_name, "org": org_name, "name": teamspace_name}
71
+
72
+ return teamspaces
73
+
74
+ def _resolve_teamspace(self, teamspace: Optional[str]) -> Teamspace:
75
+ try:
76
+ user = _get_authed_user()
77
+
78
+ possible_teamspaces = self._get_possible_teamspaces(user)
79
+ if teamspace is None:
80
+ teamspace_dict = self._get_teamspace_from_interactive_menu(possible_teamspaces=possible_teamspaces)
81
+ else:
82
+ teamspace_dict = self._get_teamspace_from_name(
83
+ teamspace=teamspace, possible_teamspaces=possible_teamspaces
84
+ )
85
+
86
+ return Teamspace(**teamspace_dict)
87
+ except KeyboardInterrupt:
88
+ raise KeyboardInterrupt from None
89
+
90
+ except Exception as e:
91
+ raise StudioCliError(
92
+ f"Could not find the given Teamspace {teamspace}. "
93
+ "Please contact Lightning AI directly to resolve this issue."
94
+ ) from e
lightning_sdk/job/base.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import TYPE_CHECKING, Any, Dict, Optional, Union
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:
@@ -12,6 +13,19 @@ if TYPE_CHECKING:
12
13
  from lightning_sdk.user import User
13
14
 
14
15
 
16
+ class MachineDict(TypedDict):
17
+ name: str
18
+ status: "Status"
19
+ machine: "Machine"
20
+
21
+
22
+ class JobDict(MachineDict):
23
+ command: str
24
+ teamspace: str
25
+ studio: Optional[str]
26
+ image: Optional[str]
27
+
28
+
15
29
  class _BaseJob(ABC):
16
30
  """Base interface to all job types."""
17
31
 
@@ -65,6 +79,7 @@ class _BaseJob(ABC):
65
79
  cloud_account_auth: bool = False,
66
80
  artifacts_local: Optional[str] = None,
67
81
  artifacts_remote: Optional[str] = None,
82
+ entrypoint: str = "sh -c",
68
83
  cluster: Optional[str] = None, # deprecated in favor of cloud_account
69
84
  ) -> "_BaseJob":
70
85
  """Run async workloads using a docker image or a compute environment from your studio.
@@ -98,6 +113,10 @@ class _BaseJob(ABC):
98
113
  within it.
99
114
  Note that the connection needs to be added to the teamspace already in order for it to be found.
100
115
  Only supported for jobs with a docker image compute environment.
116
+ entrypoint: The entrypoint of your docker container. Defaults to `sh -c` which
117
+ just runs the provided command in a standard shell.
118
+ To use the pre-defined entrypoint of the provided image, set this to an empty string.
119
+ Only applicable when submitting docker jobs.
101
120
  """
102
121
  from lightning_sdk.studio import Studio
103
122
 
@@ -145,6 +164,9 @@ class _BaseJob(ABC):
145
164
  "Other jobs will automatically persist artifacts to the teamspace distributed filesystem."
146
165
  )
147
166
 
167
+ if entrypoint != "sh -c":
168
+ raise ValueError("Specifying the entrypoint has no effect for jobs with Studio envs.")
169
+
148
170
  else:
149
171
  if studio is not None:
150
172
  raise RuntimeError(
@@ -174,6 +196,7 @@ class _BaseJob(ABC):
174
196
  cloud_account_auth=cloud_account_auth,
175
197
  artifacts_local=artifacts_local,
176
198
  artifacts_remote=artifacts_remote,
199
+ entrypoint=entrypoint,
177
200
  )
178
201
 
179
202
  @abstractmethod
@@ -190,6 +213,7 @@ class _BaseJob(ABC):
190
213
  cloud_account_auth: bool = False,
191
214
  artifacts_local: Optional[str] = None,
192
215
  artifacts_remote: Optional[str] = None,
216
+ entrypoint: str = "sh -c",
193
217
  ) -> "_BaseJob":
194
218
  """Submit a new job to the Lightning AI platform.
195
219
 
@@ -218,6 +242,9 @@ class _BaseJob(ABC):
218
242
  within it.
219
243
  Note that the connection needs to be added to the teamspace already in order for it to be found.
220
244
  Only supported for jobs with a docker image compute environment.
245
+ entrypoint: The entrypoint of your docker container. Defaults to sh -c.
246
+ To use the pre-defined entrypoint of the provided image, set this to an empty string.
247
+ Only applicable when submitting docker jobs.
221
248
  """
222
249
 
223
250
  @abstractmethod
@@ -278,10 +305,51 @@ class _BaseJob(ABC):
278
305
  def logs(self) -> str:
279
306
  """The logs of the job."""
280
307
 
308
+ @property
309
+ @abstractmethod
310
+ def image(self) -> Optional[str]:
311
+ """The image used to submit the job."""
312
+
313
+ @property
314
+ @abstractmethod
315
+ def studio(self) -> Optional["Studio"]:
316
+ """The studio used to submit the job."""
317
+
318
+ @property
319
+ @abstractmethod
320
+ def command(self) -> str:
321
+ """The command the job is running."""
322
+
323
+ def dict(self) -> JobDict:
324
+ """Dict representation of this job."""
325
+ studio = self.studio
326
+
327
+ return {
328
+ "name": self.name,
329
+ "teamspace": f"{self.teamspace.owner.name}/{self.teamspace.name}",
330
+ "studio": studio.name if studio else None,
331
+ "image": self.image,
332
+ "command": self.command,
333
+ "status": self.status,
334
+ "machine": self.machine,
335
+ }
336
+
337
+ def json(self) -> str:
338
+ """JSON representation of this job."""
339
+ import json
340
+
341
+ return json.dumps(self.dict(), indent=4, sort_keys=True, default=str)
342
+
281
343
  @property
282
344
  def link(self) -> str:
283
345
  """A link to view the current job in the UI."""
284
- return f"https://lightning.ai/{self.teamspace.owner.name}/{self.teamspace.name}/studios/{self._job_api.get_studio_name(self._guaranteed_job)}/app?app_id=jobs&job_name={self.name}"
346
+ studio_name = self._job_api.get_studio_name(self._guaranteed_job)
347
+ if not studio_name:
348
+ raise RuntimeError("Cannot extract studio name from job")
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
+ )
285
353
 
286
354
  @property
287
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(
@@ -78,6 +103,7 @@ class Job(_BaseJob):
78
103
  cloud_account_auth: bool = False,
79
104
  artifacts_local: Optional[str] = None,
80
105
  artifacts_remote: Optional[str] = None,
106
+ entrypoint: str = "sh -c",
81
107
  cluster: Optional[str] = None, # deprecated in favor of cloud_account
82
108
  ) -> "Job":
83
109
  """Run async workloads using a docker image or a compute environment from your studio.
@@ -111,6 +137,10 @@ class Job(_BaseJob):
111
137
  within it.
112
138
  Note that the connection needs to be added to the teamspace already in order for it to be found.
113
139
  Only supported for jobs with a docker image compute environment.
140
+ entrypoint: The entrypoint of your docker container. Defaults to `sh -c` which
141
+ just runs the provided command in a standard shell.
142
+ To use the pre-defined entrypoint of the provided image, set this to an empty string.
143
+ Only applicable when submitting docker jobs.
114
144
  """
115
145
  ret_val = super().run(
116
146
  name=name,
@@ -128,6 +158,7 @@ class Job(_BaseJob):
128
158
  cloud_account_auth=cloud_account_auth,
129
159
  artifacts_local=artifacts_local,
130
160
  artifacts_remote=artifacts_remote,
161
+ entrypoint=entrypoint,
131
162
  cluster=cluster,
132
163
  )
133
164
  # required for typing with "Job"
@@ -149,6 +180,7 @@ class Job(_BaseJob):
149
180
  cloud_account_auth: bool = False,
150
181
  artifacts_local: Optional[str] = None,
151
182
  artifacts_remote: Optional[str] = None,
183
+ entrypoint: str = "sh -c",
152
184
  ) -> "Job":
153
185
  """Submit a new job to the Lightning AI platform.
154
186
 
@@ -177,6 +209,9 @@ class Job(_BaseJob):
177
209
  within it.
178
210
  Note that the connection needs to be added to the teamspace already in order for it to be found.
179
211
  Only supported for jobs with a docker image compute environment.
212
+ entrypoint: The entrypoint of your docker container. Defaults to sh -c.
213
+ To use the pre-defined entrypoint of the provided image, set this to an empty string.
214
+ Only applicable when submitting docker jobs.
180
215
  """
181
216
  self._job = self._internal_job._submit(
182
217
  machine=machine,
@@ -245,6 +280,21 @@ class Job(_BaseJob):
245
280
  """The teamspace the job is part of."""
246
281
  return self._internal_job._teamspace
247
282
 
283
+ @property
284
+ def studio(self) -> Optional["Studio"]:
285
+ """The studio used to submit the job."""
286
+ return self._internal_job.studio
287
+
288
+ @property
289
+ def image(self) -> Optional[str]:
290
+ """The image used to submit the job."""
291
+ return self._internal_job.image
292
+
293
+ @property
294
+ def command(self) -> str:
295
+ """The command the job is running."""
296
+ return self._internal_job.command
297
+
248
298
  @property
249
299
  def logs(self) -> str:
250
300
  """The logs of the job."""
lightning_sdk/job/v1.py CHANGED
@@ -100,6 +100,7 @@ class _JobV1(_BaseJob):
100
100
  cloud_account_auth: bool = False,
101
101
  artifacts_local: Optional[str] = None,
102
102
  artifacts_remote: Optional[str] = None,
103
+ entrypoint: str = "sh -c",
103
104
  ) -> "_JobV1":
104
105
  """Submit a job to run on a machine.
105
106
 
@@ -115,6 +116,10 @@ class _JobV1(_BaseJob):
115
116
  cloud_account_auth: Whether to use cloud account authentication for the job (not supported).
116
117
  artifacts_local: The local path for persisting artifacts (not supported).
117
118
  artifacts_remote: The remote path for persisting artifacts (not supported).
119
+ entrypoint: The entrypoint of your docker container (not supported).
120
+ Defaults to `sh -c` which just runs the provided command in a standard shell.
121
+ To use the pre-defined entrypoint of the provided image, set this to an empty string.
122
+ Only applicable when submitting docker jobs.
118
123
 
119
124
  Returns:
120
125
  The submitted job.
@@ -132,6 +137,10 @@ class _JobV1(_BaseJob):
132
137
  raise ValueError("Environment variables are not supported for submitting jobs")
133
138
  if command is None:
134
139
  raise ValueError("Command is required for submitting jobs")
140
+
141
+ if entrypoint != "sh -c":
142
+ raise ValueError("Specifying the entrypoint is not yet supported with jobs")
143
+
135
144
  # TODO: add support for empty names (will give an empty string)
136
145
  _submitted = self._job_api.submit_job(
137
146
  name=self._name,
@@ -215,6 +224,25 @@ class _JobV1(_BaseJob):
215
224
  """The logs of the job."""
216
225
  return self.work.logs
217
226
 
227
+ @property
228
+ def image(self) -> Optional[str]:
229
+ """The image used to submit the job."""
230
+ # jobsv1 don't support images, so return None here
231
+ return None
232
+
233
+ @property
234
+ def studio(self) -> Optional["Studio"]:
235
+ """The studio used to submit the job."""
236
+ from lightning_sdk.studio import Studio
237
+
238
+ studio_name = self._job_api.get_studio_name(self._guaranteed_job)
239
+ return Studio(studio_name, teamspace=self.teamspace)
240
+
241
+ @property
242
+ def command(self) -> str:
243
+ """The command the job is running."""
244
+ return self._job_api.get_command(self._guaranteed_job)
245
+
218
246
  # the following and functions are solely to make the Work class function
219
247
  @property
220
248
  def _id(self) -> str:
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:
@@ -47,6 +48,7 @@ class _JobV2(_BaseJob):
47
48
  cloud_account_auth: bool = False,
48
49
  artifacts_local: Optional[str] = None,
49
50
  artifacts_remote: Optional[str] = None,
51
+ entrypoint: str = "sh -c",
50
52
  ) -> "_JobV2":
51
53
  """Submit a new job to the Lightning AI platform.
52
54
 
@@ -75,6 +77,10 @@ class _JobV2(_BaseJob):
75
77
  within it.
76
78
  Note that the connection needs to be added to the teamspace already in order for it to be found.
77
79
  Only supported for jobs with a docker image compute environment.
80
+ entrypoint: The entrypoint of your docker container. Defaults to `sh -c` which
81
+ just runs the provided command in a standard shell.
82
+ To use the pre-defined entrypoint of the provided image, set this to an empty string.
83
+ Only applicable when submitting docker jobs.
78
84
  """
79
85
  # Command is required if Studio is provided to know what to run
80
86
  # Image is mutually exclusive with Studio
@@ -107,6 +113,7 @@ class _JobV2(_BaseJob):
107
113
  cloud_account_auth=cloud_account_auth,
108
114
  artifacts_local=artifacts_local,
109
115
  artifacts_remote=artifacts_remote,
116
+ entrypoint=entrypoint,
110
117
  )
111
118
  self._job = submitted
112
119
  self._name = submitted.name
@@ -178,13 +185,42 @@ class _JobV2(_BaseJob):
178
185
 
179
186
  @property
180
187
  def link(self) -> str:
181
- if self._guaranteed_job.spec.image:
182
- return (
183
- f"https://lightning.ai/{self.teamspace.owner.name}/{self.teamspace.name}/jobs/{self.name}?app_id=jobs"
184
- )
188
+ mmt_name = self._job_api.get_mmt_name(self._guaranteed_job)
189
+
190
+ if self._job_api.get_image_name(self._guaranteed_job):
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"
185
198
 
199
+ # TODO: MMT env with studio
186
200
  return super().link
187
201
 
202
+ @property
203
+ def image(self) -> Optional[str]:
204
+ """The image used to submit the job."""
205
+ return self._job_api.get_image_name(self._guaranteed_job)
206
+
207
+ @property
208
+ def studio(self) -> Optional["Studio"]:
209
+ """The studio used to submit the job."""
210
+ from lightning_sdk.studio import Studio
211
+
212
+ studio_name = self._job_api.get_studio_name(self._guaranteed_job)
213
+
214
+ # if job was submitted with image, studio will be None
215
+ if not studio_name:
216
+ return None
217
+ return Studio(studio_name, teamspace=self.teamspace)
218
+
219
+ @property
220
+ def command(self) -> str:
221
+ """The command the job is running."""
222
+ return self._job_api.get_command(self._guaranteed_job)
223
+
188
224
  def _update_internal_job(self) -> None:
189
225
  if getattr(self, "_job", None) is None:
190
226
  self._job = self._job_api.get_job_by_name(name=self._name, teamspace_id=self._teamspace.id)
lightning_sdk/job/work.py CHANGED
@@ -3,9 +3,10 @@ from typing import TYPE_CHECKING, Any, Optional, Protocol
3
3
  from lightning_sdk.api.job_api import JobApiV1
4
4
 
5
5
  if TYPE_CHECKING:
6
+ from lightning_sdk.job.base import MachineDict
7
+ from lightning_sdk.machine import Machine
6
8
  from lightning_sdk.status import Status
7
9
  from lightning_sdk.teamspace import Teamspace
8
- from lightning_sdk.machine import Machine
9
10
 
10
11
 
11
12
  class _WorkHolder(Protocol):
@@ -70,3 +71,11 @@ class Work:
70
71
  raise RuntimeError("Getting jobs logs while the job is pending or running is not supported yet!")
71
72
 
72
73
  return self._job_api.get_logs_finished(job_id=self._job._id, work_id=self._id, teamspace_id=self._teamspace.id)
74
+
75
+ def dict(self) -> "MachineDict":
76
+ """Dict representation of the work."""
77
+ return {
78
+ "name": self.name,
79
+ "status": self.status,
80
+ "machine": self.machine,
81
+ }
@@ -341,6 +341,7 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningapp_instanc
341
341
  from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningapp_instance_response import V1DeleteLightningappInstanceResponse
342
342
  from lightning_sdk.lightning_cloud.openapi.models.v1_delete_lightningwork_response import V1DeleteLightningworkResponse
343
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
344
345
  from lightning_sdk.lightning_cloud.openapi.models.v1_delete_logger_artifact_response import V1DeleteLoggerArtifactResponse
345
346
  from lightning_sdk.lightning_cloud.openapi.models.v1_delete_managed_endpoint_response import V1DeleteManagedEndpointResponse
346
347
  from lightning_sdk.lightning_cloud.openapi.models.v1_delete_metrics_stream_response import V1DeleteMetricsStreamResponse
@@ -793,8 +794,10 @@ from lightning_sdk.lightning_cloud.openapi.models.v1_vultr_direct_v1 import V1Vu
793
794
  from lightning_sdk.lightning_cloud.openapi.models.v1_work import V1Work
794
795
  from lightning_sdk.lightning_cloud.openapi.models.validate import Validate
795
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
796
798
  from lightning_sdk.lightning_cloud.openapi.models.version_uploads_body import VersionUploadsBody
797
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
798
801
  from lightning_sdk.lightning_cloud.openapi.models.works_id_body import WorksIdBody
799
802
  from lightning_sdk.lightning_cloud.openapi.models.v1_image_spec import V1ImageSpec as Gridv1ImageSpec
800
803
  from lightning_sdk.lightning_cloud.openapi.models.clusters_id_body import ClustersIdBody as Body