-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#29 wip: sketches Path interface for Files enpoint covering contents …
…and delete methods. by Piotr
- Loading branch information
Showing
4 changed files
with
130 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""User interface for interacting with PythonAnywhere files. | ||
Provides a class `Path` which should be used by helper scripts | ||
providing features for programmatic handling of user's files.""" | ||
|
||
import logging | ||
|
||
from pythonanywhere.api.files_api import Files | ||
from pythonanywhere.snakesay import snakesay | ||
|
||
logger = logging.getLogger(name=__name__) | ||
|
||
|
||
class Path: | ||
"""Class providing interface for interacting with PythonAnywhere user files. | ||
""" | ||
|
||
def __init__(self, path): | ||
self.path = path | ||
self.api = Files() | ||
|
||
def __repr__(self): | ||
user_url = self.api.base_url.replace("/api/v0", "") | ||
return f"{user_url}{self.path[1:]}" | ||
|
||
def contents(self): | ||
content = self.api.path_get(self.path) | ||
return content if type(content) == dict else content.decode("utf-8") | ||
|
||
def delete(self): | ||
try: | ||
self.api.path_delete(self.path) | ||
logger.info(snakesay(f"{self.path} deleted!")) | ||
except Exception as e: | ||
logger.warning(snakesay(str(e))) | ||
|
||
def upload(self): | ||
pass | ||
|
||
def share(self): | ||
pass | ||
|
||
def unshare(self): | ||
pass | ||
|
||
def tree(self): | ||
pass | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import getpass | ||
from urllib.parse import urljoin | ||
from unittest.mock import call | ||
|
||
import pytest | ||
|
||
from pythonanywhere.api.base import get_api_endpoint | ||
from pythonanywhere.files import Path | ||
from tests.test_api_files import TestFiles | ||
|
||
|
||
@pytest.mark.files | ||
class TestPathRepr(TestFiles): | ||
def test_contains_correct_pythonanywhere_resource_url_for_instantiated_path(self): | ||
path = self.home_dir_path | ||
|
||
user_path = self.base_url.replace('/api/v0', '') | ||
assert Path(path).__repr__() == f"{user_path}{path[1:]}" | ||
|
||
|
||
@pytest.mark.files | ||
class TestPathContents(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") | ||
mock_path_get.return_value = self.readme_contents | ||
|
||
result = Path(path).contents() | ||
|
||
assert mock_path_get.call_args == call(path) | ||
assert result == self.readme_contents.decode() | ||
|
||
def test_returns_directory_contents_if_path_points_to_a_directory(self, mocker): | ||
mock_path_get = mocker.patch("pythonanywhere.api.files_api.Files.path_get") | ||
mock_path_get.return_value = self.default_home_dir_files | ||
|
||
result = Path(self.home_dir_path).contents() | ||
|
||
assert result == self.default_home_dir_files | ||
|
||
def test_raises_when_path_unavailable(self, mocker): | ||
mock_path_get = mocker.patch("pythonanywhere.api.files_api.Files.path_get") | ||
mock_path_get.side_effect = Exception("error msg") | ||
|
||
with pytest.raises(Exception) as e: | ||
Path('/home/different_user').contents() | ||
|
||
assert str(e.value) == "error msg" | ||
|
||
|
||
@pytest.mark.files | ||
class TestPathDelete(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 | ||
mock_snake = mocker.patch("pythonanywhere.files.snakesay") | ||
mock_info = mocker.patch("pythonanywhere.files.logger.info") | ||
path = "/valid/path" | ||
|
||
Path(path).delete() | ||
|
||
assert mock_delete.call_args == call(path) | ||
assert mock_snake.call_args == call(f"{path} deleted!") | ||
assert mock_info.call_args == call(mock_snake.return_value) | ||
|
||
def test_warns_about_failed_deletion(self, mocker): | ||
mock_delete = mocker.patch("pythonanywhere.api.files_api.Files.path_delete") | ||
mock_delete.side_effect = Exception("error msg") | ||
mock_snake = mocker.patch("pythonanywhere.files.snakesay") | ||
mock_warning = mocker.patch("pythonanywhere.files.logger.warning") | ||
undeletable_path = "/home/" | ||
|
||
Path(undeletable_path).delete() | ||
|
||
assert mock_snake.call_args == call("error msg") |