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
@@ -1,20 +1,25 @@
1
1
  import os
2
2
  import subprocess
3
- import warnings
4
3
  from pathlib import Path
5
4
  from typing import Optional, Union
6
5
 
6
+ import click
7
7
  import docker
8
8
  from rich.console import Console
9
9
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
10
10
  from rich.prompt import Confirm
11
11
 
12
+ from lightning_sdk.cli.docker import _api as dockerize_api
12
13
 
13
- class _LitServe:
14
+
15
+ @click.group("serve")
16
+ def serve() -> None:
14
17
  """Serve a LitServe model.
15
18
 
16
19
  Example:
17
20
  lightning serve api server.py # serve locally
21
+
22
+ Example:
18
23
  lightning serve api server.py --cloud # deploy to the cloud
19
24
 
20
25
  You can deploy the API to the cloud by running `lightning serve api server.py --cloud`.
@@ -22,192 +27,160 @@ class _LitServe:
22
27
  Deploying to the cloud requires pre-login to the docker registry.
23
28
  """
24
29
 
25
- def api(
26
- self,
27
- script_path: Union[str, Path],
28
- easy: bool = False,
29
- cloud: bool = False,
30
- repository: Optional[str] = None,
31
- non_interactive: bool = False,
32
- ) -> None:
33
- """Deploy a LitServe model script.
34
-
35
- Args:
36
- script_path: Path to the script to serve
37
- easy: If True, generates a client for the model
38
- cloud: If True, deploy the model to the Lightning Studio
39
- repository: Optional Docker repository name (e.g., 'username/model-name')
40
- non_interactive: If True, do not prompt for confirmation
41
- Raises:
42
- FileNotFoundError: If script_path doesn't exist
43
- ImportError: If litserve is not installed
44
- subprocess.CalledProcessError: If the script fails to run
45
- IOError: If client.py generation fails
46
- """
47
- console = Console()
48
- script_path = Path(script_path)
49
- if not script_path.exists():
50
- raise FileNotFoundError(f"Script not found: {script_path}")
51
- if not script_path.is_file():
52
- raise ValueError(f"Path is not a file: {script_path}")
53
30
 
54
- try:
55
- from litserve.python_client import client_template
56
- except ImportError:
57
- raise ImportError(
58
- "litserve is not installed. Please install it with `pip install lightning_sdk[serve]`"
59
- ) from None
60
-
61
- if easy:
62
- client_path = Path("client.py")
63
- if client_path.exists():
64
- console.print("Skipping client generation: client.py already exists", style="blue")
65
- else:
66
- try:
67
- client_path.write_text(client_template)
68
- console.print(" Client generated at client.py", style="bold green")
69
- except OSError as e:
70
- raise OSError(f"Failed to generate client.py: {e!s}") from None
71
-
72
- if cloud:
73
- tag = repository if repository else "litserve-model"
74
- return self._handle_cloud(script_path, console, tag=tag, non_interactive=non_interactive)
75
-
76
- try:
77
- subprocess.run(
78
- ["python", str(script_path)],
79
- check=True,
80
- text=True,
81
- )
82
- except subprocess.CalledProcessError as e:
83
- error_msg = f"Script execution failed with exit code {e.returncode}\nstdout: {e.stdout}\nstderr: {e.stderr}"
84
- raise RuntimeError(error_msg) from None
85
-
86
- def _handle_cloud(
87
- self,
88
- script_path: Union[str, Path],
89
- console: Console,
90
- tag: str = "litserve-model",
91
- non_interactive: bool = False,
92
- ) -> None:
93
- try:
94
- client = docker.from_env()
95
- client.ping()
96
- except docker.errors.DockerException as e:
97
- raise RuntimeError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
98
-
99
- dockerizer = _Docker()
100
- path = dockerizer.api(script_path, port=8000, gpu=False, tag=tag)
101
-
102
- console.clear()
103
- if non_interactive:
104
- console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
105
-
106
- console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
107
- correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
108
- if not correct_dockerfile:
109
- console.print("Please fix the Dockerfile and try again.", style="red")
110
- return
111
-
112
- with Progress(
113
- SpinnerColumn(),
114
- TextColumn("[progress.description]{task.description}"),
115
- TimeElapsedColumn(),
116
- console=console,
117
- transient=False,
118
- ) as progress:
119
- build_task = progress.add_task("Building Docker image", total=None)
120
- build_status = client.api.build(
121
- path=os.path.dirname(path), dockerfile=path, tag=tag, decode=True, quiet=False
122
- )
123
- for line in build_status:
124
- if "error" in line:
125
- progress.stop()
126
- console.print(f"\n[red]{line}[/red]")
127
- return
128
- if "stream" in line and line["stream"].strip():
129
- console.print(line["stream"].strip(), style="bright_black")
130
- progress.update(build_task, description="Building Docker image")
131
-
132
- progress.update(build_task, description="[green]Build completed![/green]")
133
-
134
- push_task = progress.add_task("Pushing to registry", total=None)
135
- console.print("\nPushing image...", style="bold blue")
136
- push_status = client.api.push(tag, stream=True, decode=True)
137
- for line in push_status:
138
- if "error" in line:
139
- progress.stop()
140
- console.print(f"\n[red]{line}[/red]")
141
- return
142
- if "status" in line:
143
- console.print(line["status"], style="bright_black")
144
- progress.update(push_task, description="Pushing to registry")
145
-
146
- progress.update(push_task, description="[green]Push completed![/green]")
147
-
148
- console.print(f"\n✅ Image pushed to {tag}", style="bold green")
149
- console.print(
150
- "Soon you will be able to deploy this model to the Lightning Studio!",
31
+ @serve.command("api")
32
+ @click.argument("script-path", type=click.Path(exists=True))
33
+ @click.option(
34
+ "--easy",
35
+ is_flag=True,
36
+ default=False,
37
+ flag_value=True,
38
+ help="Generate a client for the model",
39
+ )
40
+ @click.option(
41
+ "--cloud",
42
+ is_flag=True,
43
+ default=False,
44
+ flag_value=True,
45
+ help="Deploy the model to the Lightning AI platform",
46
+ )
47
+ @click.option("--gpu", is_flag=True, default=False, flag_value=True, help="Use GPU for serving")
48
+ @click.option("--repository", default=None, help="Docker repository name (e.g., 'username/model-name')")
49
+ @click.option(
50
+ "--non-interactive",
51
+ "--non_interactive",
52
+ is_flag=True,
53
+ default=False,
54
+ flag_value=True,
55
+ help="Do not prompt for confirmation",
56
+ )
57
+ def api(
58
+ script_path: str,
59
+ easy: bool,
60
+ cloud: bool,
61
+ gpu: bool,
62
+ repository: str,
63
+ non_interactive: bool,
64
+ ) -> None:
65
+ """Deploy a LitServe model script."""
66
+ return api_impl(
67
+ script_path=script_path, easy=easy, cloud=cloud, gpu=gpu, repository=repository, non_interactive=non_interactive
68
+ )
69
+
70
+
71
+ def api_impl(
72
+ script_path: Union[str, Path],
73
+ easy: bool = False,
74
+ cloud: bool = False,
75
+ gpu: bool = False,
76
+ repository: Optional[str] = None,
77
+ non_interactive: bool = False,
78
+ ) -> None:
79
+ """Deploy a LitServe model script."""
80
+ console = Console()
81
+ script_path = Path(script_path)
82
+ if not script_path.exists():
83
+ raise FileNotFoundError(f"Script not found: {script_path}")
84
+ if not script_path.is_file():
85
+ raise ValueError(f"Path is not a file: {script_path}")
86
+
87
+ _generate_client(console) if easy else None
88
+
89
+ if cloud:
90
+ tag = repository if repository else "litserve-model"
91
+ return _handle_cloud(script_path, console, gpu=gpu, tag=tag, non_interactive=non_interactive)
92
+
93
+ try:
94
+ subprocess.run(
95
+ ["python", str(script_path)],
96
+ check=True,
97
+ text=True,
151
98
  )
152
- # TODO: Deploy to the cloud
153
-
154
-
155
- class _Docker:
156
- """Generate a Dockerfile for a LitServe model."""
157
-
158
- def api(self, server_filename: str, port: int = 8000, gpu: bool = False, tag: str = "litserve-model") -> str:
159
- """Generate a Dockerfile for the given server code.
160
-
161
- Args:
162
- server_filename: The path to the server file. Example sever.py or app.py.
163
- port: The port to expose in the Docker container.
164
- gpu: Whether to use a GPU-enabled Docker image.
165
- tag: Docker image tag to use in examples.
166
- """
167
- import litserve as ls
168
- from litserve import docker_builder
169
-
170
- requirements = ""
171
- if os.path.exists("requirements.txt"):
172
- requirements = "-r requirements.txt"
173
- else:
174
- warnings.warn(
175
- f"requirements.txt not found at {os.getcwd()}. "
176
- f"Make sure to install the required packages in the Dockerfile.",
177
- UserWarning,
178
- )
179
-
180
- current_dir = Path.cwd()
181
- if not (current_dir / server_filename).is_file():
182
- raise FileNotFoundError(f"Server file `{server_filename}` must be in the current directory: {os.getcwd()}")
183
-
184
- version = ls.__version__
185
- if gpu:
186
- run_cmd = f"docker run --gpus all -p {port}:{port} {tag}:latest"
187
- docker_template = docker_builder.CUDA_DOCKER_TEMPLATE
188
- else:
189
- run_cmd = f"docker run -p {port}:{port} {tag}:latest"
190
- docker_template = docker_builder.DOCKERFILE_TEMPLATE
191
- dockerfile_content = docker_template.format(
192
- server_filename=server_filename,
193
- port=port,
194
- version=version,
195
- requirements=requirements,
196
- )
197
- with open("Dockerfile", "w") as f:
198
- f.write(dockerfile_content)
199
-
200
- success_msg = f"""[bold]Dockerfile created successfully[/bold]
201
- Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additional dependencies or commands.
202
-
203
- [bold]Build the container with:[/bold]
204
- > [underline]docker build -t {tag} .[/underline]
205
-
206
- [bold]To run the Docker container on the machine:[/bold]
207
- > [underline]{run_cmd}[/underline]
208
-
209
- [bold]To push the container to a registry:[/bold]
210
- > [underline]docker push {tag}[/underline]
211
- """
212
- Console().print(success_msg)
213
- return os.path.abspath("Dockerfile")
99
+ except subprocess.CalledProcessError as e:
100
+ error_msg = f"Script execution failed with exit code {e.returncode}\nstdout: {e.stdout}\nstderr: {e.stderr}"
101
+ raise RuntimeError(error_msg) from None
102
+
103
+
104
+ def _generate_client(console: Console) -> None:
105
+ try:
106
+ from litserve.python_client import client_template
107
+ except ImportError:
108
+ raise ImportError(
109
+ "litserve is not installed. Please install it with `pip install lightning_sdk[serve]`"
110
+ ) from None
111
+
112
+ client_path = Path("client.py")
113
+ if client_path.exists():
114
+ console.print("Skipping client generation: client.py already exists", style="blue")
115
+ else:
116
+ try:
117
+ client_path.write_text(client_template)
118
+ console.print("✅ Client generated at client.py", style="bold green")
119
+ except OSError as e:
120
+ raise OSError(f"Failed to generate client.py: {e!s}") from None
121
+
122
+
123
+ def _handle_cloud(
124
+ script_path: Union[str, Path],
125
+ console: Console,
126
+ gpu: bool,
127
+ tag: str = "litserve-model",
128
+ non_interactive: bool = False,
129
+ ) -> None:
130
+ try:
131
+ client = docker.from_env()
132
+ client.ping()
133
+ except docker.errors.DockerException as e:
134
+ raise RuntimeError(f"Failed to connect to Docker daemon: {e!s}. Is Docker running?") from None
135
+
136
+ path = dockerize_api(script_path, port=8000, gpu=gpu, tag=tag)
137
+
138
+ console.clear()
139
+ if non_interactive:
140
+ console.print("[italic]non-interactive[/italic] mode enabled, skipping confirmation prompts", style="blue")
141
+
142
+ console.print(f"\nPlease review the Dockerfile at [u]{path}[/u] and make sure it is correct.", style="bold")
143
+ correct_dockerfile = True if non_interactive else Confirm.ask("Is the Dockerfile correct?", default=True)
144
+ if not correct_dockerfile:
145
+ console.print("Please fix the Dockerfile and try again.", style="red")
146
+ return
147
+
148
+ with Progress(
149
+ SpinnerColumn(),
150
+ TextColumn("[progress.description]{task.description}"),
151
+ TimeElapsedColumn(),
152
+ console=console,
153
+ transient=False,
154
+ ) as progress:
155
+ build_task = progress.add_task("Building Docker image", total=None)
156
+ build_status = client.api.build(path=os.path.dirname(path), dockerfile=path, tag=tag, decode=True, quiet=False)
157
+ for line in build_status:
158
+ if "error" in line:
159
+ progress.stop()
160
+ console.print(f"\n[red]{line}[/red]")
161
+ return
162
+ if "stream" in line and line["stream"].strip():
163
+ console.print(line["stream"].strip(), style="bright_black")
164
+ progress.update(build_task, description="Building Docker image")
165
+
166
+ progress.update(build_task, description="[green]Build completed![/green]")
167
+
168
+ push_task = progress.add_task("Pushing to registry", total=None)
169
+ console.print("\nPushing image...", style="bold blue")
170
+ push_status = client.api.push(tag, stream=True, decode=True)
171
+ for line in push_status:
172
+ if "error" in line:
173
+ progress.stop()
174
+ console.print(f"\n[red]{line}[/red]")
175
+ return
176
+ if "status" in line:
177
+ console.print(line["status"], style="bright_black")
178
+ progress.update(push_task, description="Pushing to registry")
179
+
180
+ progress.update(push_task, description="[green]Push completed![/green]")
181
+
182
+ console.print(f"\n✅ Image pushed to {tag}", style="bold green")
183
+ console.print(
184
+ "Soon you will be able to deploy this model to the Lightning Studio!",
185
+ )
186
+ # TODO: Deploy to the cloud
@@ -1,43 +1,62 @@
1
1
  from typing import Optional
2
2
 
3
+ import click
4
+
3
5
  from lightning_sdk import Machine, Studio
6
+ from lightning_sdk.lightning_cloud.openapi.rest import ApiException
7
+
8
+ _MACHINE_VALUES = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
4
9
 
5
10
 
6
- class _Start:
11
+ @click.group("start")
12
+ def start() -> None:
7
13
  """Start resources on the Lightning AI platform."""
8
14
 
9
- def __init__(self) -> None:
10
- _machine_values = tuple([machine.name for machine in Machine.__dict__.values() if isinstance(machine, Machine)])
11
-
12
- docstr_studio = f"""Start a studio on a given machine.
13
-
14
- Args:
15
- name: The name of the studio to start.
16
- If not specified, tries to infer from the environment (e.g. when run from within a Studio.)
17
- teamspace: The teamspace the studio is part of. Should be of format <OWNER>/<TEAMSPACE_NAME>.
18
- If not specified, tries to infer from the environment (e.g. when run from within a Studio.)
19
- machine: The machine type to start the studio on. One of {", ".join(_machine_values)}.
20
- Defaults to the CPU Machine.
21
- """
22
- self.studio.__func__.__doc__ = docstr_studio
23
-
24
- def studio(self, name: Optional[str] = None, teamspace: Optional[str] = None, machine: str = "CPU") -> None:
25
- if teamspace is not None:
26
- ts_splits = teamspace.split("/")
27
- if len(ts_splits) != 2:
28
- raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
29
- owner, teamspace = ts_splits
30
- else:
31
- owner, teamspace = None, None
32
-
33
- try:
34
- studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
35
- except (RuntimeError, ValueError):
36
- studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
37
-
38
- try:
39
- resolved_machine = getattr(Machine, machine.upper(), Machine(machine, machine))
40
- except KeyError:
41
- resolved_machine = machine
42
-
43
- studio.start(resolved_machine)
15
+
16
+ @start.command("studio")
17
+ @click.argument(
18
+ "name",
19
+ )
20
+ @click.option(
21
+ "--teamspace",
22
+ default=None,
23
+ help=(
24
+ "The teamspace the studio is part of. "
25
+ "Should be of format <OWNER>/<TEAMSPACE_NAME>. "
26
+ "If not specified, tries to infer from the environment (e.g. when run from within a Studio.)"
27
+ ),
28
+ )
29
+ @click.option(
30
+ "--machine",
31
+ default="CPU",
32
+ show_default=True,
33
+ type=click.Choice(_MACHINE_VALUES),
34
+ help="The machine type to start the studio on.",
35
+ )
36
+ def studio(name: str, teamspace: Optional[str] = None, machine: str = "CPU") -> None:
37
+ """Start a studio on a given machine.
38
+
39
+ Example:
40
+ lightning start studio NAME
41
+
42
+ NAME: the name of the studio to start
43
+ """
44
+ if teamspace is not None:
45
+ ts_splits = teamspace.split("/")
46
+ if len(ts_splits) != 2:
47
+ raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
48
+ owner, teamspace = ts_splits
49
+ else:
50
+ owner, teamspace = None, None
51
+
52
+ try:
53
+ studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
54
+ except (RuntimeError, ValueError, ApiException):
55
+ studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
56
+
57
+ try:
58
+ resolved_machine = getattr(Machine, machine.upper(), Machine(machine, machine))
59
+ except KeyError:
60
+ resolved_machine = machine
61
+
62
+ studio.start(resolved_machine)
lightning_sdk/cli/stop.py CHANGED
@@ -1,65 +1,107 @@
1
1
  from typing import Optional
2
2
 
3
+ import click
3
4
  from rich.console import Console
4
5
 
5
6
  from lightning_sdk.cli.job_and_mmt_action import _JobAndMMTAction
7
+ from lightning_sdk.lightning_cloud.openapi.rest import ApiException
6
8
  from lightning_sdk.studio import Studio
7
9
 
8
10
 
9
- class _Stop(_JobAndMMTAction):
11
+ @click.group("stop")
12
+ def stop() -> None:
10
13
  """Stop resources on the Lightning AI platform."""
11
14
 
12
- def job(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
13
- """Stop a job.
14
-
15
- Args:
16
- name: the name of the job. If not specified can be selected interactively.
17
- teamspace: the name of the teamspace the job lives in.
18
- Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
19
- If not specified can be selected interactively.
20
-
21
- """
22
- job = super().job(name=name, teamspace=teamspace)
23
-
24
- job.stop()
25
- Console().print(f"Successfully stopped {job.name}!")
26
-
27
- def mmt(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
28
- """Stop a multi-machine job.
29
-
30
- Args:
31
- name: the name of the job. If not specified can be selected interactively.
32
- teamspace: the name of the teamspace the job lives in.
33
- Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace).
34
- If not specified can be selected interactively.
35
-
36
- """
37
- mmt = super().mmt(name=name, teamspace=teamspace)
38
-
39
- mmt.stop()
40
- Console().print(f"Successfully stopped {mmt.name}!")
41
-
42
- def studio(self, name: Optional[str] = None, teamspace: Optional[str] = None) -> None:
43
- """Stop a running studio.
44
-
45
- Args:
46
- name: The name of the studio to stop.
47
- If not specified, tries to infer from the environment (e.g. when run from within a Studio.)
48
- teamspace: The teamspace the studio is part of. Should be of format <OWNER>/<TEAMSPACE_NAME>.
49
- If not specified, tries to infer from the environment (e.g. when run from within a Studio.)
50
- """
51
- if teamspace is not None:
52
- ts_splits = teamspace.split("/")
53
- if len(ts_splits) != 2:
54
- raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
55
- owner, teamspace = ts_splits
56
- else:
57
- owner, teamspace = None, None
58
-
59
- try:
60
- studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
61
- except (RuntimeError, ValueError):
62
- studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
63
-
64
- studio.stop()
65
- Console().print("Studio successfully stopped")
15
+
16
+ @stop.command("job")
17
+ @click.argument(
18
+ "name",
19
+ )
20
+ @click.option(
21
+ "--teamspace",
22
+ default=None,
23
+ help=(
24
+ "the name of the teamspace the job lives in. "
25
+ "Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
26
+ "If not specified can be selected interactively."
27
+ ),
28
+ )
29
+ def job(name: str, teamspace: Optional[str] = None) -> None:
30
+ """Stop a job.
31
+
32
+ Example:
33
+ lightning stop job NAME
34
+
35
+ NAME: the name of the job to stop.
36
+ """
37
+ menu = _JobAndMMTAction()
38
+ job = menu.job(name=name, teamspace=teamspace)
39
+
40
+ job.stop()
41
+ Console().print(f"Successfully stopped {job.name}!")
42
+
43
+
44
+ @stop.command("mmt")
45
+ @click.argument(
46
+ "name",
47
+ )
48
+ @click.option(
49
+ "--teamspace",
50
+ default=None,
51
+ help=(
52
+ "the name of the teamspace the multi-machine job lives in. "
53
+ "Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
54
+ "If not specified can be selected interactively."
55
+ ),
56
+ )
57
+ def mmt(name: str, teamspace: Optional[str] = None) -> None:
58
+ """Stop a multi-machine job.
59
+
60
+ Example:
61
+ lightning stop mmt NAME
62
+
63
+ NAME: the name of the multi-machine job to stop.
64
+ """
65
+ menu = _JobAndMMTAction()
66
+ mmt = menu.mmt(name=name, teamspace=teamspace)
67
+
68
+ mmt.stop()
69
+ Console().print(f"Successfully stopped {mmt.name}!")
70
+
71
+
72
+ @stop.command("studio")
73
+ @click.argument(
74
+ "name",
75
+ )
76
+ @click.option(
77
+ "--teamspace",
78
+ default=None,
79
+ help=(
80
+ "the name of the teamspace the studio lives in. "
81
+ "Should be specified as {teamspace_owner}/{teamspace_name} (e.g my-org/my-teamspace). "
82
+ "If not specified can be selected interactively."
83
+ ),
84
+ )
85
+ def studio(name: str, teamspace: Optional[str] = None) -> None:
86
+ """Stop a running studio.
87
+
88
+ Example:
89
+ lightning stop studio NAME
90
+
91
+ NAME: the name of the studio to stop.
92
+ """
93
+ if teamspace is not None:
94
+ ts_splits = teamspace.split("/")
95
+ if len(ts_splits) != 2:
96
+ raise ValueError(f"Teamspace should be of format <OWNER>/<TEAMSPACE_NAME> but got {teamspace}")
97
+ owner, teamspace = ts_splits
98
+ else:
99
+ owner, teamspace = None, None
100
+
101
+ try:
102
+ studio = Studio(name=name, teamspace=teamspace, org=owner, user=None, create_ok=False)
103
+ except (RuntimeError, ValueError, ApiException):
104
+ studio = Studio(name=name, teamspace=teamspace, org=None, user=owner, create_ok=False)
105
+
106
+ studio.stop()
107
+ Console().print("Studio successfully stopped")