Skip to content

Commit e54f784

Browse files
tchatonBorda
authored andcommitted
[App] Enable running an app from the Gallery (#15941)
Co-authored-by: thomas <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ethan Harris <[email protected]> Co-authored-by: Jirka <[email protected]> (cherry picked from commit 4983083)
1 parent e2e383a commit e54f784

File tree

5 files changed

+210
-114
lines changed

5 files changed

+210
-114
lines changed

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ files = [
3030
"src/lightning_lite",
3131
"src/lightning_app",
3232
]
33+
# This section is for folders with "-" as they are not valid python modules
3334
exclude = [
35+
"src/lightning_app/cli/app-template",
3436
"src/lightning_app/cli/component-template",
3537
"src/lightning_app/cli/pl-app-template",
3638
"src/lightning_app/cli/react-ui-template",
37-
"src/lightning_app/cli/app-template",
38-
"src/lightning_app/components/database",
39-
"src/lightning_app/components/multi_node",
40-
"src/lightning_app/frontend/just_py/just_py",
4139
]
4240
install_types = "True"
4341
non_interactive = "True"
@@ -67,7 +65,9 @@ module = [
6765
"lightning_app.api.request_types",
6866
"lightning_app.cli.commands.app_commands",
6967
"lightning_app.cli.commands.connection",
70-
"lightning_app.cli.react-ui-template.example_app",
68+
"lightning_app.cli.commands.lightning_cli",
69+
"lightning_app.cli.commands.cmd_install",
70+
"lightning_app.cli.cmd_install",
7171
"lightning_app.components.database.client",
7272
"lightning_app.components.database.server",
7373
"lightning_app.components.database.utilities",

src/lightning_app/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
1717
- Added the property `ready` of the LightningFlow to inform when the `Open App` should be visible ([#15921](https://github.com/Lightning-AI/lightning/pull/15921))
1818
- Added private work attributed `_start_method` to customize how to start the works ([#15923](https://github.com/Lightning-AI/lightning/pull/15923))
1919
- Added a `configure_layout` method to the `LightningWork` which can be used to control how the work is handled in the layout of a parent flow ([#15926](https://github.com/Lightning-AI/lightning/pull/15926))
20+
- Added the ability to run a Lightning App or Component directly from the Gallery using `lightning run app organization/name` ([#15941](https://github.com/Lightning-AI/lightning/pull/15941))
2021
- Added automatic conversion of list and dict of works and flows to structures ([#15961](https://github.com/Lightning-AI/lightning/pull/15961))
2122

2223
### Changed

src/lightning_app/cli/cmd_install.py

Lines changed: 164 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
from typing import Dict, Optional, Tuple
77

8+
import click
89
import requests
910
from packaging.version import Version
1011

@@ -14,7 +15,117 @@
1415
logger = Logger(__name__)
1516

1617

17-
def gallery_component(name: str, yes_arg: bool, version_arg: str, cwd: str = None) -> None:
18+
@click.group(name="install")
19+
def install() -> None:
20+
"""Install Lightning AI selfresources."""
21+
pass
22+
23+
24+
@install.command("app")
25+
@click.argument("name", type=str)
26+
@click.option(
27+
"--yes",
28+
"-y",
29+
is_flag=True,
30+
help="disables prompt to ask permission to create env and run install cmds",
31+
)
32+
@click.option(
33+
"--version",
34+
"-v",
35+
type=str,
36+
help="Specify the version to install. By default it uses 'latest'",
37+
default="latest",
38+
show_default=True,
39+
)
40+
@click.option(
41+
"--overwrite",
42+
"-f",
43+
is_flag=True,
44+
default=False,
45+
help="When set, overwrite the app directory without asking if it already exists.",
46+
)
47+
def install_app(name: str, yes: bool, version: str, overwrite: bool = False) -> None:
48+
_install_app_command(name, yes, version, overwrite=overwrite)
49+
50+
51+
@install.command("component")
52+
@click.argument("name", type=str)
53+
@click.option(
54+
"--yes",
55+
"-y",
56+
is_flag=True,
57+
help="disables prompt to ask permission to create env and run install cmds",
58+
)
59+
@click.option(
60+
"--version",
61+
"-v",
62+
type=str,
63+
help="Specify the version to install. By default it uses 'latest'",
64+
default="latest",
65+
show_default=True,
66+
)
67+
def install_component(name: str, yes: bool, version: str) -> None:
68+
_install_component_command(name, yes, version)
69+
70+
71+
def _install_app_command(name: str, yes: bool, version: str, overwrite: bool = False) -> None:
72+
if "github.com" in name:
73+
if version != "latest":
74+
logger.warn(
75+
"When installing from GitHub, only the 'latest' version is supported. "
76+
f"The provided version ({version}) will be ignored."
77+
)
78+
return non_gallery_app(name, yes, overwrite=overwrite)
79+
else:
80+
return gallery_app(name, yes, version, overwrite=overwrite)
81+
82+
83+
def _install_component_command(name: str, yes: bool, version: str, overwrite: bool = False) -> None:
84+
if "github.com" in name:
85+
if version != "latest":
86+
logger.warn(
87+
"When installing from GitHub, only the 'latest' version is supported. "
88+
f"The provided version ({version}) will be ignored."
89+
)
90+
return non_gallery_component(name, yes)
91+
else:
92+
return gallery_component(name, yes, version)
93+
94+
95+
def gallery_apps_and_components(
96+
name: str, yes_arg: bool, version_arg: str, cwd: str = None, overwrite: bool = False
97+
) -> Optional[str]:
98+
99+
try:
100+
org, app_or_component = name.split("/")
101+
except Exception:
102+
return None
103+
104+
entry, kind = _resolve_entry(app_or_component, version_arg)
105+
106+
if kind == "app":
107+
# give the user the chance to do a manual install
108+
source_url, git_url, folder_name, git_sha = _show_install_app_prompt(
109+
entry, app_or_component, org, yes_arg, resource_type="app"
110+
)
111+
# run installation if requested
112+
_install_app_from_source(source_url, git_url, folder_name, cwd=cwd, overwrite=overwrite, git_sha=git_sha)
113+
114+
return os.path.join(os.getcwd(), folder_name, entry["appEntrypointFile"])
115+
116+
elif kind == "component":
117+
# give the user the chance to do a manual install
118+
git_url = _show_install_component_prompt(entry, app_or_component, org, yes_arg)
119+
120+
# run installation if requested
121+
_install_component_from_source(git_url)
122+
123+
return os.path.join(os.getcwd(), entry["appEntrypointFile"])
124+
125+
return None
126+
127+
128+
def gallery_component(name: str, yes_arg: bool, version_arg: str, cwd: str = None) -> str:
18129
# make sure org/component-name name is correct
19130
org, component = _validate_name(name, resource_type="component", example="lightning/LAI-slack-component")
20131

@@ -28,7 +139,9 @@ def gallery_component(name: str, yes_arg: bool, version_arg: str, cwd: str = Non
28139
git_url = _show_install_component_prompt(component_entry, component, org, yes_arg)
29140

30141
# run installation if requested
31-
_install_component(git_url)
142+
_install_component_from_source(git_url)
143+
144+
return os.path.join(os.getcwd(), component_entry["entrypointFile"])
32145

33146

34147
def non_gallery_component(gh_url: str, yes_arg: bool, cwd: str = None) -> None:
@@ -37,10 +150,10 @@ def non_gallery_component(gh_url: str, yes_arg: bool, cwd: str = None) -> None:
37150
git_url = _show_non_gallery_install_component_prompt(gh_url, yes_arg)
38151

39152
# run installation if requested
40-
_install_component(git_url)
153+
_install_component_from_source(git_url)
41154

42155

43-
def gallery_app(name: str, yes_arg: bool, version_arg: str, cwd: str = None, overwrite: bool = False) -> None:
156+
def gallery_app(name: str, yes_arg: bool, version_arg: str, cwd: str = None, overwrite: bool = False) -> str:
44157

45158
# make sure org/app-name syntax is correct
46159
org, app = _validate_name(name, resource_type="app", example="lightning/quick-start")
@@ -57,7 +170,9 @@ def gallery_app(name: str, yes_arg: bool, version_arg: str, cwd: str = None, ove
57170
)
58171

59172
# run installation if requested
60-
_install_app(source_url, git_url, folder_name, cwd=cwd, overwrite=overwrite, git_sha=git_sha)
173+
_install_app_from_source(source_url, git_url, folder_name, cwd=cwd, overwrite=overwrite, git_sha=git_sha)
174+
175+
return os.path.join(os.getcwd(), folder_name, app_entry["appEntrypointFile"])
61176

62177

63178
def non_gallery_app(gh_url: str, yes_arg: bool, cwd: str = None, overwrite: bool = False) -> None:
@@ -66,7 +181,7 @@ def non_gallery_app(gh_url: str, yes_arg: bool, cwd: str = None, overwrite: bool
66181
repo_url, folder_name = _show_non_gallery_install_app_prompt(gh_url, yes_arg)
67182

68183
# run installation if requested
69-
_install_app(repo_url, repo_url, folder_name, cwd=cwd, overwrite=overwrite)
184+
_install_app_from_source(repo_url, repo_url, folder_name, cwd=cwd, overwrite=overwrite)
70185

71186

72187
def _show_install_component_prompt(entry: Dict[str, str], component: str, org: str, yes_arg: bool) -> str:
@@ -299,7 +414,35 @@ def _validate_name(name: str, resource_type: str, example: str) -> Tuple[str, st
299414
return org, resource
300415

301416

302-
def _resolve_resource(registry_url: str, name: str, version_arg: str, resource_type: str) -> Dict[str, str]:
417+
def _resolve_entry(name, version_arg) -> Tuple[Optional[Dict], Optional[str]]:
418+
entry = None
419+
kind = None
420+
421+
# resolve registry (orgs can have a private registry through their environment variables)
422+
registry_url = _resolve_app_registry()
423+
424+
# load the app resource
425+
entry = _resolve_resource(registry_url, name=name, version_arg=version_arg, resource_type="app", raise_error=False)
426+
427+
if not entry:
428+
429+
registry_url = _resolve_component_registry()
430+
431+
# load the component resource
432+
entry = _resolve_resource(
433+
registry_url, name=name, version_arg=version_arg, resource_type="component", raise_error=False
434+
)
435+
kind = "component" if entry else None
436+
437+
else:
438+
kind = "app"
439+
440+
return entry, kind
441+
442+
443+
def _resolve_resource(
444+
registry_url: str, name: str, version_arg: str, resource_type: str, raise_error: bool = True
445+
) -> Dict[str, str]:
303446
gallery_entries = []
304447
try:
305448
response = requests.get(registry_url)
@@ -327,7 +470,10 @@ def _resolve_resource(registry_url: str, name: str, version_arg: str, resource_t
327470
all_versions.append(x["version"])
328471

329472
if len(entries) == 0:
330-
raise SystemExit(f"{resource_type}: '{name}' is not available on ⚡ Lightning AI ⚡")
473+
if raise_error:
474+
raise SystemExit(f"{resource_type}: '{name}' is not available on ⚡ Lightning AI ⚡")
475+
else:
476+
return None
331477

332478
entry = None
333479
if version_arg == "latest":
@@ -337,11 +483,14 @@ def _resolve_resource(registry_url: str, name: str, version_arg: str, resource_t
337483
if e["version"] == version_arg:
338484
entry = e
339485
break
340-
if entry is None:
341-
raise Exception(
342-
f"{resource_type}: 'Version {version_arg} for {name}' is not available on ⚡ Lightning AI ⚡. "
343-
f"Here is the list of all availables versions:{os.linesep}{os.linesep.join(all_versions)}"
344-
)
486+
if entry is None and raise_error:
487+
if raise_error:
488+
raise Exception(
489+
f"{resource_type}: 'Version {version_arg} for {name}' is not available on ⚡ Lightning AI ⚡. "
490+
f"Here is the list of all availables versions:{os.linesep}{os.linesep.join(all_versions)}"
491+
)
492+
else:
493+
return None
345494

346495
return entry
347496

@@ -381,7 +530,7 @@ def _install_with_env(repo_url: str, folder_name: str, cwd: str = None) -> None:
381530
logger.info(m)
382531

383532

384-
def _install_app(
533+
def _install_app_from_source(
385534
source_url: str, git_url: str, folder_name: str, cwd: str = None, overwrite: bool = False, git_sha: str = None
386535
) -> None:
387536
"""Installing lighting app from the `git_url`
@@ -458,7 +607,7 @@ def _install_app(
458607
logger.info(m)
459608

460609

461-
def _install_component(git_url: str) -> None:
610+
def _install_component_from_source(git_url: str) -> None:
462611
logger.info("⚡ RUN: pip install")
463612

464613
out = subprocess.check_output(["pip", "install", git_url])

0 commit comments

Comments
 (0)