8
8
import click
9
9
import psutil
10
10
from lightning_utilities .core .imports import package_available
11
+ from rich .progress import Progress
11
12
12
13
from lightning_app .utilities .cli_helpers import _LightningAppOpenAPIRetriever
13
14
from lightning_app .utilities .cloud import _get_project
16
17
from lightning_app .utilities .network import LightningClient
17
18
18
19
_HOME = os .path .expanduser ("~" )
19
- _PPID = str (psutil .Process (os .getpid ()).ppid ())
20
+ _PPID = os . getenv ( "LIGHTNING_CONNECT_PPID" , str (psutil .Process (os .getpid ()).ppid () ))
20
21
_LIGHTNING_CONNECTION = os .path .join (_HOME , ".lightning" , "lightning_connection" )
21
22
_LIGHTNING_CONNECTION_FOLDER = os .path .join (_LIGHTNING_CONNECTION , _PPID )
22
23
23
24
24
25
@click .argument ("app_name_or_id" , required = True )
25
- @click .option ("-y" , "--yes" , required = False , is_flag = True , help = "Whether to download the commands automatically." )
26
- def connect (app_name_or_id : str , yes : bool = False ):
27
- """Connect to a Lightning App."""
26
+ def connect (app_name_or_id : str ):
27
+ """Connect your local terminal to a running lightning app.
28
+
29
+ After connecting, the lightning CLI will respond to commands exposed by the app.
30
+
31
+ Example:
32
+
33
+ \b
34
+ # connect to an app named pizza-cooker-123
35
+ lightning connect pizza-cooker-123
36
+ \b
37
+ # this will now show the commands exposed by pizza-cooker-123
38
+ lightning --help
39
+ \b
40
+ # while connected, you can run the cook-pizza command exposed
41
+ # by pizza-cooker-123.BTW, this should arguably generate an exception :-)
42
+ lightning cook-pizza --flavor pineapple
43
+ \b
44
+ # once done, disconnect and go back to the standard lightning CLI commands
45
+ lightning disconnect
46
+ """
28
47
from lightning_app .utilities .commands .base import _download_command
29
48
30
49
_clean_lightning_connection ()
@@ -47,51 +66,64 @@ def connect(app_name_or_id: str, yes: bool = False):
47
66
click .echo (f"You are already connected to the cloud Lightning App: { app_name_or_id } ." )
48
67
else :
49
68
disconnect ()
50
- connect (app_name_or_id , yes )
69
+ connect (app_name_or_id )
51
70
52
71
elif app_name_or_id .startswith ("localhost" ):
53
72
54
- if app_name_or_id != "localhost" :
55
- raise Exception ( "You need to pass localhost to connect to the local Lightning App." )
73
+ with Progress () as progress_bar :
74
+ connecting = progress_bar . add_task ( "[magenta]Setting things up for you..." , total = 1.0 )
56
75
57
- retriever = _LightningAppOpenAPIRetriever (None )
76
+ if app_name_or_id != "localhost" :
77
+ raise Exception ("You need to pass localhost to connect to the local Lightning App." )
58
78
59
- if retriever .api_commands is None :
60
- raise Exception (f"The commands weren't found. Is your app { app_name_or_id } running ?" )
79
+ retriever = _LightningAppOpenAPIRetriever (None )
61
80
62
- commands_folder = os .path .join (_LIGHTNING_CONNECTION_FOLDER , "commands" )
63
- if not os .path .exists (commands_folder ):
64
- os .makedirs (commands_folder )
81
+ if retriever .api_commands is None :
82
+ raise Exception (f"Connection wasn't successful. Is your app { app_name_or_id } running?" )
65
83
66
- _write_commands_metadata ( retriever .api_commands )
84
+ increment = 1 / ( 1 + len ( retriever .api_commands ) )
67
85
68
- with open (os .path .join (commands_folder , "openapi.json" ), "w" ) as f :
69
- json .dump (retriever .openapi , f )
86
+ progress_bar .update (connecting , advance = increment )
70
87
71
- _install_missing_requirements (retriever , yes )
88
+ commands_folder = os .path .join (_LIGHTNING_CONNECTION_FOLDER , "commands" )
89
+ if not os .path .exists (commands_folder ):
90
+ os .makedirs (commands_folder )
72
91
73
- for command_name , metadata in retriever .api_commands .items ():
74
- if "cls_path" in metadata :
75
- target_file = os .path .join (commands_folder , f"{ command_name .replace (' ' ,'_' )} .py" )
76
- _download_command (
77
- command_name ,
78
- metadata ["cls_path" ],
79
- metadata ["cls_name" ],
80
- None ,
81
- target_file = target_file ,
82
- )
83
- repr_command_name = command_name .replace ("_" , " " )
84
- click .echo (f"Storing `{ repr_command_name } ` at { target_file } " )
85
- else :
86
- with open (os .path .join (commands_folder , f"{ command_name } .txt" ), "w" ) as f :
87
- f .write (command_name )
92
+ _write_commands_metadata (retriever .api_commands )
93
+
94
+ with open (os .path .join (commands_folder , "openapi.json" ), "w" ) as f :
95
+ json .dump (retriever .openapi , f )
88
96
89
- click .echo (f"You can review all the downloaded commands at { commands_folder } " )
97
+ _install_missing_requirements (retriever )
98
+
99
+ for command_name , metadata in retriever .api_commands .items ():
100
+ if "cls_path" in metadata :
101
+ target_file = os .path .join (commands_folder , f"{ command_name .replace (' ' ,'_' )} .py" )
102
+ _download_command (
103
+ command_name ,
104
+ metadata ["cls_path" ],
105
+ metadata ["cls_name" ],
106
+ None ,
107
+ target_file = target_file ,
108
+ )
109
+ else :
110
+ with open (os .path .join (commands_folder , f"{ command_name } .txt" ), "w" ) as f :
111
+ f .write (command_name )
112
+
113
+ progress_bar .update (connecting , advance = increment )
90
114
91
115
with open (connected_file , "w" ) as f :
92
116
f .write (app_name_or_id + "\n " )
93
117
94
- click .echo ("You are connected to the local Lightning App." )
118
+ click .echo ("The lightning CLI now responds to app commands. Use 'lightning --help' to see them." )
119
+ click .echo (" " )
120
+
121
+ Popen (
122
+ f"LIGHTNING_CONNECT_PPID={ _PPID } { sys .executable } -m lightning --help" ,
123
+ shell = True ,
124
+ stdout = sys .stdout ,
125
+ stderr = sys .stderr ,
126
+ ).wait ()
95
127
96
128
elif matched_connection_path :
97
129
@@ -101,40 +133,39 @@ def connect(app_name_or_id: str, yes: bool = False):
101
133
commands = os .path .join (_LIGHTNING_CONNECTION_FOLDER , "commands" )
102
134
shutil .copytree (matched_commands , commands )
103
135
shutil .copy (matched_connected_file , connected_file )
104
- copied_files = [el for el in os .listdir (commands ) if os .path .splitext (el )[1 ] == ".py" ]
105
- click .echo ("Found existing connection, reusing cached commands" )
106
- for target_file in copied_files :
107
- pretty_command_name = os .path .splitext (target_file )[0 ].replace ("_" , " " )
108
- click .echo (f"Storing `{ pretty_command_name } ` at { os .path .join (commands , target_file )} " )
109
136
110
- click .echo (f"You can review all the commands at { commands } " )
137
+ click .echo ("The lightning CLI now responds to app commands. Use 'lightning --help' to see them. " )
111
138
click .echo (" " )
112
- click .echo (f"You are connected to the cloud Lightning App: { app_name_or_id } ." )
113
139
114
- else :
140
+ Popen (
141
+ f"LIGHTNING_CONNECT_PPID={ _PPID } { sys .executable } -m lightning --help" ,
142
+ shell = True ,
143
+ stdout = sys .stdout ,
144
+ stderr = sys .stderr ,
145
+ ).wait ()
115
146
116
- retriever = _LightningAppOpenAPIRetriever (app_name_or_id )
147
+ else :
148
+ with Progress () as progress_bar :
149
+ connecting = progress_bar .add_task ("[magenta]Setting things up for you..." , total = 1.0 )
150
+
151
+ retriever = _LightningAppOpenAPIRetriever (app_name_or_id )
152
+
153
+ if not retriever .api_commands :
154
+ client = LightningClient ()
155
+ project = _get_project (client )
156
+ apps = client .lightningapp_instance_service_list_lightningapp_instances (project_id = project .project_id )
157
+ click .echo (
158
+ "We didn't find a matching App. Here are the available Apps that you can"
159
+ f"connect to { [app .name for app in apps .lightningapps ]} ."
160
+ )
161
+ return
117
162
118
- if not retriever .api_commands :
119
- client = LightningClient ()
120
- project = _get_project (client )
121
- apps = client .lightningapp_instance_service_list_lightningapp_instances (project_id = project .project_id )
122
- click .echo (
123
- "We didn't find a matching App. Here are the available Apps that could be "
124
- f"connected to { [app .name for app in apps .lightningapps ]} ."
125
- )
126
- return
163
+ increment = 1 / (1 + len (retriever .api_commands ))
127
164
128
- _install_missing_requirements ( retriever , yes )
165
+ progress_bar . update ( connecting , advance = increment )
129
166
130
- if not yes :
131
- yes = click .confirm (
132
- f"The Lightning App `{ app_name_or_id } ` provides a command-line (CLI). "
133
- "Do you want to proceed and install its CLI ?"
134
- )
135
- click .echo (" " )
167
+ _install_missing_requirements (retriever )
136
168
137
- if yes :
138
169
commands_folder = os .path .join (_LIGHTNING_CONNECTION_FOLDER , "commands" )
139
170
if not os .path .exists (commands_folder ):
140
171
os .makedirs (commands_folder )
@@ -151,26 +182,25 @@ def connect(app_name_or_id: str, yes: bool = False):
151
182
retriever .app_id ,
152
183
target_file = target_file ,
153
184
)
154
- pretty_command_name = command_name .replace ("_" , " " )
155
- click .echo (f"Storing `{ pretty_command_name } ` at { target_file } " )
156
185
else :
157
186
with open (os .path .join (commands_folder , f"{ command_name } .txt" ), "w" ) as f :
158
187
f .write (command_name )
159
188
160
- click .echo (f"You can review all the downloaded commands at { commands_folder } " )
161
-
162
- click .echo (" " )
163
- click .echo ("The client interface has been successfully installed. " )
164
- click .echo ("You can now run the following commands:" )
165
- for command in retriever .api_commands :
166
- pretty_command_name = command .replace ("_" , " " )
167
- click .echo (f" lightning { pretty_command_name } " )
189
+ progress_bar .update (connecting , advance = increment )
168
190
169
191
with open (connected_file , "w" ) as f :
170
192
f .write (retriever .app_name + "\n " )
171
193
f .write (retriever .app_id + "\n " )
194
+
195
+ click .echo ("The lightning CLI now responds to app commands. Use 'lightning --help' to see them." )
172
196
click .echo (" " )
173
- click .echo (f"You are connected to the cloud Lightning App: { app_name_or_id } ." )
197
+
198
+ Popen (
199
+ f"LIGHTNING_CONNECT_PPID={ _PPID } { sys .executable } -m lightning --help" ,
200
+ shell = True ,
201
+ stdout = sys .stdout ,
202
+ stderr = sys .stderr ,
203
+ ).wait ()
174
204
175
205
176
206
def disconnect (logout : bool = False ):
@@ -244,22 +274,37 @@ def _list_app_commands(echo: bool = True) -> List[str]:
244
274
click .echo ("The current Lightning App doesn't have commands." )
245
275
return []
246
276
277
+ app_info = metadata [command_names [0 ]].get ("app_info" , None )
278
+
279
+ title , description , on_connect_end = "Lightning" , None , None
280
+ if app_info :
281
+ title = app_info .get ("title" )
282
+ description = app_info .get ("description" )
283
+ on_connect_end = app_info .get ("on_connect_end" )
284
+
247
285
if echo :
248
- click .echo ("Usage: lightning [OPTIONS] COMMAND [ARGS]..." )
249
- click .echo ("" )
250
- click .echo (" --help Show this message and exit." )
286
+ click .echo (f"{ title } App" )
287
+ if description :
288
+ click .echo ("" )
289
+ click .echo ("Description:" )
290
+ if description .endswith ("\n " ):
291
+ description = description [:- 2 ]
292
+ click .echo (f" { description } " )
251
293
click .echo ("" )
252
- click .echo ("Lightning App Commands" )
294
+ click .echo ("Commands: " )
253
295
max_length = max (len (n ) for n in command_names )
254
296
for command_name in command_names :
255
297
padding = (max_length + 1 - len (command_name )) * " "
256
298
click .echo (f" { command_name } { padding } { metadata [command_name ].get ('description' , '' )} " )
299
+ if "LIGHTNING_CONNECT_PPID" in os .environ and on_connect_end :
300
+ if on_connect_end .endswith ("\n " ):
301
+ on_connect_end = on_connect_end [:- 2 ]
302
+ click .echo (on_connect_end )
257
303
return command_names
258
304
259
305
260
306
def _install_missing_requirements (
261
307
retriever : _LightningAppOpenAPIRetriever ,
262
- yes_global : bool = False ,
263
308
fail_if_missing : bool = False ,
264
309
):
265
310
requirements = set ()
@@ -281,20 +326,15 @@ def _install_missing_requirements(
281
326
sys .exit (0 )
282
327
283
328
for req in missing_requirements :
284
- if not yes_global :
285
- yes = click .confirm (
286
- f"The Lightning App CLI `{ retriever .app_id } ` requires `{ req } `. Do you want to install it ?"
287
- )
288
- else :
289
- print (f"Installing missing `{ req } ` requirement." )
290
- yes = yes_global
291
- if yes :
292
- std_out_out = get_logfile ("output.log" )
293
- with open (std_out_out , "wb" ) as stdout :
294
- Popen (
295
- f"{ sys .executable } -m pip install { req } " , shell = True , stdout = stdout , stderr = sys .stderr
296
- ).wait ()
297
- print ()
329
+ std_out_out = get_logfile ("output.log" )
330
+ with open (std_out_out , "wb" ) as stdout :
331
+ Popen (
332
+ f"{ sys .executable } -m pip install { req } " ,
333
+ shell = True ,
334
+ stdout = stdout ,
335
+ stderr = stdout ,
336
+ ).wait ()
337
+ os .remove (std_out_out )
298
338
299
339
300
340
def _clean_lightning_connection ():
0 commit comments