Skip to content

Commit

Permalink
Add progress output to neuro rm
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Skurikhin committed Aug 12, 2020
1 parent 0bd0d62 commit 6ed9521
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,7 @@ Name | Description|
|----|------------|
|_--help_|Show this message and exit.|
|_\--glob / --no-glob_|Expand glob patterns in PATHS \[default: True]|
|_\-p, --progress / -P, --no-progress_|Show progress, on by default in TTY mode, off otherwise.|
|_\-r, --recursive_|remove directories and their contents recursively|


Expand Down Expand Up @@ -2321,6 +2322,7 @@ Name | Description|
|----|------------|
|_--help_|Show this message and exit.|
|_\--glob / --no-glob_|Expand glob patterns in PATHS \[default: True]|
|_\-p, --progress / -P, --no-progress_|Show progress, on by default in TTY mode, off otherwise.|
|_\-r, --recursive_|remove directories and their contents recursively|


Expand Down
16 changes: 16 additions & 0 deletions neuromation/cli/formatters/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
from yarl import URL

from neuromation.api import (
AbstractDeleteProgress,
AbstractRecursiveFileProgress,
Action,
FileStatus,
FileStatusType,
StorageProgressComplete,
StorageProgressDelete,
StorageProgressEnterDir,
StorageProgressFail,
StorageProgressLeaveDir,
Expand Down Expand Up @@ -567,6 +569,20 @@ def key(self) -> Any:
# progress indicator


class DeleteProgress(AbstractDeleteProgress):
def __init__(self, root: Root) -> None:
self.painter = get_painter(root.color, quote=True)

def fmt_url(self, url: URL, type: FileStatusType) -> str:
return self.painter.paint(str(url), type)

def delete(self, data: StorageProgressDelete) -> None:
url_label = self.fmt_url(
data.uri, FileStatusType.DIRECTORY if data.is_dir else FileStatusType.FILE,
)
click.echo(f"Removed: {url_label}")


class BaseStorageProgress(AbstractRecursiveFileProgress):
@abc.abstractmethod
def begin(self, src: URL, dst: URL) -> None: # pragma: no cover
Expand Down
23 changes: 21 additions & 2 deletions neuromation/cli/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .const import EX_OSFILE
from .formatters.storage import (
BaseFilesFormatter,
DeleteProgress,
FilesSorter,
LongFilesFormatter,
SimpleFilesFormatter,
Expand Down Expand Up @@ -68,7 +69,20 @@ def storage() -> None:
show_default=True,
help="Expand glob patterns in PATHS",
)
async def rm(root: Root, paths: Sequence[str], recursive: bool, glob: bool) -> None:
@option(
"-p/-P",
"--progress/--no-progress",
is_flag=True,
default=None,
help="Show progress, on by default in TTY mode, off otherwise.",
)
async def rm(
root: Root,
paths: Sequence[str],
recursive: bool,
glob: bool,
progress: Optional[bool],
) -> None:
"""
Remove files or directories.
Expand All @@ -80,9 +94,14 @@ async def rm(root: Root, paths: Sequence[str], recursive: bool, glob: bool) -> N
neuro rm storage:foo/**/*.tmp
"""
errors = False
show_progress = root.tty if progress is None else progress

for uri in await _expand(paths, root, glob):
try:
await root.client.storage.rm(uri, recursive=recursive)
progress_obj = DeleteProgress(root) if show_progress else None
await root.client.storage.rm(
uri, recursive=recursive, progress=progress_obj
)
except (OSError, ResourceNotFound, IllegalArgumentError) as error:
log.error(f"cannot remove {uri}: {error}")
errors = True
Expand Down
54 changes: 10 additions & 44 deletions tests/api/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from filecmp import dircmp
from pathlib import Path
from shutil import copytree
from typing import Any, AsyncIterator, Callable, List, Mapping, Tuple
from typing import Any, AsyncIterator, Callable, List, Tuple
from unittest import mock

import pytest
Expand Down Expand Up @@ -401,36 +401,19 @@ async def glob(pattern: str) -> List[URL]:
assert await glob("storage:**/b*/") == [URL("storage:folder/bar/")]


def _fstat_to_remove_listing(fstat: Mapping[str, Any]) -> Mapping[str, str]:
return {
"path": fstat["FileStatus"]["path"],
"is_dir": fstat["FileStatus"]["type"] == "DIRECTORY",
}


async def test_storage_rm_file(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
file_status = {
"FileStatus": {
"path": "/user/file",
"type": "FILE",
"length": 1234,
"modificationTime": 3456,
"permission": "read",
}
}
remove_listing = {"path": "/user/file", "is_dir": False}

async def delete_handler(request: web.Request) -> web.Response:
async def delete_handler(request: web.Request) -> web.StreamResponse:
assert request.path == "/storage/user/file"
assert request.query == {"op": "DELETE", "recursive": "false"}
assert request.headers["Accept"] == "application/x-ndjson"
resp = web.StreamResponse()
resp.headers["Content-Type"] = "application/x-ndjson"
await resp.prepare(request)
await resp.write(
json.dumps(_fstat_to_remove_listing(file_status)).encode() + b"\n"
)
await resp.write(json.dumps(remove_listing).encode() + b"\n")
return resp

app = web.Application()
Expand All @@ -445,15 +428,7 @@ async def delete_handler(request: web.Request) -> web.Response:
async def test_storage_rm_file_progress(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
file_status = {
"FileStatus": {
"path": "/user/file",
"type": "FILE",
"length": 1234,
"modificationTime": 3456,
"permission": "read",
}
}
remove_listing = {"path": "/user/file", "is_dir": False}

async def delete_handler(request: web.Request) -> web.StreamResponse:
assert request.path == "/storage/user/file"
Expand All @@ -462,9 +437,7 @@ async def delete_handler(request: web.Request) -> web.StreamResponse:
resp = web.StreamResponse()
resp.headers["Content-Type"] = "application/x-ndjson"
await resp.prepare(request)
await resp.write(
json.dumps(_fstat_to_remove_listing(file_status)).encode() + b"\n"
)
await resp.write(json.dumps(remove_listing).encode() + b"\n")
return resp

app = web.Application()
Expand Down Expand Up @@ -506,14 +479,9 @@ async def delete_handler(request: web.Request) -> web.Response:
async def test_storage_rm_recursive(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
file_status = {
"FileStatus": {
"path": "/user/folder",
"type": "DIRECTORY",
"length": 1234,
"modificationTime": 3456,
"permission": "read",
}
remove_listing = {
"path": "/user/folder",
"is_dir": True,
}

async def delete_handler(request: web.Request) -> web.StreamResponse:
Expand All @@ -523,9 +491,7 @@ async def delete_handler(request: web.Request) -> web.StreamResponse:
resp = web.StreamResponse()
resp.headers["Content-Type"] = "application/x-ndjson"
await resp.prepare(request)
await resp.write(
json.dumps(_fstat_to_remove_listing(file_status)).encode() + b"\n"
)
await resp.write(json.dumps(remove_listing).encode() + b"\n")
return resp

app = web.Application()
Expand Down
16 changes: 16 additions & 0 deletions tests/cli/test_storage_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from neuromation.api import (
FileStatusType,
StorageProgressComplete,
StorageProgressDelete,
StorageProgressEnterDir,
StorageProgressFail,
StorageProgressLeaveDir,
Expand All @@ -18,6 +19,7 @@
)
from neuromation.cli.formatters.storage import (
BaseStorageProgress,
DeleteProgress,
StreamProgress,
TTYProgress,
create_storage_progress,
Expand Down Expand Up @@ -500,3 +502,17 @@ def test_tty_append_second_dir(make_root: _MakeRoot) -> None:
(URL("c"), True, "c"),
(URL("d"), False, "d"),
]


def test_delete_progress_no_color(capsys: Any, make_root: _MakeRoot) -> None:
report = DeleteProgress(make_root(False, False, False))
url_file = URL("storage:/abc/foo")
url_dir = URL("storage:/abc")

report.delete(StorageProgressDelete(url_file, is_dir=False))
captured = capsys.readouterr()
assert captured.out == f"Removed: '{url_file}'\n"

report.delete(StorageProgressDelete(url_dir, is_dir=True))
captured = capsys.readouterr()
assert captured.out == f"Removed: '{url_dir}'\n"

0 comments on commit 6ed9521

Please sign in to comment.