11import logging
22import os
3+ import sys
4+ from argparse import ArgumentParser
35from pathlib import Path
46from typing import List , Tuple , Union
7+ from uuid import uuid4
58
69import click
10+ import requests
711from requests .exceptions import ConnectionError
812
913from lightning_app import __version__ as ver
1014from lightning_app .cli import cmd_init , cmd_install , cmd_pl_init , cmd_react_ui_init
1115from lightning_app .core .constants import get_lightning_cloud_url , LOCAL_LAUNCH_ADMIN_VIEW
1216from lightning_app .runners .runtime import dispatch
1317from lightning_app .runners .runtime_type import RuntimeType
14- from lightning_app .utilities .cli_helpers import _format_input_env_variables
18+ from lightning_app .utilities .cli_helpers import (
19+ _format_input_env_variables ,
20+ _retrieve_application_url_and_available_commands ,
21+ )
1522from lightning_app .utilities .install_components import register_all_external_components
1623from lightning_app .utilities .login import Auth
24+ from lightning_app .utilities .state import headers_for
1725
1826logger = logging .getLogger (__name__ )
1927
@@ -26,14 +34,23 @@ def get_app_url(runtime_type: RuntimeType, *args) -> str:
2634 return "http://127.0.0.1:7501/admin" if LOCAL_LAUNCH_ADMIN_VIEW else "http://127.0.0.1:7501/view"
2735
2836
37+ def main ():
38+ if len (sys .argv ) == 1 :
39+ _main ()
40+ elif sys .argv [1 ] in _main .commands .keys () or sys .argv [1 ] == "--help" :
41+ _main ()
42+ else :
43+ app_command ()
44+
45+
2946@click .group ()
3047@click .version_option (ver )
31- def main ():
48+ def _main ():
3249 register_all_external_components ()
3350 pass
3451
3552
36- @main .command ()
53+ @_main .command ()
3754def login ():
3855 """Log in to your Lightning.ai account."""
3956 auth = Auth ()
@@ -46,7 +63,7 @@ def login():
4663 exit (1 )
4764
4865
49- @main .command ()
66+ @_main .command ()
5067def logout ():
5168 """Log out of your Lightning.ai account."""
5269 Auth ().clear ()
@@ -93,7 +110,7 @@ def on_before_run(*args):
93110 click .echo ("Application is ready in the cloud" )
94111
95112
96- @main .group ()
113+ @_main .group ()
97114def run ():
98115 """Run your application."""
99116
@@ -125,31 +142,83 @@ def run_app(
125142 _run_app (file , cloud , without_server , no_cache , name , blocking , open_ui , env )
126143
127144
128- @main .group (hidden = True )
145+ def app_command ():
146+ """Execute a function in a running application from its name."""
147+ from lightning_app .utilities .commands .base import _download_command
148+
149+ logger .warn ("Lightning Commands are a beta feature and APIs aren't stable yet." )
150+
151+ debug_mode = bool (int (os .getenv ("DEBUG" , "0" )))
152+
153+ parser = ArgumentParser ()
154+ parser .add_argument ("--app_id" , default = None , type = str , help = "Optional argument to identify an application." )
155+ hparams , argv = parser .parse_known_args ()
156+
157+ # 1: Collect the url and comments from the running application
158+ url , commands = _retrieve_application_url_and_available_commands (hparams .app_id )
159+ if url is None or commands is None :
160+ raise Exception ("We couldn't find any matching running app." )
161+
162+ if not commands :
163+ raise Exception ("This application doesn't expose any commands yet." )
164+
165+ command = argv [0 ]
166+
167+ command_names = [c ["command" ] for c in commands ]
168+ if command not in command_names :
169+ raise Exception (f"The provided command { command } isn't available in { command_names } " )
170+
171+ # 2: Send the command from the user
172+ command_metadata = [c for c in commands if c ["command" ] == command ][0 ]
173+ params = command_metadata ["params" ]
174+
175+ # 3: Execute the command
176+ if not command_metadata ["is_client_command" ]:
177+ # TODO: Improve what is supported there.
178+ kwargs = {k .split ("=" )[0 ].replace ("--" , "" ): k .split ("=" )[1 ] for k in argv [1 :]}
179+ for param in params :
180+ if param not in kwargs :
181+ raise Exception (f"The argument --{ param } =X hasn't been provided." )
182+ json = {
183+ "command_name" : command ,
184+ "command_arguments" : kwargs ,
185+ "affiliation" : command_metadata ["affiliation" ],
186+ "id" : str (uuid4 ()),
187+ }
188+ resp = requests .post (url + "/api/v1/commands" , json = json , headers = headers_for ({}))
189+ assert resp .status_code == 200 , resp .json ()
190+ else :
191+ client_command , models = _download_command (command_metadata , hparams .app_id , debug_mode = debug_mode )
192+ client_command ._setup (metadata = command_metadata , models = models , app_url = url )
193+ sys .argv = argv
194+ client_command .run ()
195+
196+
197+ @_main .group (hidden = True )
129198def fork ():
130199 """Fork an application."""
131200 pass
132201
133202
134- @main .group (hidden = True )
203+ @_main .group (hidden = True )
135204def stop ():
136205 """Stop your application."""
137206 pass
138207
139208
140- @main .group (hidden = True )
209+ @_main .group (hidden = True )
141210def delete ():
142211 """Delete an application."""
143212 pass
144213
145214
146- @main .group (name = "list" , hidden = True )
215+ @_main .group (name = "list" , hidden = True )
147216def get_list ():
148217 """List your applications."""
149218 pass
150219
151220
152- @main .group ()
221+ @_main .group ()
153222def install ():
154223 """Install Lightning apps and components."""
155224
@@ -207,7 +276,7 @@ def install_component(name, yes, version):
207276 cmd_install .gallery_component (name , yes , version )
208277
209278
210- @main .group ()
279+ @_main .group ()
211280def init ():
212281 """Init a Lightning app and component."""
213282
0 commit comments