From f466508feb785496db45592b777bd95f117f5206 Mon Sep 17 00:00:00 2001 From: Piotr Kaznowski Date: Sat, 16 Jan 2021 22:54:58 +0100 Subject: [PATCH] #29 covers sharing interface for files, adds return values to upload and delete as well --- pythonanywhere/files.py | 34 +++++++++- pythonanywhere/files.pyi | 7 +- tests/test_files.py | 143 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 172 insertions(+), 12 deletions(-) diff --git a/pythonanywhere/files.py b/pythonanywhere/files.py index 44c53f6..c525b85 100644 --- a/pythonanywhere/files.py +++ b/pythonanywhere/files.py @@ -30,15 +30,17 @@ def delete(self): try: self.api.path_delete(self.path) logger.info(snakesay(f"{self.path} deleted!")) + return True except Exception as e: logger.warning(snakesay(str(e))) + return False def upload(self, content): try: result = self.api.path_post(self.path, content) except Exception as e: logger.warning(snakesay(str(e))) - return None + return False msg = { 200: f"{self.path} successfully updated!", @@ -46,12 +48,38 @@ def upload(self, content): }[result] logger.info(snakesay(msg)) + return True + + def get_sharing_url(self): + url = self.api.sharing_get(self.path) + if url: + logger.info(snakesay(f"{self.path} is shared at {url}")) + return url + logger.info(snakesay(f"{self.path} has not been shared.")) + return "" def share(self): - pass + try: + code, shared_url = self.api.sharing_post(self.path) + except Exception as e: + logger.warning(snakesay(str(e))) + return "" + + msg = {200: "was already", 201: "successfully"}[code] + logger.info(snakesay(f"{self.path} {msg} shared at {shared_url}")) + return shared_url def unshare(self): - pass + already_shared = self.get_sharing_url() + if already_shared: + result = self.api.sharing_delete(self.path) + if result == 204: + logger.info(snakesay(f"{self.path} is no longer shared!")) + return True + logger.info(snakesay(f"Could not unshare {self.path}... :(")) + return False + logger.info(snakesay(f"{self.path} is not being shared, no need to stop sharing...")) + return True def tree(self): pass diff --git a/pythonanywhere/files.pyi b/pythonanywhere/files.pyi index 1091114..58932a3 100644 --- a/pythonanywhere/files.pyi +++ b/pythonanywhere/files.pyi @@ -6,5 +6,8 @@ class PAPath: def __init__(self, path: str) -> None: ... def __repr__(self) -> str: ... def contents(self) -> Union[dict, str]: ... - def delete(self) -> None: ... - def upload(self, content: bytes) -> None: ... + def delete(self) -> bool: ... + def upload(self, content: bytes) -> bool: ... + def get_sharing_url(self) -> str: ... + def share(self) -> str: ... + def unshare(self) -> bool: ... diff --git a/tests/test_files.py b/tests/test_files.py index 4ebe854..b054319 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -10,7 +10,7 @@ @pytest.mark.files -class TestPathRepr(TestFiles): +class TestPAPathRepr(TestFiles): def test_contains_correct_pythonanywhere_resource_url_for_instantiated_path(self): path = self.home_dir_path @@ -19,7 +19,7 @@ def test_contains_correct_pythonanywhere_resource_url_for_instantiated_path(self @pytest.mark.files -class TestPathContents(TestFiles): +class TestPAPathContents(TestFiles): def test_returns_file_contents_as_string_if_path_points_to_a_file(self, mocker): path = f"{self.home_dir_path}README.txt" mock_path_get = mocker.patch("pythonanywhere.api.files_api.Files.path_get") @@ -49,7 +49,7 @@ def test_raises_when_path_unavailable(self, mocker): @pytest.mark.files -class TestPathDelete(TestFiles): +class TestPAPathDelete(TestFiles): def test_informes_about_successful_file_deletion(self, mocker): mock_delete = mocker.patch("pythonanywhere.api.files_api.Files.path_delete") mock_delete.return_value.status_code = 204 @@ -76,7 +76,7 @@ def test_warns_about_failed_deletion(self, mocker): @pytest.mark.files -class TestPathUpload(TestFiles): +class TestPAPathUpload(TestFiles): def test_informs_about_successful_upload_of_a_file(self, mocker): mock_post = mocker.patch("pythonanywhere.api.files_api.Files.path_post") mock_post.return_value = 201 @@ -85,11 +85,12 @@ def test_informs_about_successful_upload_of_a_file(self, mocker): destination_path = "/home/user/" content = "content".encode() - PAPath(destination_path).upload(content) + result = PAPath(destination_path).upload(content) assert mock_post.call_args == call(destination_path, content) assert mock_snake.call_args == call(f"Content successfully uploaded to {destination_path}!") assert mock_info.call_args == call(mock_snake.return_value) + assert result == True def test_informs_about_successful_update_of_existing_file_with_provided_stream(self, mocker): mock_post = mocker.patch("pythonanywhere.api.files_api.Files.path_post") @@ -99,11 +100,12 @@ def test_informs_about_successful_update_of_existing_file_with_provided_stream(s destination_path = "/home/user/" content = "content".encode() - PAPath(destination_path).upload(content) + result = PAPath(destination_path).upload(content) assert mock_post.call_args == call(destination_path, content) assert mock_snake.call_args == call(f"{destination_path} successfully updated!") assert mock_info.call_args == call(mock_snake.return_value) + assert result == True def test_warns_when_file_has_not_been_uploaded(self, mocker): @@ -115,8 +117,135 @@ def test_warns_when_file_has_not_been_uploaded(self, mocker): destination_path = "wrong/path" content = "content".encode() - PAPath(destination_path).upload(content) + result = PAPath(destination_path).upload(content) assert mock_post.call_args == call(destination_path, content) assert mock_snake.call_args == call("sth went wrong") assert mock_warning.call_args == call(mock_snake.return_value) + assert result == False + + +@pytest.mark.files +class TestPAPathShare(TestFiles): + def test_returns_url_for_shared_file(self, mocker): + mock_sharing_get = mocker.patch("pythonanywhere.api.files_api.Files.sharing_get") + mock_sharing_get.return_value = "url" + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + query_path = "/pa/path/to/a/file" + + result = PAPath(query_path).get_sharing_url() + + assert mock_sharing_get.call_args == call(query_path) + assert mock_snake.call_args == call(f"{query_path} is shared at url") + assert mock_info.call_args == call(mock_snake.return_value) + assert result == "url" + + def test_returns_empty_string_when_file_not_shared(self, mocker): + mock_sharing_get = mocker.patch("pythonanywhere.api.files_api.Files.sharing_get") + mock_sharing_get.return_value = "" + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + query_path = "/pa/path/to/a/file" + + result = PAPath(query_path).get_sharing_url() + + assert mock_sharing_get.call_args == call(query_path) + assert mock_snake.call_args == call(f"{query_path} has not been shared.") + assert mock_info.call_args == call(mock_snake.return_value) + assert result == "" + + def test_path_already_shared(self, mocker): + mock_sharing_post = mocker.patch("pythonanywhere.api.files_api.Files.sharing_post") + mock_sharing_post.return_value = (200, "url") + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + path_to_share = "/pa/path/to/a/file" + + result = PAPath(path_to_share).share() + + assert mock_sharing_post.call_args == call(path_to_share) + assert mock_snake.call_args == call(f"{path_to_share} was already shared at url") + assert mock_info.call_args == call(mock_snake.return_value) + assert result == "url" + + def test_path_successfully_shared(self, mocker): + mock_sharing_post = mocker.patch("pythonanywhere.api.files_api.Files.sharing_post") + mock_sharing_post.return_value = (201, "url") + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + path_to_share = "/pa/path/to/a/file" + + result = PAPath(path_to_share).share() + + assert mock_sharing_post.call_args == call(path_to_share) + assert mock_snake.call_args == call(f"{path_to_share} successfully shared at url") + assert mock_info.call_args == call(mock_snake.return_value) + assert result == "url" + + def test_warns_if_share_fails(self, mocker): + mock_sharing_post = mocker.patch("pythonanywhere.api.files_api.Files.sharing_post") + mock_sharing_post.side_effect = Exception("failed") + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_warning = mocker.patch("pythonanywhere.files.logger.warning") + path_to_share = "/invalid/path" + + result = PAPath(path_to_share).share() + + assert mock_sharing_post.call_args == call(path_to_share) + assert mock_snake.call_args == call("failed") + assert mock_warning.call_args == call(mock_snake.return_value) + assert result == "" + + def test_path_is_not_shared_so_cannot_be_unshared(self, mocker): + mock_sharing_get = mocker.patch("pythonanywhere.api.files_api.Files.sharing_get") + mock_sharing_get.return_value = "" + mock_sharing_delete = mocker.patch("pythonanywhere.api.files_api.Files.sharing_delete") + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + path_to_unshare = "/pa/path/to/a/file" + + result = PAPath(path_to_unshare).unshare() + + assert mock_sharing_get.call_args == call(path_to_unshare) + assert mock_sharing_delete.call_count == 0 + assert mock_snake.call_args == call( + f"{path_to_unshare} is not being shared, no need to stop sharing..." + ) + assert mock_info.call_args == call(mock_snake.return_value) + assert result == True + + def test_path_successfully_unshared(self, mocker): + mock_sharing_get = mocker.patch("pythonanywhere.api.files_api.Files.sharing_get") + mock_sharing_get.return_value = "url" + mock_sharing_delete = mocker.patch("pythonanywhere.api.files_api.Files.sharing_delete") + mock_sharing_delete.return_value = 204 + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + path_to_shared_file = "/pa/path/to/a/file" + + result = PAPath(path_to_shared_file).unshare() + + assert mock_sharing_get.call_args == call(path_to_shared_file) + assert mock_sharing_delete.call_args == call(path_to_shared_file) + assert mock_snake.call_args == call(f"{path_to_shared_file} is no longer shared!") + assert mock_info.call_args == call(mock_snake.return_value) + assert result == True + + def test_warns_if_unshare_not_successful(self, mocker): + mock_sharing_get = mocker.patch("pythonanywhere.api.files_api.Files.sharing_get") + mock_sharing_get.return_value = "url" + mock_sharing_delete = mocker.patch("pythonanywhere.api.files_api.Files.sharing_delete") + mock_sharing_delete.return_value = 999 + mock_snake = mocker.patch("pythonanywhere.files.snakesay") + mock_info = mocker.patch("pythonanywhere.files.logger.info") + path_to_shared_file = "/pa/path/to/a/file" + + result = PAPath(path_to_shared_file).unshare() + + assert mock_sharing_get.call_args == call(path_to_shared_file) + assert mock_sharing_delete.call_args == call(path_to_shared_file) + assert mock_snake.call_args == call(f"Could not unshare {path_to_shared_file}... :(") + assert mock_info.call_args == call(mock_snake.return_value) + assert result == False +