lightning-sdk 0.2.15__py3-none-any.whl → 0.2.17__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 (80) hide show
  1. lightning_sdk/__init__.py +1 -1
  2. lightning_sdk/api/base_studio_api.py +7 -1
  3. lightning_sdk/api/cluster_api.py +83 -1
  4. lightning_sdk/api/llm_api.py +27 -5
  5. lightning_sdk/api/studio_api.py +64 -0
  6. lightning_sdk/api/teamspace_api.py +127 -1
  7. lightning_sdk/api/utils.py +4 -0
  8. lightning_sdk/base_studio.py +14 -1
  9. lightning_sdk/cli/create.py +21 -1
  10. lightning_sdk/cli/deploy/__init__.py +0 -0
  11. lightning_sdk/cli/deploy/_auth.py +189 -0
  12. lightning_sdk/cli/deploy/devbox.py +157 -0
  13. lightning_sdk/cli/{serve.py → deploy/serve.py} +11 -322
  14. lightning_sdk/cli/download.py +44 -16
  15. lightning_sdk/cli/entrypoint.py +1 -1
  16. lightning_sdk/cli/open.py +21 -2
  17. lightning_sdk/cli/start.py +12 -3
  18. lightning_sdk/cli/upload.py +2 -4
  19. lightning_sdk/lightning_cloud/openapi/__init__.py +19 -0
  20. lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +126 -1
  21. lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +97 -0
  22. lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +105 -0
  23. lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +105 -0
  24. lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +752 -106
  25. lightning_sdk/lightning_cloud/openapi/api/storage_service_api.py +93 -0
  26. lightning_sdk/lightning_cloud/openapi/models/__init__.py +19 -0
  27. lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +53 -1
  28. lightning_sdk/lightning_cloud/openapi/models/cloudspaces_id_body.py +53 -1
  29. lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +27 -1
  30. lightning_sdk/lightning_cloud/openapi/models/deployment_id_alertingpolicies_body.py +357 -0
  31. lightning_sdk/lightning_cloud/openapi/models/deployment_id_alertingpolicies_body1.py +331 -0
  32. lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +79 -1
  33. lightning_sdk/lightning_cloud/openapi/models/models_id_body.py +123 -0
  34. lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +105 -1
  35. lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py +27 -1
  36. lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +29 -3
  37. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +79 -1
  38. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +27 -1
  39. lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_source_type.py +103 -0
  40. lightning_sdk/lightning_cloud/openapi/models/v1_cluster_tagging_options.py +27 -1
  41. lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +27 -1
  42. lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
  43. lightning_sdk/lightning_cloud/openapi/models/v1_delete_deployment_alerting_policy_response.py +175 -0
  44. lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +79 -1
  45. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_event.py +487 -0
  46. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy.py +409 -0
  47. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_frequency.py +105 -0
  48. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_operation.py +105 -0
  49. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_severity.py +106 -0
  50. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_type.py +111 -0
  51. lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_recipients.py +175 -0
  52. lightning_sdk/lightning_cloud/openapi/models/v1_ge_list_deployment_routing_telemetry_response.py +27 -1
  53. lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_instance_open_ports_response.py +123 -0
  54. lightning_sdk/lightning_cloud/openapi/models/v1_get_deployment_routing_telemetry_content_response.py +123 -0
  55. lightning_sdk/lightning_cloud/openapi/models/v1_get_organization_storage_metadata_response.py +331 -0
  56. lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +1 -27
  57. lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +27 -1
  58. lightning_sdk/lightning_cloud/openapi/models/v1_list_deployment_alerting_events_response.py +123 -0
  59. lightning_sdk/lightning_cloud/openapi/models/v1_list_deployment_alerting_policies_response.py +175 -0
  60. lightning_sdk/lightning_cloud/openapi/models/v1_membership.py +27 -1
  61. lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +105 -1
  62. lightning_sdk/lightning_cloud/openapi/models/v1_project.py +27 -1
  63. lightning_sdk/lightning_cloud/openapi/models/v1_project_membership.py +27 -1
  64. lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +29 -3
  65. lightning_sdk/lightning_cloud/openapi/models/v1_project_storage.py +53 -1
  66. lightning_sdk/lightning_cloud/openapi/models/v1_routing_telemetry.py +253 -0
  67. lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_type.py +1 -0
  68. lightning_sdk/lightning_cloud/openapi/models/v1_sleep_server_response.py +97 -0
  69. lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +1 -27
  70. lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +105 -53
  71. lightning_sdk/lightning_cloud/openapi/models/v1_user_requested_compute_config.py +27 -1
  72. lightning_sdk/llm/llm.py +54 -8
  73. lightning_sdk/studio.py +40 -1
  74. lightning_sdk/teamspace.py +68 -0
  75. {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/METADATA +1 -1
  76. {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/RECORD +80 -58
  77. {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/LICENSE +0 -0
  78. {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/WHEEL +0 -0
  79. {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/entry_points.txt +0 -0
  80. {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,189 @@
1
+ import os
2
+ import time
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import Any, List, Optional, TypedDict
6
+ from urllib.parse import urlencode
7
+
8
+ from rich.console import Console
9
+ from rich.prompt import Confirm
10
+
11
+ from lightning_sdk import Teamspace
12
+ from lightning_sdk.api import UserApi
13
+ from lightning_sdk.cli.teamspace_menu import _TeamspacesMenu
14
+ from lightning_sdk.lightning_cloud import env
15
+ from lightning_sdk.lightning_cloud.login import Auth, AuthServer
16
+ from lightning_sdk.lightning_cloud.openapi import V1CloudSpace
17
+ from lightning_sdk.lightning_cloud.rest_client import LightningClient
18
+ from lightning_sdk.utils.resolve import _get_authed_user, _resolve_teamspace
19
+
20
+ LITSERVE_CODE = os.environ.get("LITSERVE_CODE", "j39bzk903h")
21
+ _POLL_TIMEOUT = 120
22
+
23
+
24
+ class _AuthMode(Enum):
25
+ DEVBOX = "dev"
26
+ DEPLOY = "deploy"
27
+
28
+
29
+ class _AuthServer(AuthServer):
30
+ def __init__(self, mode: _AuthMode, *args: Any, **kwargs: Any) -> None:
31
+ self._mode = mode
32
+ super().__init__(*args, **kwargs)
33
+
34
+ def get_auth_url(self, port: int) -> str:
35
+ redirect_uri = f"http://localhost:{port}/login-complete"
36
+ params = urlencode({"redirectTo": redirect_uri, "mode": self._mode.value, "okbhrt": LITSERVE_CODE})
37
+ return f"{env.LIGHTNING_CLOUD_URL}/sign-in?{params}"
38
+
39
+
40
+ class _Auth(Auth):
41
+ def __init__(self, mode: _AuthMode, shall_confirm: bool = False) -> None:
42
+ super().__init__()
43
+ self._mode = mode
44
+ self._shall_confirm = shall_confirm
45
+
46
+ def _run_server(self) -> None:
47
+ if self._shall_confirm:
48
+ proceed = Confirm.ask(
49
+ "Authenticating with Lightning AI. This will open a browser window. Continue?", default=True
50
+ )
51
+ if not proceed:
52
+ raise RuntimeError(
53
+ "Login cancelled. Please login to Lightning AI to deploy the API. Run `lightning login` to login."
54
+ ) from None
55
+ print("Opening browser for authentication...")
56
+ print("Please come back to the terminal after logging in.")
57
+ time.sleep(3)
58
+ _AuthServer(self._mode).login_with_browser(self)
59
+
60
+
61
+ def authenticate(mode: _AuthMode, shall_confirm: bool = True) -> None:
62
+ auth = _Auth(mode, shall_confirm)
63
+ auth.authenticate()
64
+
65
+
66
+ def select_teamspace(teamspace: Optional[str], org: Optional[str], user: Optional[str]) -> Teamspace:
67
+ if teamspace is None:
68
+ user = _get_authed_user()
69
+ menu = _TeamspacesMenu()
70
+ possible_teamspaces = menu._get_possible_teamspaces(user)
71
+ if len(possible_teamspaces) == 1:
72
+ name = next(iter(possible_teamspaces.values()))["name"]
73
+ return Teamspace(name=name, org=org, user=user)
74
+
75
+ return menu._resolve_teamspace(teamspace)
76
+
77
+ return _resolve_teamspace(teamspace=teamspace, org=org, user=user)
78
+
79
+
80
+ class _UserStatus(TypedDict):
81
+ verified: bool
82
+ onboarded: bool
83
+
84
+
85
+ def poll_verified_status(timeout: int = _POLL_TIMEOUT) -> _UserStatus:
86
+ """Polls the verified status of the user until it is True or a timeout occurs."""
87
+ user_api = UserApi()
88
+ user = _get_authed_user()
89
+ start_time = datetime.now()
90
+ result = {"onboarded": False, "verified": False}
91
+ while True:
92
+ user_resp = user_api.get_user(name=user.name)
93
+ result["onboarded"] = user_resp.status.completed_project_onboarding
94
+ result["verified"] = user_resp.status.verified
95
+ if user_resp.status.verified:
96
+ return result
97
+ if (datetime.now() - start_time).total_seconds() > timeout:
98
+ break
99
+ time.sleep(5)
100
+ return result
101
+
102
+
103
+ class _OnboardingStatus(Enum):
104
+ NOT_VERIFIED = "not_verified"
105
+ ONBOARDING = "onboarding"
106
+ ONBOARDED = "onboarded"
107
+
108
+
109
+ class _Onboarding:
110
+ def __init__(self, console: Console) -> None:
111
+ self.console = console
112
+ self.user = _get_authed_user()
113
+ self.user_api = UserApi()
114
+ self.client = LightningClient(max_tries=7)
115
+
116
+ @property
117
+ def verified(self) -> bool:
118
+ return self.user_api.get_user(name=self.user.name).status.verified
119
+
120
+ @property
121
+ def is_onboarded(self) -> bool:
122
+ return self.user_api.get_user(name=self.user.name).status.completed_project_onboarding
123
+
124
+ @property
125
+ def can_join_org(self) -> bool:
126
+ return len(self.client.organizations_service_list_joinable_organizations().joinable_organizations) > 0
127
+
128
+ @property
129
+ def status(self) -> _OnboardingStatus:
130
+ if not self.verified:
131
+ return _OnboardingStatus.NOT_VERIFIED
132
+ if self.is_onboarded:
133
+ return _OnboardingStatus.ONBOARDED
134
+ return _OnboardingStatus.ONBOARDING
135
+
136
+ def _wait_user_onboarding(self, timeout: int = _POLL_TIMEOUT) -> None:
137
+ """Wait for user onboarding if they can join the teamspace otherwise move to select a teamspace."""
138
+ status = self.status
139
+ if status == _OnboardingStatus.ONBOARDED:
140
+ return
141
+
142
+ self.console.print("Waiting for account setup. Visit lightning.ai")
143
+ start_time = datetime.now()
144
+ while self.status != _OnboardingStatus.ONBOARDED:
145
+ time.sleep(5)
146
+ if self.is_onboarded:
147
+ return
148
+ if (datetime.now() - start_time).total_seconds() > timeout:
149
+ break
150
+
151
+ raise RuntimeError("Timed out waiting for onboarding status")
152
+
153
+ def get_cloudspace_id(self, teamspace: Teamspace) -> Optional[str]:
154
+ cloudspaces: List[V1CloudSpace] = self.client.cloud_space_service_list_cloud_spaces(teamspace.id).cloudspaces
155
+ cloudspaces = sorted(cloudspaces, key=lambda cloudspace: cloudspace.created_at, reverse=True)
156
+ if len(cloudspaces) == 0:
157
+ raise RuntimeError("Error creating deployment! Finish account setup at lightning.ai first.")
158
+ # get the first cloudspace
159
+ cloudspace = cloudspaces[0]
160
+ if "scratch-studio" in cloudspace.name or "scratch-studio" in cloudspace.display_name:
161
+ return cloudspace.id
162
+ return None
163
+
164
+ def select_teamspace(self, teamspace: Optional[str], org: Optional[str], user: Optional[str]) -> Teamspace:
165
+ """Select a teamspace while onboarding.
166
+
167
+ If user is being onboarded and can't join any org, the teamspace it will be resolved to the default
168
+ personal teamspace.
169
+ If user is being onboarded and can join an org then it will select default teamspace from the org.
170
+ """
171
+ if self.is_onboarded:
172
+ return select_teamspace(teamspace, org, user)
173
+
174
+ # Run only when user hasn't completed onboarding yet.
175
+ menu = _TeamspacesMenu()
176
+ self._wait_user_onboarding()
177
+ # Onboarding has been completed - user already selected organization if they could
178
+ possible_teamspaces = menu._get_possible_teamspaces(self.user)
179
+ if len(possible_teamspaces) == 1:
180
+ # User didn't select any org
181
+ value = next(iter(possible_teamspaces.values()))
182
+ return Teamspace(name=value["name"], org=value["org"], user=value["user"])
183
+
184
+ for _, value in possible_teamspaces.items():
185
+ # User select an org
186
+ # Onboarding teamspace will be the default teamspace in the selected org
187
+ if value["org"]:
188
+ return Teamspace(name=value["name"], org=value["org"], user=value["user"])
189
+ raise RuntimeError("Unable to select teamspace. Visit lightning.ai")
@@ -0,0 +1,157 @@
1
+ import concurrent.futures
2
+ import os
3
+ import re
4
+ import webbrowser
5
+ from pathlib import Path
6
+ from threading import Thread
7
+ from typing import Dict, Optional
8
+
9
+ from rich.console import Console
10
+ from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
11
+ from rich.prompt import Confirm
12
+ from rich.syntax import Syntax
13
+
14
+ from lightning_sdk import Machine
15
+ from lightning_sdk.cli.deploy._auth import _AuthMode, _Onboarding, authenticate, poll_verified_status, select_teamspace
16
+ from lightning_sdk.cli.upload import (
17
+ _dump_current_upload_state,
18
+ _resolve_previous_upload_state,
19
+ _single_file_upload,
20
+ )
21
+ from lightning_sdk.lightning_cloud.openapi import V1CloudSpaceSourceType
22
+ from lightning_sdk.studio import Studio
23
+ from lightning_sdk.utils.resolve import _get_studio_url
24
+
25
+
26
+ # TODO: Move the rest of the devbox logic here
27
+ class _LitServeDevbox:
28
+ """Build LitServe API in a Studio."""
29
+
30
+ def resolve_previous_upload(self, studio: Studio, folder: str) -> Dict[str, str]:
31
+ remote_path = "."
32
+ pairs = {}
33
+ for root, _, files in os.walk(folder):
34
+ rel_root = os.path.relpath(root, folder)
35
+ for f in files:
36
+ pairs[os.path.join(root, f)] = os.path.join(remote_path, rel_root, f)
37
+ return _resolve_previous_upload_state(studio, remote_path, pairs)
38
+
39
+ def upload_folder(self, studio: Studio, folder: str, upload_state: Dict[str, str]) -> None:
40
+ with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
41
+ futures = []
42
+ for k, v in upload_state.items():
43
+ futures.append(
44
+ executor.submit(_single_file_upload, studio=studio, local_path=k, remote_path=v, progress_bar=False)
45
+ )
46
+ total_files = len(upload_state)
47
+
48
+ with Progress(
49
+ SpinnerColumn(),
50
+ TextColumn("[progress.description]{task.description}"),
51
+ TimeElapsedColumn(),
52
+ console=Console(),
53
+ transient=True,
54
+ ) as progress:
55
+ upload_task = progress.add_task(f"[cyan]Uploading {total_files} files to Studio...", total=total_files)
56
+ for f in concurrent.futures.as_completed(futures):
57
+ upload_state.pop(f.result())
58
+ _dump_current_upload_state(studio, ".", upload_state)
59
+ progress.update(upload_task, advance=1)
60
+
61
+ def _detect_port(self, script_path: Path) -> int:
62
+ with open(script_path) as f:
63
+ content = f.read()
64
+
65
+ # Try to match server.run first and then any variable name and then default port=8000
66
+ match = re.search(r"server\.run\s*\([^)]*port\s*=\s*(\d+)", content) or re.search(
67
+ r"\w+\.run\s*\([^)]*port\s*=\s*(\d+)", content
68
+ )
69
+ return int(match.group(1)) if match else 8000
70
+
71
+
72
+ def _handle_devbox(
73
+ name: str,
74
+ script_path: Path,
75
+ console: Console,
76
+ non_interactive: bool = False,
77
+ machine: Machine = Machine.CPU,
78
+ interruptible: bool = False,
79
+ teamspace: Optional[str] = None,
80
+ org: Optional[str] = None,
81
+ user: Optional[str] = None,
82
+ ) -> None:
83
+ if script_path.suffix != ".py":
84
+ console.print("❌ Error: Only Python files (.py) are supported for development servers", style="red")
85
+ return
86
+
87
+ from_onboarding = False
88
+ authenticate(_AuthMode.DEVBOX, shall_confirm=not non_interactive)
89
+ user_status = poll_verified_status()
90
+ if not user_status["verified"]:
91
+ console.print("❌ Verify phone number to continue. Visit lightning.ai.", style="red")
92
+ return
93
+ if not user_status["onboarded"]:
94
+ console.print("onboarding user")
95
+ onboarding = _Onboarding(console)
96
+ resolved_teamspace = onboarding.select_teamspace(teamspace, org, user)
97
+ from_onboarding = True
98
+ else:
99
+ resolved_teamspace = select_teamspace(teamspace, org, user)
100
+ studio = Studio(name=name, teamspace=resolved_teamspace, source=V1CloudSpaceSourceType.LITSERVE)
101
+ studio.install_plugin("custom-port")
102
+ lit_devbox = _LitServeDevbox()
103
+
104
+ studio_url = _get_studio_url(studio, turn_on=True)
105
+ pathlib_path = Path(script_path).resolve()
106
+ browser_opened = False
107
+ studio_path = f"{studio.owner.name}/{studio.teamspace.name}/{studio.name}"
108
+
109
+ console.print("\n=== Lightning Studio Setup ===")
110
+ console.print(f"🔧 [bold]Setting up Studio:[/bold] {studio_path}")
111
+ console.print(f"📁 [bold]Local project:[/bold] {pathlib_path.parent}")
112
+
113
+ upload_state = lit_devbox.resolve_previous_upload(studio, str(pathlib_path.parent))
114
+ if non_interactive:
115
+ console.print(f"🌐 [bold]Opening Studio:[/bold] [link={studio_url}]{studio_url}[/link]")
116
+ browser_opened = webbrowser.open(studio_url)
117
+ elif not from_onboarding:
118
+ if Confirm.ask("Would you like to open your Studio in the browser?", default=True):
119
+ console.print(f"🌐 [bold]Opening Studio:[/bold] [link={studio_url}]{studio_url}[/link]")
120
+ browser_opened = webbrowser.open(studio_url)
121
+
122
+ if not browser_opened:
123
+ console.print(f"🔗 [bold]Access Studio:[/bold] [link={studio_url}]{studio_url}[/link]")
124
+
125
+ # Start the Studio in the background and return immediately using threading
126
+ console.print("\n⚡ Initializing Studio in the background...")
127
+ studio_thread = Thread(target=studio.start, args=(machine, interruptible))
128
+ studio_thread.start()
129
+
130
+ console.print("📤 Syncing project files to Studio...")
131
+ lit_devbox.upload_folder(studio, pathlib_path.parent, upload_state)
132
+
133
+ # Wait for the Studio to start
134
+ console.print("⚡ Waiting for Studio to start...")
135
+ studio_thread.join()
136
+
137
+ try:
138
+ console.print("🚀 Starting server...")
139
+ studio.run_and_detach(f"python {script_path}", timeout=10)
140
+ except Exception as e:
141
+ console.print("❌ Error while starting server", style="red")
142
+ syntax = Syntax(f"{e}", "bash", theme="monokai")
143
+ console.print(syntax)
144
+ console.print(f"\n🔄 [bold]To fix:[/bold] Edit your code in Studio and run with: [u]python {script_path}[/u]")
145
+ return
146
+
147
+ port = lit_devbox._detect_port(pathlib_path)
148
+ console.print("🔌 Configuring server port...")
149
+ port_url = studio.run_plugin("custom-port", port=port)
150
+
151
+ # Add completion message with next steps
152
+ console.print("\n✅ Studio ready!")
153
+ console.print("\n📋 [bold]Next steps:[/bold]")
154
+ console.print(" [bold]1.[/bold] Server code will be available in the Studio")
155
+ console.print(" [bold]2.[/bold] The Studio is now running with the specified configuration")
156
+ console.print(" [bold]3.[/bold] Modify and run your server directly in the Studio")
157
+ console.print(f" [bold]4.[/bold] Your server will be accessible on [link={port_url}]{port_url}[/link]")