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.
- lightning_sdk/__init__.py +1 -1
- lightning_sdk/api/base_studio_api.py +7 -1
- lightning_sdk/api/cluster_api.py +83 -1
- lightning_sdk/api/llm_api.py +27 -5
- lightning_sdk/api/studio_api.py +64 -0
- lightning_sdk/api/teamspace_api.py +127 -1
- lightning_sdk/api/utils.py +4 -0
- lightning_sdk/base_studio.py +14 -1
- lightning_sdk/cli/create.py +21 -1
- lightning_sdk/cli/deploy/__init__.py +0 -0
- lightning_sdk/cli/deploy/_auth.py +189 -0
- lightning_sdk/cli/deploy/devbox.py +157 -0
- lightning_sdk/cli/{serve.py → deploy/serve.py} +11 -322
- lightning_sdk/cli/download.py +44 -16
- lightning_sdk/cli/entrypoint.py +1 -1
- lightning_sdk/cli/open.py +21 -2
- lightning_sdk/cli/start.py +12 -3
- lightning_sdk/cli/upload.py +2 -4
- lightning_sdk/lightning_cloud/openapi/__init__.py +19 -0
- lightning_sdk/lightning_cloud/openapi/api/assistants_service_api.py +126 -1
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +97 -0
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_service_api.py +105 -0
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +105 -0
- lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +752 -106
- lightning_sdk/lightning_cloud/openapi/api/storage_service_api.py +93 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +19 -0
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/cloudspaces_id_body.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/deployment_id_alertingpolicies_body.py +357 -0
- lightning_sdk/lightning_cloud/openapi/models/deployment_id_alertingpolicies_body1.py +331 -0
- lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/models_id_body.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/project_id_cloudspaces_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_source_type.py +103 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_tagging_options.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_data_connection.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_deployment_alerting_policy_response.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_event.py +487 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy.py +409 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_frequency.py +105 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_operation.py +105 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_severity.py +106 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_policy_type.py +111 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_alerting_recipients.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_ge_list_deployment_routing_telemetry_response.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_get_cloud_space_instance_open_ports_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_deployment_routing_telemetry_content_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_organization_storage_metadata_response.py +331 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_google_cloud_direct_v1.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_list_deployment_alerting_events_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_deployment_alerting_policies_response.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_membership.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +105 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project_membership.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +29 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_project_storage.py +53 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_routing_telemetry.py +253 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_server_alert_type.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_sleep_server_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +105 -53
- lightning_sdk/lightning_cloud/openapi/models/v1_user_requested_compute_config.py +27 -1
- lightning_sdk/llm/llm.py +54 -8
- lightning_sdk/studio.py +40 -1
- lightning_sdk/teamspace.py +68 -0
- {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/RECORD +80 -58
- {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.15.dist-info → lightning_sdk-0.2.17.dist-info}/entry_points.txt +0 -0
- {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]")
|