Repository: TheFriendlyCoder/friendlypins

Issue:
Add option to delete pins post download
We should add an option to the `fpins dt` command to delete pins once their thumbnails have been successfully downloaded. I'm thinking adding support for an optional '--delete' parameter should do the trick.

Discussion:

Patch:
diff --git a/src/friendlypins/scripts/fpins.py b/src/friendlypins/scripts/fpins.py
index 0835d2d..2fd8974 100644
--- a/src/friendlypins/scripts/fpins.py
+++ b/src/friendlypins/scripts/fpins.py
@@ -13,7 +13,7 @@ def _download_thumbnails(args):
     :returns: zero on success, non-zero on failure
     :rtype: :class:`int`
     """
-    return download_thumbnails(args.token, args.board, args.path)
+    return download_thumbnails(args.token, args.board, args.path, args.delete)
 
 
 def get_args(args):
@@ -59,6 +59,11 @@ def get_args(args):
         required=True,
         help="Path to the folder where thumbnails are to be downloaded",
     )
+    thumbnails_cmd.add_argument(
+        '--delete', '-d',
+        action="store_true",
+        help="Deletes each pin as it's thumbnail is downloaded"
+    )
 
     # If we've been given debugging arguments, convert them to the
     # format argparse expects
diff --git a/src/friendlypins/utils/console_actions.py b/src/friendlypins/utils/console_actions.py
index e6208fd..84c63b7 100644
--- a/src/friendlypins/utils/console_actions.py
+++ b/src/friendlypins/utils/console_actions.py
@@ -38,12 +38,13 @@ def _download_pin(pin, folder):
         return 2
     return 0
 
-def download_thumbnails(api_token, board_name, output_folder):
+def download_thumbnails(api_token, board_name, output_folder, delete):
     """Downloads thumbnails of all pins on a board
 
     :param str api_token: Authentication token for accessing the Pinterest API
     :param str board_name: name of the board containing the pins to process
     :param str output_folder: path where the thumbnails are to be downloaded
+    :param bool delete: flag to delete pins as their thumbnails are downloaded
     :returns:
         status code describing the result of the action
         zero on success, non-zero on failure
@@ -71,6 +72,9 @@ def download_thumbnails(api_token, board_name, output_folder):
         retval = _download_pin(cur_pin, output_folder)
         if retval:
             return retval
+        if delete:
+            cur_pin.delete()
+
     return 0
 
 if __name__ == "__main__":


Test patch:
diff --git a/unit_tests/test_console_actions.py b/unit_tests/test_console_actions.py
index 0d5e174..d92a3e9 100644
--- a/unit_tests/test_console_actions.py
+++ b/unit_tests/test_console_actions.py
@@ -74,7 +74,7 @@ def test_download_thumbnails(api_requests, user_requests, board_requests, action
     mock_os.path.exists.return_value = False
 
     # Flex our code
-    result = download_thumbnails("1234abcd", expected_board_name, "/tmp")
+    result = download_thumbnails("1234abcd", expected_board_name, "/tmp", False)
 
     # Make sure the call was successful, and that our mock APIs
     # that must have executed as part of the process were called
@@ -85,6 +85,91 @@ def test_download_thumbnails(api_requests, user_requests, board_requests, action
     mock_open.assert_called()
 
 
+@mock.patch("friendlypins.utils.console_actions.os")
+@mock.patch("friendlypins.utils.console_actions.open")
+@mock.patch("friendlypins.utils.console_actions.requests")
+@mock.patch("friendlypins.board.requests")
+@mock.patch("friendlypins.user.requests")
+@mock.patch("friendlypins.api.requests")
+@mock.patch("friendlypins.pin.requests")
+def test_download_thumbnails_with_delete(pin_requests, api_requests, user_requests, board_requests, action_requests, mock_open, mock_os):
+
+    # Fake user data for the user authenticating to Pinterest
+    expected_user_data = {
+        'data': {
+            'url': 'https://www.pinterest.com/MyUserName/',
+            'first_name': "John",
+            'last_name': "Doe",
+            'id': "12345678"
+        }
+    }
+
+    # Fake board data for the boards owned by the fake authenticated user
+    expected_board_name = "MyBoard"
+    expected_board_data = {
+        "data": [{
+            "id": "6789",
+            "name": expected_board_name,
+            "url": "https://www.pinterest.ca/MyName/MyBoard/"
+        }]
+    }
+
+    # Fake pin data for the fake board, with fake thumbnail metadata
+    expected_thumbnail_url = "https://i.pinimg.com/originals/1/2/3/abcd.jpg"
+    expected_pin_data = {
+        "data": [{
+            "id": "1234",
+            "url": "https://www.pinterest.ca/MyName/MyPin/",
+            "note": "My Pin descriptive text",
+            "link": "http://www.mysite.com/target",
+            "media": {
+                "type": "image"
+            },
+            "image": {
+                "original": {
+                    "url": expected_thumbnail_url,
+                    "width": "800",
+                    "height": "600"
+                }
+            }
+        }],
+        "page": {
+            "cursor": None
+        }
+    }
+
+    # fake our Pinterest API data to flex our implementation logic
+    mock_user_response = mock.MagicMock()
+    mock_user_response.json.return_value = expected_user_data
+    api_requests.get.return_value = mock_user_response
+
+    mock_board_response = mock.MagicMock()
+    mock_board_response.json.return_value = expected_board_data
+    user_requests.get.return_value = mock_board_response
+
+    mock_pin_response = mock.MagicMock()
+    mock_pin_response.json.return_value = expected_pin_data
+    board_requests.get.return_value = mock_pin_response
+
+    mock_delete_response = mock.MagicMock()
+    pin_requests.delete.return_value = mock_delete_response
+
+    # Make sure the code think's the output file where the
+    # thumbnail is to be downloaded doesn't already exist
+    mock_os.path.exists.return_value = False
+
+    # Flex our code
+    result = download_thumbnails("1234abcd", expected_board_name, "/tmp", True)
+
+    # Make sure the call was successful, and that our mock APIs
+    # that must have executed as part of the process were called
+    assert result == 0
+    action_requests.get.assert_called_once_with(expected_thumbnail_url, stream=True)
+    mock_os.makedirs.assert_called()
+    mock_os.path.exists.assert_called()
+    mock_open.assert_called()
+    pin_requests.delete.assert_called_once()
+
 @mock.patch("friendlypins.utils.console_actions.os")
 @mock.patch("friendlypins.utils.console_actions.open")
 @mock.patch("friendlypins.utils.console_actions.requests")
@@ -160,7 +245,7 @@ def test_download_thumbnails_error(api_requests, user_requests, board_requests,
     action_requests.get.return_value = mock_action_response
 
     # Flex our code
-    result = download_thumbnails("1234abcd", expected_board_name, "/tmp")
+    result = download_thumbnails("1234abcd", expected_board_name, "/tmp", False)
 
     # Make sure the call was successful, and that our mock APIs
     # that must have executed as part of the process were called
@@ -239,7 +324,7 @@ def test_download_thumbnails_missing_board(api_requests, user_requests, board_re
     mock_os.path.exists.return_value = False
 
     # Flex our code
-    result = download_thumbnails("1234abcd", "FuBar", "/tmp")
+    result = download_thumbnails("1234abcd", "FuBar", "/tmp", False)
 
     # Make sure the call was successful, and that our mock APIs
     # that must have executed as part of the process were called
@@ -323,7 +408,7 @@ def test_download_thumbnails_exists(api_requests, user_requests, board_requests,
 
     # Flex our code
     output_folder = "/tmp"
-    result = download_thumbnails("1234abcd", expected_board_name, output_folder)
+    result = download_thumbnails("1234abcd", expected_board_name, output_folder, False)
 
     # Make sure the call was successful, and that our mock APIs
     # that must have executed as part of the process were called