From c4db72bef7a5d312d7653d5b6a69d298942c2e52 Mon Sep 17 00:00:00 2001 From: Piotr Kaznowski Date: Fri, 9 Apr 2021 19:53:36 +0200 Subject: [PATCH] #29 updates docs for cli path commands; minor formatting --- cli/path.py | 63 +++++++++++++++++++-------------- pythonanywhere/api/files_api.py | 2 +- tests/test_cli_path.py | 34 +++++++++--------- tests/test_files.py | 8 ++--- 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/cli/path.py b/cli/path.py index 7b536bb..194f4a3 100644 --- a/cli/path.py +++ b/cli/path.py @@ -22,18 +22,19 @@ def setup(path: str, quiet: bool) -> Tuple[str, PAPath]: @app.command() def get( - path: str = typer.Argument(..., help="Path to PythonAnywhere file or directory"), - only_files: bool = typer.Option(False, "-f", "--files", help="List only files"), - only_dirs: bool = typer.Option(False, "-d", "--dirs", help="List only directories"), - sort_by_type: bool = typer.Option(False, "-t", "--type", help="Sort by type"), - sort_reverse: bool = typer.Option(False, "-r", "--reverse", help="Sort in reverse order"), + path: str = typer.Argument(..., help="Path to PythonAnywhere file or directory."), + only_files: bool = typer.Option(False, "-f", "--files", help="List only files."), + only_dirs: bool = typer.Option(False, "-d", "--dirs", help="List only directories."), + sort_by_type: bool = typer.Option(False, "-t", "--type", help="Sort by type."), + sort_reverse: bool = typer.Option(False, "-r", "--reverse", help="Sort in reverse order."), raw: bool = typer.Option( - False, "-a", "--raw", help="Print API response (if PATH is file that's the only option)" + False, "-a", "--raw", help="Print API response (has effect only for directories)." ), - quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging"), + quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging."), ): """ Get contents of PATH. + If PATH points to a directory, show list of it's contents. If PATH points to a file, print it's contents. """ @@ -73,7 +74,7 @@ def _format_tree(data, current): for entry in reversed(data): entry = re.sub(r"/$", "\0", entry.replace(current, "")) - chunks = [cc for cc in entry.split('/') if cc] + chunks = [cc for cc in entry.split("/") if cc] item = chunks[-1].replace("\0", "/") level = len(chunks) - 1 level_tracker = set([lvl for lvl in level_tracker if lvl <= level]) @@ -87,9 +88,10 @@ def _format_tree(data, current): @app.command() def tree( - path: str = typer.Argument(..., help="Path to PythonAnywhere file or directory"), - quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging") + path: str = typer.Argument(..., help="Path to PythonAnywhere directory."), + quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging.") ): + """Show preview of directory contents at PATH in tree-like format (2 levels deep).""" pa_path = setup(path, quiet) tree = pa_path.tree @@ -103,21 +105,20 @@ def tree( @app.command() def upload( - path: str = typer.Argument( - ..., - help=( - "Full path of FILE where CONTENTS should be uploaded to -- " - "Warning: if FILE already exists, it's contents will be overwritten" - ) - ), + path: str = typer.Argument(..., help=("Full path of FILE where CONTENTS should be uploaded to.")), file: typer.FileBinaryRead = typer.Option( ..., "-c", "--contents", - help="Path to exisitng file or stdin stream that should be uploaded to PATH" + help="Path to exisitng file or stdin stream that should be uploaded to PATH." ), - quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging") + quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging.") ): + """ + Upload CONTENTS to file at PATH. + + If PATH points to an existing file, it will be overwritten. + """ pa_path = setup(path, quiet) success = pa_path.upload(file) sys.exit(0 if success else 1) @@ -125,9 +126,15 @@ def upload( @app.command() def delete( - path: str = typer.Argument(..., help="Path to PythonAnywhere file or directory to be deleted"), - quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging") + path: str = typer.Argument(..., help="Path to PythonAnywhere file or directory to be deleted."), + quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging.") ): + """ + Delete file or directory at PATH. + + If PATH points to a user owned directory all its contents will be + deleted recursively. + """ pa_path = setup(path, quiet) success = pa_path.delete() sys.exit(0 if success else 1) @@ -135,11 +142,12 @@ def delete( @app.command() def share( - path: str = typer.Argument(..., help="Path to PythonAnywhere file to be shared"), - check: bool = typer.Option(False, "-c", "--check", help="Check sharing status"), - porcelain: bool = typer.Option(False, "-p", "--porcelain", help="Return sharing url in easy-to-parse format"), - quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable logging"), + path: str = typer.Argument(..., help="Path to PythonAnywhere file."), + check: bool = typer.Option(False, "-c", "--check", help="Check sharing status."), + porcelain: bool = typer.Option(False, "-p", "--porcelain", help="Return sharing url in easy-to-parse format."), + quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable logging."), ): + """Create a sharing link to a file at PATH or check its sharing status.""" pa_path = setup(path, quiet or porcelain) link = pa_path.get_sharing_url() if check else pa_path.share() @@ -151,9 +159,10 @@ def share( @app.command() def unshare( - path: str = typer.Argument(..., help="Path to PythonAnywhere file to be unshared"), - quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging") + path: str = typer.Argument(..., help="Path to PythonAnywhere file."), + quiet: bool = typer.Option(False, "-q", "--quiet", help="Disable additional logging.") ): + """Disable sharing link for a file at PATH.""" pa_path = setup(path, quiet) success = pa_path.unshare() sys.exit(0 if success else 1) diff --git a/pythonanywhere/api/files_api.py b/pythonanywhere/api/files_api.py index b22db1a..320e27a 100644 --- a/pythonanywhere/api/files_api.py +++ b/pythonanywhere/api/files_api.py @@ -122,7 +122,7 @@ def sharing_post(self, path): url = self.sharing_endpoint - result = call_api(url, "POST", json={'path': path}) + result = call_api(url, "POST", json={"path": path}) if result.ok: return result.status_code, result.json()["url"] diff --git a/tests/test_cli_path.py b/tests/test_cli_path.py index 750387e..8da9457 100644 --- a/tests/test_cli_path.py +++ b/tests/test_cli_path.py @@ -45,7 +45,7 @@ class TestGet: def test_exits_early_when_no_contents_for_given_path(self, mock_path): mock_path.return_value.contents = None - result = runner.invoke(app, ["get", '~/nonexistent.file']) + result = runner.invoke(app, ["get", "~/nonexistent.file"]) assert result.exit_code == 1 @@ -67,9 +67,9 @@ def test_lists_only_directories_when_dirs_option_set(self, mock_homedir_path, ho assert result.stdout.startswith(home_dir) for item, value in mock_homedir_path.return_value.contents.items(): - if value['type'] == 'file': + if value["type"] == "file": assert item not in result.stdout - elif value['type'] == 'directory': + elif value["type"] == "directory": assert item in result.stdout def test_lists_only_files_when_files_option_set(self, mock_homedir_path, home_dir): @@ -79,9 +79,9 @@ def test_lists_only_files_when_files_option_set(self, mock_homedir_path, home_di assert result.stdout.startswith(home_dir) for item, value in mock_homedir_path.return_value.contents.items(): - if value['type'] == 'file': + if value["type"] == "file": assert item in result.stdout - elif value['type'] == 'directory': + elif value["type"] == "directory": assert item not in result.stdout def test_reverses_directory_content_list_when_reverse_option_set(self, mock_homedir_path, home_dir): @@ -132,18 +132,18 @@ class TestTree: def test_prints_formatted_tree_when_successfull_api_call(self, mock_path, home_dir): mock_path.return_value.path = home_dir mock_path.return_value.tree = [ - f'{home_dir}/README.txt', - f'{home_dir}/dir_one/', - f'{home_dir}/dir_one/bar.txt', - f'{home_dir}/dir_one/nested_one/', - f'{home_dir}/dir_one/nested_one/foo.txt', - f'{home_dir}/dir_one/nested_two/', - f'{home_dir}/empty/', - f'{home_dir}/dir_two/', - f'{home_dir}/dir_two/quux', - f'{home_dir}/dir_two/baz/', - f'{home_dir}/dir_three/', - f'{home_dir}/dir_three/last.txt', + f"{home_dir}/README.txt", + f"{home_dir}/dir_one/", + f"{home_dir}/dir_one/bar.txt", + f"{home_dir}/dir_one/nested_one/", + f"{home_dir}/dir_one/nested_one/foo.txt", + f"{home_dir}/dir_one/nested_two/", + f"{home_dir}/empty/", + f"{home_dir}/dir_two/", + f"{home_dir}/dir_two/quux", + f"{home_dir}/dir_two/baz/", + f"{home_dir}/dir_three/", + f"{home_dir}/dir_three/last.txt", ] result = runner.invoke(app, ["tree", "~"]) diff --git a/tests/test_files.py b/tests/test_files.py index 3991d55..33b5776 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -32,11 +32,11 @@ def test_repr_returns_url_property_value(self, mocker): def test_make_sharing_url_contains_pa_site_address(self, mocker): mock_urljoin = mocker.patch("pythonanywhere.files.urljoin") - pa_path = PAPath('path') + pa_path = PAPath("path") - pa_path._make_sharing_url('rest') + pa_path._make_sharing_url("rest") - assert mock_urljoin.call_args == call(pa_path.api.base_url.split("api")[0], 'rest') + assert mock_urljoin.call_args == call(pa_path.api.base_url.split("api")[0], "rest") def test_sanitizes_path(self): pa_path = PAPath("~") @@ -70,7 +70,7 @@ def test_warns_when_path_unavailable(self, mocker): mock_snake = mocker.patch("pythonanywhere.files.snakesay") mock_warning = mocker.patch("pythonanywhere.files.logger.warning") - result = PAPath('/home/different_user').contents + result = PAPath("/home/different_user").contents assert mock_snake.call_args == call("error msg") assert mock_warning.call_args == call(mock_snake.return_value)