Skip to content
Merged
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
984421c
update
Feb 1, 2023
25bf9f6
update
Feb 1, 2023
258c9ec
Merge branch 'master' of https://github.com/Lightning-AI/lightning in…
Feb 1, 2023
5ca87d0
update
Feb 1, 2023
24b86d1
update
Feb 1, 2023
7fe89be
update
Feb 1, 2023
cc1bcb7
update
Feb 1, 2023
2c70775
update
Feb 1, 2023
34c7796
update
Feb 1, 2023
00e47c1
update
Feb 1, 2023
7ec12c6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
e3d5e60
Merge branch 'master' into storage_commands
tchaton Feb 2, 2023
29c1789
update
Feb 2, 2023
914b59a
update
Feb 2, 2023
af67e93
update
Feb 2, 2023
16b6069
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
22ca6d2
update
Feb 2, 2023
22380a7
update
Feb 2, 2023
752744b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
b053e27
update
Feb 2, 2023
a7c8199
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
f45eb1a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
079fb7e
update
Feb 2, 2023
7c05441
update
Feb 2, 2023
3941b0c
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
27d33b0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
701f153
update
Feb 2, 2023
8d88889
update
Feb 2, 2023
f7700c7
update
Feb 2, 2023
51ddaa9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
b4fcda3
update
Feb 2, 2023
fc66745
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
76f5569
Merge branch 'master' into storage_commands
Feb 2, 2023
ca3b3d6
update
Feb 2, 2023
8a893b7
update
Feb 2, 2023
28f7abd
update
Feb 2, 2023
6be410c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
c5d8d4b
update
Feb 2, 2023
c9ce764
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
a576374
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
432ad24
update
Feb 2, 2023
1f2e572
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
3d97325
update
Feb 2, 2023
2eae315
update
Feb 2, 2023
51120cb
update
Feb 2, 2023
1068f1f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
830417a
update
Feb 2, 2023
dd63a1d
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
015967a
Merge branch 'master' into storage_commands
tchaton Feb 2, 2023
02ffea9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
e91ffb5
update
Feb 2, 2023
8a00448
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
af69b9d
update
Feb 2, 2023
fd3f0a4
update
Feb 2, 2023
743e248
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
51f263e
update
Feb 2, 2023
1da2f37
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
86f70bb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
5029206
update
Feb 2, 2023
5f3cbeb
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
3fb9ba8
update
Feb 2, 2023
33698e8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
b538141
update
Feb 2, 2023
d453fe8
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
a9fed6f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
89314ad
update
Feb 2, 2023
9857564
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
e69927d
update
Feb 2, 2023
6982c0e
update
Feb 2, 2023
45a479c
update
Feb 2, 2023
98268b8
Merge branch 'master' into storage_commands
tchaton Feb 2, 2023
1daf6a8
update
Feb 2, 2023
22704f3
Merge branch 'storage_commands' of https://github.com/Lightning-AI/li…
Feb 2, 2023
b3b4998
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
87d51e4
update
Feb 2, 2023
c91f260
update
Feb 2, 2023
35b7247
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 2, 2023
7756438
update
Feb 2, 2023
b922bc4
update
Feb 2, 2023
63cf5cb
update
Feb 2, 2023
7abe820
update
Feb 2, 2023
d5c9f58
update
Feb 3, 2023
eda9560
update
Feb 3, 2023
44b260f
update
Feb 3, 2023
e208346
update
Feb 3, 2023
e6f7dde
update
Feb 3, 2023
287dd64
update
Feb 3, 2023
0c37ff0
update
Feb 3, 2023
54d6948
Merge branch 'master' into storage_commands_optimised
tchaton Feb 3, 2023
ca8d332
update
Feb 3, 2023
3b96d30
Merge branch 'storage_commands_optimised' of https://github.com/Light…
Feb 3, 2023
9b611cb
update
Feb 3, 2023
e326078
update
Feb 3, 2023
bd4f6bc
Update src/lightning/app/CHANGELOG.md
tchaton Feb 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lightning/app/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Added FileSystem abstraction to simply manipulation of files ([#16581](https://github.com/Lightning-AI/lightning/pull/16581))

- Added Storage Commands ([#16606](https://github.com/Lightning-AI/lightning/pull/16606))
- Enable `ls` and `cp` (download) at project level ([#16622](https://github.com/Lightning-AI/lightning/pull/16622))
* `ls`: List files from your Cloud Platform Filesystem
* `cd`: Change the current directory within your Cloud Platform filesystem (terminal session based)
* `pwd`: Return the current folder in your Cloud Platform Filesystem
Expand Down
2 changes: 0 additions & 2 deletions src/lightning/app/cli/commands/cd.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ def cd(path: Optional[Union[Tuple[str], str]]) -> None:

root = "/"

live.stop()

if isinstance(path, Tuple) and len(path) > 0:
path = " ".join(path)

Expand Down
59 changes: 44 additions & 15 deletions src/lightning/app/cli/commands/cp.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@
from functools import partial
from multiprocessing.pool import ApplyResult
from pathlib import Path
from time import sleep
from typing import Optional, Tuple
from typing import Optional, Tuple, Union

import click
import requests
import rich
import urllib3
from lightning_cloud.openapi import IdArtifactsBody
from lightning_cloud.openapi import Externalv1LightningappInstance, IdArtifactsBody, V1CloudSpace
from rich.live import Live
from rich.progress import BarColumn, DownloadColumn, Progress, Task, TextColumn
from rich.spinner import Spinner
from rich.text import Text

from lightning.app.cli.commands.ls import _collect_artifacts, _get_prefix
from lightning.app.cli.commands.pwd import _pwd
from lightning.app.source_code import FileUploader
from lightning.app.utilities.app_helpers import Logger
Expand Down Expand Up @@ -59,8 +59,8 @@ def cp(src_path: str, dst_path: str, r: bool = False, recursive: bool = False) -

client = LightningClient()

src_path, src_remote = _sanetize_path(src_path, pwd)
dst_path, dst_remote = _sanetize_path(dst_path, pwd)
src_path, src_remote = _sanitize_path(src_path, pwd)
dst_path, dst_remote = _sanitize_path(dst_path, pwd)

if src_remote and dst_remote:
return _error_and_exit("Moving files remotely isn't supported yet. Please, open a Github issue.")
Expand Down Expand Up @@ -103,9 +103,6 @@ def _upload_files(live, client: LightningClient, local_src: str, remote_dst: str

live.stop()

# Sleep to avoid rich live collision.
sleep(1)

progress = _get_progress_bar()

total_size = sum([Path(path).stat().st_size for path in upload_paths])
Expand Down Expand Up @@ -140,14 +137,15 @@ def _upload(source_file: str, presigned_url: ApplyResult, progress: Progress, ta


def _download_files(live, client, remote_src: str, local_dst: str, pwd: str):
project_id, app_id = _get_project_app_ids(pwd)
project_id, lit_resource = _get_project_id_and_resource(pwd)

download_paths = []
download_urls = []
total_size = []

response = client.lightningapp_instance_service_list_lightningapp_instance_artifacts(project_id, app_id)
for artifact in response.artifacts:
prefix = _get_prefix("/".join(pwd.split("/")[3:]), lit_resource)

for artifact in _collect_artifacts(client, project_id, prefix, include_download_url=True):
path = os.path.join(local_dst, artifact.filename.replace(remote_src, ""))
path = Path(path).resolve()
os.makedirs(path.parent, exist_ok=True)
Expand All @@ -157,14 +155,15 @@ def _download_files(live, client, remote_src: str, local_dst: str, pwd: str):

live.stop()

# Sleep to avoid rich live collision.
sleep(1)
if not download_paths:
print("There were no files to download.")
return

progress = progress = _get_progress_bar()

progress.start()

task_id = progress.add_task("download", filename=path, total=sum(total_size))
task_id = progress.add_task("download", filename="", total=sum(total_size))

_download_file_fn = partial(_download_file, progress=progress, task_id=task_id)

Expand Down Expand Up @@ -193,7 +192,7 @@ def _download_file(path: str, url: str, progress: Progress, task_id: Task) -> No
progress.update(task_id, advance=len(chunk))


def _sanetize_path(path: str, pwd: str) -> Tuple[str, bool]:
def _sanitize_path(path: str, pwd: str) -> Tuple[str, bool]:
is_remote = _is_remote(path)
if is_remote:
path = _remove_remote(path)
Expand All @@ -217,6 +216,7 @@ def _error_and_exit(msg: str) -> str:
sys.exit(0)


# TODO: To be removed when upload is supported for CloudSpaces.
def _get_project_app_ids(pwd: str) -> Tuple[str, str]:
"""Convert a root path to a project id and app id."""
# TODO: Handle project level
Expand All @@ -234,6 +234,35 @@ def _get_project_app_ids(pwd: str) -> Tuple[str, str]:
return project_id, lit_app.id


def _get_project_id_and_resource(pwd: str) -> Tuple[str, Union[Externalv1LightningappInstance, V1CloudSpace]]:
"""Convert a root path to a project id and app id."""
# TODO: Handle project level
project_name, resource_name, *_ = pwd.split("/")[1:3]

# 1. Collect the projects of the user
client = LightningClient()
projects = client.projects_service_list_memberships()
project_id = [project.project_id for project in projects.memberships if project.name == project_name][0]

# 2. Collect resources
lit_apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id).lightningapps

lit_cloud_spaces = client.cloud_space_service_list_cloud_spaces(project_id=project_id).cloudspaces

lit_ressources = [lit_resource for lit_resource in lit_cloud_spaces if lit_resource.name == resource_name]

if len(lit_ressources) == 0:

lit_ressources = [lit_resource for lit_resource in lit_apps if lit_resource.name == resource_name]

if len(lit_ressources) == 0:

print(f"ERROR: There isn't any Lightning Ressource matching the name {resource_name}.")
sys.exit(0)

return project_id, lit_ressources[0]


def _get_progress_bar():
return Progress(
TextColumn("[bold blue]{task.description}", justify="left"),
Expand Down
141 changes: 106 additions & 35 deletions src/lightning/app/cli/commands/ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import click
import rich
from lightning_cloud.openapi import Externalv1LightningappInstance
from rich.console import Console
from rich.live import Live
from rich.spinner import Spinner
Expand All @@ -44,9 +45,7 @@ def ls(path: Optional[str] = None) -> List[str]:

root = "/"

with Live(Spinner("point", text=Text("pending...", style="white")), transient=True) as live:

live.stop()
with Live(Spinner("point", text=Text("pending...", style="white")), transient=True):

if not os.path.exists(_LIGHTNING_CONNECTION_FOLDER):
os.makedirs(_LIGHTNING_CONNECTION_FOLDER)
Expand Down Expand Up @@ -76,45 +75,74 @@ def ls(path: Optional[str] = None) -> List[str]:

lit_apps = client.lightningapp_instance_service_list_lightningapp_instances(project_id=project_id).lightningapps

lit_cloud_spaces = client.cloud_space_service_list_cloud_spaces(project_id=project_id).cloudspaces

if len(splits) == 1:
app_names = sorted([lit_app.name for lit_app in lit_apps])
_print_names_with_colors(app_names, [_FOLDER_COLOR] * len(app_names))
return app_names
apps = [lit_app.name for lit_app in lit_apps]
cloud_spaces = [lit_cloud_space.name for lit_cloud_space in lit_cloud_spaces]
ressource_names = sorted(set(cloud_spaces + apps))
_print_names_with_colors(ressource_names, [_FOLDER_COLOR] * len(ressource_names))
return ressource_names

lit_ressources = [lit_resource for lit_resource in lit_cloud_spaces if lit_resource.name == splits[1]]

if len(lit_ressources) == 0:

lit_apps = [lit_app for lit_app in lit_apps if lit_app.name == splits[1]]
lit_ressources = [lit_resource for lit_resource in lit_apps if lit_resource.name == splits[1]]

if len(lit_apps) != 1:
print(f"ERROR: There isn't any Lightning App matching the name {splits[1]}.")
sys.exit(0)
if len(lit_ressources) == 0:

lit_app = lit_apps[0]
print(f"ERROR: There isn't any Lightning Ressource matching the name {splits[1]}.")
sys.exit(0)

lit_resource = lit_ressources[0]

app_paths = []
app_colors = []

cloud_spaces_paths = []
cloud_spaces_colors = []

paths = []
colors = []
depth = len(splits)
subpath = "/".join(splits[2:])
# TODO: Replace with project level endpoints
for artifact in _collect_artifacts(client, project_id, lit_app.id):
path = os.path.join(project_id, lit_app.name, artifact.filename)

prefix = "/".join(splits[2:])
prefix = _get_prefix(prefix, lit_resource)

for artifact in _collect_artifacts(client=client, project_id=project_id, prefix=prefix):

if str(artifact.filename).startswith("/"):
artifact.filename = artifact.filename[1:]

path = os.path.join(project_id, prefix[1:], artifact.filename)

artifact_splits = path.split("/")

if len(artifact_splits) < depth + 1:
if len(artifact_splits) <= depth + 1:
continue

if not str(artifact.filename).startswith(subpath):
continue
path = artifact_splits[depth + 1]

path = artifact_splits[depth]
paths = app_paths if isinstance(lit_resource, Externalv1LightningappInstance) else cloud_spaces_paths
colors = app_colors if isinstance(lit_resource, Externalv1LightningappInstance) else cloud_spaces_colors

if path not in paths:
paths.append(path)

# display files otherwise folders
colors.append(_FILE_COLOR if len(artifact_splits) == depth + 1 else _FOLDER_COLOR)

_print_names_with_colors(paths, colors)
if app_paths and cloud_spaces_paths:
if app_paths:
rich.print("Lightning App")
_print_names_with_colors(app_paths, app_colors)

if cloud_spaces_paths:
rich.print("Lightning CloudSpaces")
_print_names_with_colors(cloud_spaces_paths, cloud_spaces_colors)
else:
_print_names_with_colors(app_paths + cloud_spaces_paths, app_colors + cloud_spaces_colors)

return paths
return app_paths + cloud_spaces_paths


def _add_colors(filename: str, color: Optional[str] = None) -> str:
Expand Down Expand Up @@ -156,21 +184,64 @@ def _print_names_with_colors(names: List[str], colors: List[str], padding: int =
def _collect_artifacts(
client: LightningClient,
project_id: str,
app_id: str,
prefix: str = "",
page_token: Optional[str] = "",
cluster_id: Optional[str] = None,
page_size: int = 100_000,
tokens=None,
include_download_url: bool = False,
) -> Generator:
if tokens is None:
tokens = []

if page_token in tokens:
return

response = client.lightningapp_instance_service_list_lightningapp_instance_artifacts(
project_id, app_id, page_token=page_token
)
yield from response.artifacts

if response.next_page_token != "":
tokens.append(page_token)
yield from _collect_artifacts(client, project_id, app_id, page_token=response.next_page_token, tokens=tokens)
if cluster_id is None:
clusters = client.projects_service_list_project_cluster_bindings(project_id)
for cluster in clusters.clusters:
yield from _collect_artifacts(
client,
project_id,
prefix=prefix,
cluster_id=cluster.cluster_id,
page_token=page_token,
tokens=tokens,
page_size=page_size,
include_download_url=include_download_url,
)
else:

if page_token in tokens:
return

response = client.lightningapp_instance_service_list_project_artifacts(
project_id,
prefix=prefix,
cluster_id=cluster_id,
page_token=page_token,
include_download_url=include_download_url,
page_size=str(page_size),
)
yield from response.artifacts

if response.next_page_token != "":
tokens.append(page_token)
yield from _collect_artifacts(
client,
project_id,
prefix=prefix,
cluster_id=cluster_id,
page_token=response.next_page_token,
tokens=tokens,
)


def _add_resource_prefix(prefix: str, resource_path: str):
if resource_path in prefix:
return prefix
return "/" + os.path.join(resource_path, prefix)


def _get_prefix(prefix: str, lit_resource) -> str:
if isinstance(lit_resource, Externalv1LightningappInstance):
return _add_resource_prefix(prefix, f"lightningapps/{lit_resource.id}")

return _add_resource_prefix(prefix, f"cloudspaces/{lit_resource.id}")
6 changes: 5 additions & 1 deletion tests/tests_app/cli/test_cp.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def test_cp_cloud_to_local(tmpdir, monkeypatch):
memberships=[V1Membership(name="project-0")]
)

clusters = MagicMock()
clusters.clusters = [MagicMock()]
client.projects_service_list_project_cluster_bindings.return_value = clusters

client.lightningapp_instance_service_list_lightningapp_instances.return_value = V1ListLightningappInstancesResponse(
lightningapps=[
Externalv1LightningappInstance(
Expand All @@ -80,7 +84,7 @@ def test_cp_cloud_to_local(tmpdir, monkeypatch):
]
)

client.lightningapp_instance_service_list_lightningapp_instance_artifacts.return_value = (
client.lightningapp_instance_service_list_project_artifacts.return_value = (
V1ListLightningappInstanceArtifactsResponse(
artifacts=[
V1LightningappInstanceArtifact(
Expand Down
Loading