lightning-sdk 0.1.57__py3-none-any.whl → 0.2.0__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 (118) hide show
  1. lightning_sdk/__init__.py +5 -3
  2. lightning_sdk/api/deployment_api.py +23 -11
  3. lightning_sdk/api/job_api.py +42 -7
  4. lightning_sdk/api/lit_container_api.py +88 -22
  5. lightning_sdk/api/mmt_api.py +46 -8
  6. lightning_sdk/api/pipeline_api.py +50 -0
  7. lightning_sdk/api/teamspace_api.py +2 -2
  8. lightning_sdk/api/utils.py +15 -5
  9. lightning_sdk/cli/ai_hub.py +30 -65
  10. lightning_sdk/cli/coloring.py +60 -0
  11. lightning_sdk/cli/configure.py +25 -40
  12. lightning_sdk/cli/connect.py +7 -20
  13. lightning_sdk/cli/create.py +83 -0
  14. lightning_sdk/cli/delete.py +72 -75
  15. lightning_sdk/cli/docker.py +77 -0
  16. lightning_sdk/cli/download.py +71 -111
  17. lightning_sdk/cli/entrypoint.py +44 -65
  18. lightning_sdk/cli/generate.py +28 -43
  19. lightning_sdk/cli/inspect.py +22 -50
  20. lightning_sdk/cli/list.py +281 -222
  21. lightning_sdk/cli/mmts_menu.py +1 -1
  22. lightning_sdk/cli/open.py +62 -0
  23. lightning_sdk/cli/run.py +430 -263
  24. lightning_sdk/cli/serve.py +162 -189
  25. lightning_sdk/cli/start.py +55 -36
  26. lightning_sdk/cli/stop.py +97 -55
  27. lightning_sdk/cli/switch.py +53 -36
  28. lightning_sdk/cli/upload.py +318 -245
  29. lightning_sdk/deployment/__init__.py +2 -0
  30. lightning_sdk/deployment/deployment.py +33 -8
  31. lightning_sdk/lightning_cloud/openapi/__init__.py +21 -0
  32. lightning_sdk/lightning_cloud/openapi/api/__init__.py +1 -0
  33. lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +10 -6
  34. lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +355 -4
  35. lightning_sdk/lightning_cloud/openapi/api/lit_logger_service_api.py +4 -4
  36. lightning_sdk/lightning_cloud/openapi/api/lit_registry_service_api.py +14 -2
  37. lightning_sdk/lightning_cloud/openapi/api/pipelines_service_api.py +670 -0
  38. lightning_sdk/lightning_cloud/openapi/api/storage_service_api.py +303 -4
  39. lightning_sdk/lightning_cloud/openapi/models/__init__.py +20 -0
  40. lightning_sdk/lightning_cloud/openapi/models/agents_id_body.py +17 -69
  41. lightning_sdk/lightning_cloud/openapi/models/cluster_id_capacityreservations_body.py +27 -1
  42. lightning_sdk/lightning_cloud/openapi/models/create.py +27 -1
  43. lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +53 -1
  44. lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +105 -1
  45. lightning_sdk/lightning_cloud/openapi/models/id_visibility_body1.py +1 -27
  46. lightning_sdk/lightning_cloud/openapi/models/id_visibility_body2.py +149 -0
  47. lightning_sdk/lightning_cloud/openapi/models/org_id_memberships_body.py +27 -1
  48. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +157 -1
  49. lightning_sdk/lightning_cloud/openapi/models/pipelines_id_body.py +435 -0
  50. lightning_sdk/lightning_cloud/openapi/models/project_id_pipelines_body.py +201 -0
  51. lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +157 -1
  52. lightning_sdk/lightning_cloud/openapi/models/slurm_jobs_body.py +79 -1
  53. lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body.py +1 -27
  54. lightning_sdk/lightning_cloud/openapi/models/uploads_upload_id_body1.py +175 -0
  55. lightning_sdk/lightning_cloud/openapi/models/v1_agent_job.py +79 -1
  56. lightning_sdk/lightning_cloud/openapi/models/v1_assistant.py +17 -69
  57. lightning_sdk/lightning_cloud/openapi/models/v1_capacity_block_offering.py +27 -1
  58. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_artifact_event_type.py +1 -1
  59. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_accelerator.py +131 -1
  60. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_capacity_reservation.py +79 -1
  61. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
  62. lightning_sdk/lightning_cloud/openapi/models/v1_complete_upload_temporary_artifact_request.py +175 -0
  63. lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +461 -0
  64. lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_template_request.py +27 -1
  65. lightning_sdk/lightning_cloud/openapi/models/v1_create_job_request.py +201 -0
  66. lightning_sdk/lightning_cloud/openapi/models/v1_create_managed_endpoint_response.py +149 -0
  67. lightning_sdk/lightning_cloud/openapi/models/v1_create_multi_machine_job_request.py +253 -0
  68. lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
  69. lightning_sdk/lightning_cloud/openapi/models/v1_delete_pipeline_response.py +149 -0
  70. lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +105 -1
  71. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_details.py +175 -0
  72. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_template.py +53 -1
  73. lightning_sdk/lightning_cloud/openapi/models/v1_filestore_data_connection.py +201 -0
  74. lightning_sdk/lightning_cloud/openapi/models/v1_filesystem_job.py +27 -1
  75. lightning_sdk/lightning_cloud/openapi/models/v1_filesystem_mmt.py +27 -1
  76. lightning_sdk/lightning_cloud/openapi/models/v1_find_capacity_block_offering_response.py +29 -3
  77. lightning_sdk/lightning_cloud/openapi/models/v1_job.py +133 -3
  78. lightning_sdk/lightning_cloud/openapi/models/v1_job_spec.py +53 -1
  79. lightning_sdk/lightning_cloud/openapi/models/v1_job_timing.py +27 -1
  80. lightning_sdk/lightning_cloud/openapi/models/v1_list_pipelines_response.py +123 -0
  81. lightning_sdk/lightning_cloud/openapi/models/v1_lit_registry_artifact.py +27 -1
  82. lightning_sdk/lightning_cloud/openapi/models/v1_lit_repository.py +29 -1
  83. lightning_sdk/lightning_cloud/openapi/models/v1_managed_model.py +27 -1
  84. lightning_sdk/lightning_cloud/openapi/models/v1_multi_machine_job.py +27 -1
  85. lightning_sdk/lightning_cloud/openapi/models/v1_multi_machine_job_state.py +2 -0
  86. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +157 -1
  87. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline.py +487 -0
  88. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_step.py +253 -0
  89. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_step_status.py +331 -0
  90. lightning_sdk/lightning_cloud/openapi/models/v1_pipeline_step_type.py +104 -0
  91. lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +157 -1
  92. lightning_sdk/lightning_cloud/openapi/models/v1_restart_timing.py +27 -1
  93. lightning_sdk/lightning_cloud/openapi/models/v1_rule_resource.py +1 -0
  94. lightning_sdk/lightning_cloud/openapi/models/v1_shared_filesystem.py +201 -0
  95. lightning_sdk/lightning_cloud/openapi/models/v1_slurm_job.py +27 -1
  96. lightning_sdk/lightning_cloud/openapi/models/v1_update_job_visibility_response.py +97 -0
  97. lightning_sdk/lightning_cloud/openapi/models/v1_upload_temporary_artifact_request.py +123 -0
  98. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +95 -355
  99. lightning_sdk/lightning_cloud/openapi/models/validate.py +27 -1
  100. lightning_sdk/lightning_cloud/rest_client.py +4 -2
  101. lightning_sdk/machine.py +25 -1
  102. lightning_sdk/models.py +18 -12
  103. lightning_sdk/pipeline/__init__.py +4 -0
  104. lightning_sdk/pipeline/pipeline.py +109 -0
  105. lightning_sdk/pipeline/types.py +268 -0
  106. lightning_sdk/pipeline/utils.py +69 -0
  107. lightning_sdk/plugin.py +9 -10
  108. lightning_sdk/services/utilities.py +2 -2
  109. lightning_sdk/studio.py +5 -1
  110. lightning_sdk/teamspace.py +1 -1
  111. lightning_sdk/utils/resolve.py +12 -1
  112. {lightning_sdk-0.1.57.dist-info → lightning_sdk-0.2.0.dist-info}/METADATA +6 -8
  113. {lightning_sdk-0.1.57.dist-info → lightning_sdk-0.2.0.dist-info}/RECORD +117 -88
  114. lightning_sdk/cli/legacy.py +0 -135
  115. {lightning_sdk-0.1.57.dist-info → lightning_sdk-0.2.0.dist-info}/LICENSE +0 -0
  116. {lightning_sdk-0.1.57.dist-info → lightning_sdk-0.2.0.dist-info}/WHEEL +0 -0
  117. {lightning_sdk-0.1.57.dist-info → lightning_sdk-0.2.0.dist-info}/entry_points.txt +0 -0
  118. {lightning_sdk-0.1.57.dist-info → lightning_sdk-0.2.0.dist-info}/top_level.txt +0 -0
lightning_sdk/cli/list.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from contextlib import suppress
2
2
  from typing import Callable, Optional
3
3
 
4
+ import click
4
5
  from rich.console import Console
5
6
  from rich.table import Table
6
7
  from typing_extensions import Literal
@@ -12,233 +13,291 @@ from lightning_sdk.lit_container import LitContainer
12
13
  from lightning_sdk.utils.resolve import _get_authed_user
13
14
 
14
15
 
15
- class _List(_TeamspacesMenu):
16
+ @click.group(name="list")
17
+ def list_cli() -> None:
16
18
  """List resources on the Lightning AI platform."""
17
19
 
18
- @staticmethod
19
- def _sort_studios_key(sort_by: str) -> Callable[[Studio], str]:
20
- """Return a key function to sort studios by a given attribute."""
21
- sort_key_map = {
22
- "name": lambda s: str(s.name or ""),
23
- "teamspace": lambda s: str(s.teamspace.name or ""),
24
- "status": lambda s: str(s.status or ""),
25
- "machine": lambda s: str(s.machine or ""),
26
- "cloud-account": lambda s: str(s.cloud_account or ""),
27
- }
28
- return sort_key_map.get(sort_by, lambda s: s.name)
29
-
30
- def studios(
31
- self,
32
- teamspace: Optional[str] = None,
33
- all: bool = False, # noqa: A002
34
- sort_by: Optional[Literal["name", "teamspace", "status", "machine", "cloud-account"]] = None,
35
- ) -> None:
36
- """List studios for a given teamspace.
37
-
38
- Args:
39
- teamspace: the teamspace to list studios from. Should be specified as {owner}/{name}
40
- If not provided, can be selected in an interactive menu.
41
- all: if teamspece is not provided, list all studios in all teamspaces.
42
- sort_by: the attribute to sort the studios by.
43
- Can be one of "name", "teamspace", "status", "machine", "cloud-account".
44
-
45
- """
46
- studios = []
47
- if all and not teamspace:
48
- user = _get_authed_user()
49
- possible_teamspaces = self._get_possible_teamspaces(user)
50
- for ts in possible_teamspaces.values():
51
- teamspace = Teamspace(**ts)
52
- studios.extend(teamspace.studios)
53
- else:
54
- resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
55
- studios = resolved_teamspace.studios
56
-
57
- table = Table(
58
- pad_edge=True,
20
+
21
+ @list_cli.command(name="studios")
22
+ @click.option(
23
+ "--teamspace",
24
+ default=None,
25
+ help=(
26
+ "the teamspace to list studios from. Should be specified as {owner}/{name}"
27
+ "If not provided, can be selected in an interactive menu."
28
+ ),
29
+ )
30
+ @click.option(
31
+ "--all",
32
+ is_flag=True,
33
+ flag_value=True,
34
+ default=False,
35
+ help="if teamspace is not provided, list all studios in all teamspaces.",
36
+ )
37
+ @click.option(
38
+ "--sort-by",
39
+ default=None,
40
+ type=click.Choice(["name", "teamspace", "status", "machine", "cloud-account"], case_sensitive=False),
41
+ help="the attribute to sort the studios by.",
42
+ )
43
+ def studios(
44
+ teamspace: Optional[str] = None,
45
+ all: bool = False, # noqa: A002
46
+ sort_by: Optional[Literal["name", "teamspace", "status", "machine", "cloud-account"]] = None,
47
+ ) -> None:
48
+ """List studios for a given teamspace."""
49
+ studios = []
50
+ menu = _TeamspacesMenu()
51
+ if all and not teamspace:
52
+ user = _get_authed_user()
53
+ possible_teamspaces = menu._get_possible_teamspaces(user)
54
+ for ts in possible_teamspaces.values():
55
+ teamspace = Teamspace(**ts)
56
+ studios.extend(teamspace.studios)
57
+ else:
58
+ resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
59
+ studios = resolved_teamspace.studios
60
+
61
+ table = Table(
62
+ pad_edge=True,
63
+ )
64
+ table.add_column("Name")
65
+ table.add_column("Teamspace")
66
+ table.add_column("Status")
67
+ table.add_column("Machine")
68
+ table.add_column("Cloud account")
69
+ for studio in sorted(studios, key=_sort_studios_key(sort_by)):
70
+ table.add_row(
71
+ studio.name,
72
+ f"{studio.teamspace.owner.name}/{studio.teamspace.name}",
73
+ str(studio.status),
74
+ str(studio.machine) if studio.machine is not None else None, # when None the cell is empty
75
+ str(studio.cloud_account),
59
76
  )
60
- table.add_column("Name")
61
- table.add_column("Teamspace")
62
- table.add_column("Status")
63
- table.add_column("Machine")
64
- table.add_column("Cloud account")
65
- for studio in sorted(studios, key=self._sort_studios_key(sort_by)):
77
+
78
+ Console().print(table)
79
+
80
+
81
+ @list_cli.command(name="jobs")
82
+ @click.option(
83
+ "--teamspace",
84
+ default=None,
85
+ help=(
86
+ "the teamspace to list jobs from. Should be specified as {owner}/{name}"
87
+ "If not provided, can be selected in an interactive menu."
88
+ ),
89
+ )
90
+ @click.option(
91
+ "--all",
92
+ is_flag=True,
93
+ flag_value=True,
94
+ default=False,
95
+ help="if teamspace is not provided, list all jobs in all teamspaces.",
96
+ )
97
+ @click.option(
98
+ "--sort-by",
99
+ "--sort_by",
100
+ default=None,
101
+ type=click.Choice(
102
+ ["name", "teamspace", "status", "studio", "machine", "image", "cloud-account"], case_sensitive=False
103
+ ),
104
+ help="the attribute to sort the jobs by.",
105
+ )
106
+ def jobs(
107
+ teamspace: Optional[str] = None,
108
+ all: bool = False, # noqa: A002
109
+ sort_by: Optional[Literal["name", "teamspace", "status", "studio", "machine", "image", "cloud-account"]] = None,
110
+ ) -> None:
111
+ """List jobs for a given teamspace."""
112
+ jobs = []
113
+ menu = _TeamspacesMenu()
114
+ if all and not teamspace:
115
+ user = _get_authed_user()
116
+ possible_teamspaces = menu._get_possible_teamspaces(user)
117
+ for ts in possible_teamspaces.values():
118
+ teamspace = Teamspace(**ts)
119
+ jobs.extend(teamspace.jobs)
120
+ else:
121
+ resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
122
+ jobs = resolved_teamspace.jobs
123
+
124
+ table = Table(pad_edge=True)
125
+ table.add_column("Name")
126
+ table.add_column("Teamspace")
127
+ table.add_column("Studio")
128
+ table.add_column("Image")
129
+ table.add_column("Status")
130
+ table.add_column("Machine")
131
+ table.add_column("Total Cost")
132
+ for j in sorted(jobs, key=_sort_jobs_key(sort_by)):
133
+ # we know we just fetched these, so no need to refetch
134
+ j._prevent_refetch_latest = True
135
+ j._internal_job._prevent_refetch_latest = True
136
+
137
+ studio = j.studio
138
+ with suppress(RuntimeError):
66
139
  table.add_row(
67
- studio.name,
68
- f"{studio.teamspace.owner.name}/{studio.teamspace.name}",
69
- str(studio.status),
70
- str(studio.machine) if studio.machine is not None else None, # when None the cell is empty
71
- str(studio.cloud_account),
140
+ j.name,
141
+ f"{j.teamspace.owner.name}/{j.teamspace.name}",
142
+ studio.name if studio else None,
143
+ j.image,
144
+ str(j.status) if j.status is not None else None,
145
+ str(j.machine),
146
+ f"{j.total_cost:.3f}",
72
147
  )
73
148
 
74
- Console().print(table)
75
-
76
- @staticmethod
77
- def _sort_jobs_key(sort_by: str) -> Callable[[Job], str]:
78
- """Return a key function to sort studios by a given attribute."""
79
- sort_key_map = {
80
- "name": lambda j: str(j.name or ""),
81
- "teamspace": lambda j: str(j.teamspace.name or ""),
82
- "status": lambda j: str(j.status or ""),
83
- "machine": lambda j: str(j.machine or ""),
84
- "studio": lambda j: str(j.studio or ""),
85
- "image": lambda j: str(j.image or ""),
86
- "cloud-account": lambda j: str(j.cloud_account or ""),
87
- }
88
- return sort_key_map.get(sort_by, lambda j: j.name)
89
-
90
- def jobs(
91
- self,
92
- teamspace: Optional[str] = None,
93
- all: bool = False, # noqa: A002
94
- sort_by: Optional[Literal["name", "teamspace", "status", "studio", "machine", "image", "cloud-account"]] = None,
95
- ) -> None:
96
- """List jobs for a given teamspace.
97
-
98
- Args:
99
- teamspace: the teamspace to list jobs from. Should be specified as {owner}/{name}
100
- If not provided, can be selected in an interactive menu.
101
- all: if teamspece is not provided, list all jobs in all teamspaces.
102
- sort_by: the attribute to sort the jobs by.
103
- Can be one of "name", "teamspace", "status", "studio", "machine", "image", "cloud-account".
104
-
105
- """
106
- jobs = []
107
- if all and not teamspace:
108
- user = _get_authed_user()
109
- possible_teamspaces = self._get_possible_teamspaces(user)
110
- for ts in possible_teamspaces.values():
111
- teamspace = Teamspace(**ts)
112
- jobs.extend(teamspace.jobs)
113
- else:
114
- resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
115
- jobs = resolved_teamspace.jobs
116
-
117
- table = Table(pad_edge=True)
118
- table.add_column("Name")
119
- table.add_column("Teamspace")
120
- table.add_column("Studio")
121
- table.add_column("Image")
122
- table.add_column("Status")
123
- table.add_column("Machine")
124
- table.add_column("Total Cost")
125
- for j in sorted(jobs, key=self._sort_jobs_key(sort_by)):
126
- # we know we just fetched these, so no need to refetch
127
- j._prevent_refetch_latest = True
149
+ Console().print(table)
150
+
151
+
152
+ @list_cli.command(name="mmts")
153
+ @click.option(
154
+ "--teamspace",
155
+ default=None,
156
+ help=(
157
+ "the teamspace to list multi-machine jobs from. Should be specified as {owner}/{name}"
158
+ "If not provided, can be selected in an interactive menu."
159
+ ),
160
+ )
161
+ @click.option(
162
+ "--all",
163
+ is_flag=True,
164
+ flag_value=True,
165
+ default=False,
166
+ help="if teamspace is not provided, list all multi-machine jobs in all teamspaces.",
167
+ )
168
+ @click.option(
169
+ "--sort-by",
170
+ "--sort_by",
171
+ default=None,
172
+ type=click.Choice(
173
+ ["name", "teamspace", "studio", "image", "status", "machine", "cloud-account"], case_sensitive=False
174
+ ),
175
+ help="the attribute to sort the multi-machine jobs by.",
176
+ )
177
+ def mmts(
178
+ teamspace: Optional[str] = None,
179
+ all: bool = False, # noqa: A002
180
+ sort_by: Optional[Literal["name", "teamspace", "studio", "image", "status", "machine", "cloud-account"]] = None,
181
+ ) -> None:
182
+ """List multi-machine jobs for a given teamspace."""
183
+ jobs = []
184
+ menu = _TeamspacesMenu()
185
+ if all and not teamspace:
186
+ user = _get_authed_user()
187
+ possible_teamspaces = menu._get_possible_teamspaces(user)
188
+ for ts in possible_teamspaces.values():
189
+ teamspace = Teamspace(**ts)
190
+ jobs.extend(teamspace.multi_machine_jobs)
191
+ else:
192
+ resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
193
+ jobs = resolved_teamspace.multi_machine_jobs
194
+
195
+ table = Table(pad_edge=True)
196
+ table.add_column("Name")
197
+ table.add_column("Teamspace")
198
+ table.add_column("Studio")
199
+ table.add_column("Image")
200
+ table.add_column("Status")
201
+ table.add_column("Machine")
202
+ table.add_column("Num Machines")
203
+ table.add_column("Total Cost")
204
+ for j in sorted(jobs, key=_sort_mmts_key(sort_by)):
205
+ # we know we just fetched these, so no need to refetch
206
+ j._prevent_refetch_latest = True
207
+ with suppress(AttributeError):
128
208
  j._internal_job._prevent_refetch_latest = True
129
209
 
130
- studio = j.studio
131
- with suppress(RuntimeError):
132
- table.add_row(
133
- j.name,
134
- f"{j.teamspace.owner.name}/{j.teamspace.name}",
135
- studio.name if studio else None,
136
- j.image,
137
- str(j.status) if j.status is not None else None,
138
- str(j.machine),
139
- f"{j.total_cost:.3f}",
140
- )
141
-
142
- Console().print(table)
143
-
144
- def _sort_mmts_key(self, sort_by: str) -> Callable[[V1MultiMachineJob], str]:
145
- """Return a key function to sort multi-machine jobs by a given attribute."""
146
- sort_key_map = {
147
- "name": lambda j: str(j.name or ""),
148
- "teamspace": lambda j: str(j.teamspace.name or ""),
149
- "studio": lambda j: str(j.studio.name or ""),
150
- "image": lambda j: str(j.image or ""),
151
- "status": lambda j: str(j.status or ""),
152
- "machine": lambda j: str(j.machine or ""),
153
- "cloud-account": lambda j: str(j.cloud_account or ""),
154
- }
155
- return sort_key_map.get(sort_by, lambda j: j.name)
156
-
157
- def mmts(
158
- self,
159
- teamspace: Optional[str] = None,
160
- all: bool = False, # noqa: A002
161
- sort_by: Optional[Literal["name", "teamspace", "studio", "image", "status", "machine", "cloud-account"]] = None,
162
- ) -> None:
163
- """List multi-machine jobs for a given teamspace.
164
-
165
- Args:
166
- teamspace: the teamspace to list jobs from. Should be specified as {owner}/{name}
167
- If not provided, can be selected in an interactive menu.
168
- all: if teamspece is not provided, list all multi-machine jobs in all teamspaces.
169
- sort_by: the attribute to sort the multi-machine jobs by.
170
- Can be one of "name", "teamspace", "studio", "image", "status", "machine", "cloud-account".
171
-
172
- """
173
- jobs = []
174
- if all and not teamspace:
175
- user = _get_authed_user()
176
- possible_teamspaces = self._get_possible_teamspaces(user)
177
- for ts in possible_teamspaces.values():
178
- teamspace = Teamspace(**ts)
179
- jobs.extend(teamspace.multi_machine_jobs)
180
- else:
181
- resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
182
- jobs = resolved_teamspace.multi_machine_jobs
183
-
184
- table = Table(pad_edge=True)
185
- table.add_column("Name")
186
- table.add_column("Teamspace")
187
- table.add_column("Studio")
188
- table.add_column("Image")
189
- table.add_column("Status")
190
- table.add_column("Machine")
191
- table.add_column("Num Machines")
192
- table.add_column("Total Cost")
193
- for j in sorted(jobs, key=self._sort_mmts_key(sort_by)):
194
- # we know we just fetched these, so no need to refetch
195
- j._prevent_refetch_latest = True
196
- with suppress(AttributeError):
197
- j._internal_job._prevent_refetch_latest = True
198
-
199
- studio = j.studio
200
- with suppress(RuntimeError):
201
- table.add_row(
202
- j.name,
203
- f"{j.teamspace.owner.name}/{j.teamspace.name}",
204
- studio.name if studio else None,
205
- j.image,
206
- str(j.status),
207
- str(j.machine),
208
- str(j.num_machines),
209
- str(j.total_cost),
210
- )
211
-
212
- Console().print(table)
213
-
214
- def containers(self, teamspace: Optional[str] = None) -> None:
215
- """Display the list of available containers.
216
-
217
- Args:
218
- teamspace: The teamspace to list containers from. Should be specified as {owner}/{name}
219
- If not provided, can be selected in an interactive menu.
220
- """
221
- api = LitContainer()
222
- resolved_teamspace = self._resolve_teamspace(teamspace=teamspace)
223
- result = api.list_containers(teamspace=resolved_teamspace.name, org=resolved_teamspace.owner.name)
224
- table = Table(pad_edge=True, box=None)
225
- table.add_column("REPOSITORY")
226
- table.add_column("IMAGE ID")
227
- table.add_column("CREATED")
228
- for repo in result:
229
- table.add_row(repo["REPOSITORY"], repo["IMAGE ID"], repo["CREATED"])
230
- Console().print(table)
231
-
232
- def machines(self) -> None:
233
- """Display the list of available machines."""
234
- table = Table(pad_edge=True)
235
- table.add_column("Name")
236
-
237
- # Get all machine types from the enum
238
- machine_types = [name for name in dir(Machine) if not name.startswith("_")]
239
-
240
- # Add rows to table
241
- for name in sorted(machine_types):
242
- table.add_row(name)
243
-
244
- Console().print(table)
210
+ studio = j.studio
211
+ with suppress(RuntimeError):
212
+ table.add_row(
213
+ j.name,
214
+ f"{j.teamspace.owner.name}/{j.teamspace.name}",
215
+ studio.name if studio else None,
216
+ j.image,
217
+ str(j.status),
218
+ str(j.machine),
219
+ str(j.num_machines),
220
+ str(j.total_cost),
221
+ )
222
+
223
+ Console().print(table)
224
+
225
+
226
+ @list_cli.command(name="containers")
227
+ @click.option(
228
+ "--teamspace",
229
+ default=None,
230
+ help=(
231
+ "the teamspace to list containers from. Should be specified as {owner}/{name}"
232
+ "If not provided, can be selected in an interactive menu."
233
+ ),
234
+ )
235
+ def containers(teamspace: Optional[str] = None) -> None:
236
+ """Display the list of available containers."""
237
+ api = LitContainer()
238
+ menu = _TeamspacesMenu()
239
+ resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
240
+ result = api.list_containers(teamspace=resolved_teamspace.name, org=resolved_teamspace.owner.name)
241
+ table = Table(pad_edge=True, box=None)
242
+ table.add_column("REPOSITORY")
243
+ table.add_column("IMAGE ID")
244
+ table.add_column("CREATED")
245
+ for repo in result:
246
+ table.add_row(repo["REPOSITORY"], repo["IMAGE ID"], repo["CREATED"])
247
+ Console().print(table)
248
+
249
+
250
+ @list_cli.command(name="machines")
251
+ def machines() -> None:
252
+ """Display the list of available machines."""
253
+ table = Table(pad_edge=True)
254
+ table.add_column("Name")
255
+
256
+ # Get all machine types from the enum
257
+ machine_types = [name for name in dir(Machine) if isinstance(getattr(Machine, name), Machine)]
258
+
259
+ # Add rows to table
260
+ for name in sorted(machine_types):
261
+ table.add_row(name)
262
+
263
+ Console().print(table)
264
+
265
+
266
+ def _sort_studios_key(sort_by: str) -> Callable[[Studio], str]:
267
+ """Return a key function to sort studios by a given attribute."""
268
+ sort_key_map = {
269
+ "name": lambda s: str(s.name or ""),
270
+ "teamspace": lambda s: str(s.teamspace.name or ""),
271
+ "status": lambda s: str(s.status or ""),
272
+ "machine": lambda s: str(s.machine or ""),
273
+ "cloud-account": lambda s: str(s.cloud_account or ""),
274
+ }
275
+ return sort_key_map.get(sort_by, lambda s: s.name)
276
+
277
+
278
+ def _sort_jobs_key(sort_by: str) -> Callable[[Job], str]:
279
+ """Return a key function to sort studios by a given attribute."""
280
+ sort_key_map = {
281
+ "name": lambda j: str(j.name or ""),
282
+ "teamspace": lambda j: str(j.teamspace.name or ""),
283
+ "status": lambda j: str(j.status or ""),
284
+ "machine": lambda j: str(j.machine or ""),
285
+ "studio": lambda j: str(j.studio or ""),
286
+ "image": lambda j: str(j.image or ""),
287
+ "cloud-account": lambda j: str(j.cloud_account or ""),
288
+ }
289
+ return sort_key_map.get(sort_by, lambda j: j.name)
290
+
291
+
292
+ def _sort_mmts_key(sort_by: str) -> Callable[[V1MultiMachineJob], str]:
293
+ """Return a key function to sort multi-machine jobs by a given attribute."""
294
+ sort_key_map = {
295
+ "name": lambda j: str(j.name or ""),
296
+ "teamspace": lambda j: str(j.teamspace.name or ""),
297
+ "studio": lambda j: str(j.studio.name or ""),
298
+ "image": lambda j: str(j.image or ""),
299
+ "status": lambda j: str(j.status or ""),
300
+ "machine": lambda j: str(j.machine or ""),
301
+ "cloud-account": lambda j: str(j.cloud_account or ""),
302
+ }
303
+ return sort_key_map.get(sort_by, lambda j: j.name)
@@ -21,7 +21,7 @@ class _MMTsMenu:
21
21
  if j.name == mmt:
22
22
  return j
23
23
 
24
- Console().print("Could not find Multi-Machine Job {mmt}, please select it from the list:")
24
+ Console().print(f"Could not find Multi-Machine Job {mmt}, please select it from the list:")
25
25
  return self._get_mmt_from_interactive_menu(possible_mmts)
26
26
 
27
27
  @staticmethod
@@ -0,0 +1,62 @@
1
+ import webbrowser
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import click
6
+ from rich.console import Console
7
+
8
+ from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
9
+ from lightning_sdk.cli.upload import _upload_folder
10
+ from lightning_sdk.studio import Studio
11
+ from lightning_sdk.teamspace import Teamspace
12
+ from lightning_sdk.utils.resolve import _get_studio_url
13
+
14
+
15
+ @click.command("open")
16
+ @click.argument("path", default=".", type=click.Path(exists=True))
17
+ @click.option(
18
+ "--teamspace",
19
+ default=None,
20
+ help=(
21
+ "The teamspace to create the Studio in. "
22
+ "Should be of format <OWNER>/<TEAMSPACE_NAME>. "
23
+ "If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
24
+ ),
25
+ )
26
+ def open(path: str = ".", teamspace: Optional[str] = None) -> None: # noqa: A001
27
+ """Open a local file or folder in a Lightning Studio.
28
+
29
+ Example:
30
+ lightning open PATH
31
+
32
+ PATH: the path to the file or folder to open. Defaults to the current directory.
33
+ """
34
+ console = Console()
35
+
36
+ pathlib_path = Path(path).resolve()
37
+
38
+ try:
39
+ resolved_teamspace = Teamspace()
40
+ except ValueError:
41
+ menu = _TeamspacesMenu()
42
+ resolved_teamspace = menu._resolve_teamspace(teamspace=teamspace)
43
+
44
+ new_studio = Studio(name=pathlib_path.stem, teamspace=resolved_teamspace)
45
+
46
+ console.print(
47
+ f"[bold]Uploading {path} to {new_studio.owner.name}/{new_studio.teamspace.name}/{new_studio.name}[/bold]"
48
+ )
49
+
50
+ if pathlib_path.is_dir():
51
+ _upload_folder(path, remote_path=".", studio=new_studio)
52
+ else:
53
+ new_studio.upload_file(path)
54
+
55
+ studio_url = _get_studio_url(new_studio, turn_on=True)
56
+
57
+ console.line()
58
+ console.print(f"[bold]Opening {new_studio.owner.name}/{new_studio.teamspace.name}/{new_studio.name}[/bold]")
59
+
60
+ ok = webbrowser.open(studio_url)
61
+ if not ok:
62
+ console.print(f"Open your Studio at: {studio_url}")