This repository has been archived by the owner on Aug 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from l-vo/add_gdrive_uploader
Add Google Drive uploader
- Loading branch information
Showing
7 changed files
with
296 additions
and
5 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
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 |
---|---|---|
|
@@ -7,3 +7,4 @@ class UploaderException(AbstractException): | |
# Error constants | ||
NOT_FOUND = 1 | ||
NOT_EMPTY = 2 | ||
MANY_DIRS = 3 |
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,65 @@ | ||
from io import BytesIO | ||
from photospicker.exception.uploader_exception import UploaderException | ||
from photospicker.uploader.abstract_uploader import AbstractUploader | ||
from pydrive.drive import GoogleDrive | ||
|
||
|
||
class GDriveUploader(AbstractUploader): | ||
"""Upload picked photo to Google Drive""" | ||
|
||
def __init__(self, gauth): | ||
""" | ||
Constructor | ||
:param pydrive.auth.GoogleAuth gauth: GoogleAth authentified instance | ||
""" | ||
super(GDriveUploader, self).__init__() | ||
self._gdrive = GoogleDrive(gauth) | ||
self._folder = None | ||
|
||
def initialize(self): | ||
"""Clear remote directory""" | ||
query = "mimeType = 'application/vnd.google-apps.folder'"\ | ||
+ " and title = 'photos-picker' and trashed=false" | ||
folders = self._gdrive.ListFile({"q": query}).GetList() | ||
|
||
count = len(folders) | ||
|
||
# Remove old folder if exists | ||
if count == 0: | ||
# Create folder | ||
folder_metadata = { | ||
'title': 'photos-picker', | ||
'mimeType': 'application/vnd.google-apps.folder' | ||
} | ||
self._folder = self._gdrive.CreateFile(folder_metadata) | ||
self._folder.Upload() | ||
elif count == 1: | ||
self._folder = folders[0] | ||
# Remove previously uploaded files | ||
query = "'{folder_id}' in parents and trashed=false" | ||
files = self._gdrive.ListFile( | ||
{"q": query.format(folder_id=self._folder['id'])} | ||
).GetList() | ||
for file_to_delete in files: | ||
file_to_delete.Delete() | ||
else: | ||
raise UploaderException( | ||
UploaderException.MANY_DIRS, | ||
"Many dirs named photos-picker; can't continue" | ||
) | ||
|
||
def upload(self, binary, original_filename): | ||
""" | ||
Upload or copy files to destination | ||
:param str binary : binary data to upload | ||
:param str original_filename: original file name | ||
""" | ||
filename = self._build_filename(original_filename) | ||
gfile = self._gdrive.CreateFile({ | ||
'title': filename, | ||
"parents": [{"kind": "drive#fileLink", "id": self._folder['id']}] | ||
}) | ||
gfile.content = BytesIO(binary) | ||
gfile.Upload() |
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 |
---|---|---|
|
@@ -7,4 +7,5 @@ tox | |
flake8 | ||
coverage | ||
codecov | ||
callee | ||
callee | ||
pydrive |
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 |
---|---|---|
|
@@ -8,13 +8,13 @@ def read(fname): | |
|
||
setup( | ||
name='photos-picker', | ||
version='0.2.1', | ||
version='0.3.0', | ||
description='Pick photos following a given strategy and upload them to various destinations', | ||
author='Laurent VOULLEMIER', | ||
author_email='[email protected]', | ||
url='https://github.com/l-vo/photos-picker', | ||
packages=find_packages(), | ||
install_requires=['Pillow', 'zope.event', 'dropbox'], | ||
install_requires=['Pillow', 'zope.event', 'dropbox', 'pydrive'], | ||
include_package_data=True, | ||
zip_safe=False, | ||
long_description=read('README.md'), | ||
|
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,185 @@ | ||
from unittest import TestCase | ||
from mock import Mock, MagicMock | ||
from photospicker.exception.uploader_exception import UploaderException | ||
from photospicker.uploader.gdrive_uploader import GDriveUploader | ||
import mock | ||
|
||
|
||
class TestGDriveUploader(TestCase): | ||
"""Test class for GDriveUploader""" | ||
|
||
def setUp(self): | ||
self._gauth = Mock() | ||
|
||
@mock.patch('photospicker.uploader.gdrive_uploader.GoogleDrive') | ||
def test_initialize_should_throw_exception_if_many_folders( | ||
self, | ||
gdrive_constructor_mock | ||
): | ||
""" | ||
Test that an exception is thrown if there is many directories | ||
with the name of the photos picker directory | ||
:param MagicMock gdrive_constructor_mock: | ||
mock for gdrive constructor | ||
""" | ||
self._initialize_gdrive_mock( | ||
gdrive_constructor_mock, | ||
[[Mock(), Mock()]] | ||
) | ||
|
||
with self.assertRaises(UploaderException) as cm: | ||
sut = GDriveUploader(self._gauth) | ||
sut.initialize() | ||
|
||
self._initialize_common_assertions(gdrive_constructor_mock) | ||
|
||
self.assertEqual(UploaderException.MANY_DIRS, cm.exception.code) | ||
|
||
@mock.patch('photospicker.uploader.gdrive_uploader.GoogleDrive') | ||
def test_initialize_with_no_existing_folder_should_create_folder( | ||
self, | ||
gdrive_constructor_mock | ||
): | ||
""" | ||
Test that if no folder exists with the name of the photos picker | ||
directory, it is created | ||
:param MagicMock gdrive_constructor_mock: | ||
mock for gdrive constructor | ||
""" | ||
gdrive_mock = self._initialize_gdrive_mock( | ||
gdrive_constructor_mock, | ||
[[]] | ||
) | ||
|
||
created_folder_mock = Mock() | ||
gdrive_mock.CreateFile.return_value = created_folder_mock | ||
|
||
sut = GDriveUploader(self._gauth) | ||
sut.initialize() | ||
|
||
gdrive_mock.CreateFile.assert_called_once_with({ | ||
'mimeType': 'application/vnd.google-apps.folder', | ||
'title': 'photos-picker' | ||
}) | ||
created_folder_mock.Upload.assert_called_once() | ||
|
||
self._initialize_common_assertions(gdrive_constructor_mock) | ||
|
||
@mock.patch('photospicker.uploader.gdrive_uploader.GoogleDrive') | ||
def test_initialize_with_existing_folder_should_empty_it( | ||
self, | ||
gdrive_constructor_mock | ||
): | ||
""" | ||
Test that if a folder already exists with the name of the photos picker | ||
directory, it is emptied | ||
:param MagicMock gdrive_constructor_mock: | ||
mock for gdrive constructor | ||
""" | ||
folder_mock = MagicMock() | ||
folder_mock.__getitem__.return_value = 7 | ||
file1 = Mock() | ||
file2 = Mock() | ||
self._initialize_gdrive_mock( | ||
gdrive_constructor_mock, | ||
[[folder_mock], [file1, file2]] | ||
) | ||
|
||
sut = GDriveUploader(self._gauth) | ||
sut.initialize() | ||
|
||
folder_mock.__getitem__.assert_called_once_with('id') | ||
|
||
self._initialize_common_assertions( | ||
gdrive_constructor_mock, | ||
[mock.call({'q': "'7' in parents and trashed=false"})] | ||
) | ||
|
||
file1.Delete.assert_called_once() | ||
file2.Delete.assert_called_once() | ||
|
||
@staticmethod | ||
def _initialize_gdrive_mock(gdrive_constructor_mock, return_values): | ||
""" | ||
Create a mock for gdrive GetList method | ||
:param MagicMock gdrive_constructor_mock: | ||
mock for gdrive constructor | ||
:param list return_values: values successively | ||
returned by GetList method | ||
:return Mock | ||
""" | ||
gdrive_mock = Mock() | ||
gdrive_constructor_mock.return_value = gdrive_mock | ||
|
||
side_effect = [] | ||
for return_value in return_values: | ||
gdrive_list_mock = Mock() | ||
gdrive_list_mock.GetList.return_value = return_value | ||
side_effect.append(gdrive_list_mock) | ||
|
||
gdrive_mock.ListFile.side_effect = side_effect | ||
|
||
return gdrive_mock | ||
|
||
def _initialize_common_assertions( | ||
self, | ||
gdrive_constructor_mock, | ||
list_files_additonal_calls=[] | ||
): | ||
""" | ||
Make common assertions for initialize tests | ||
:param MagicMock gdrive_constructor_mock: | ||
mock for gdrive constructor | ||
:param list list_files_additonal_calls: | ||
additional expected calls for ListFile method | ||
""" | ||
gdrive_constructor_mock.assert_called_once_with(self._gauth) | ||
|
||
calls = [ | ||
mock.call({ | ||
'q': "mimeType = 'application/vnd.google-apps.folder' " | ||
"and title = 'photos-picker' and trashed=false" | ||
}) | ||
] | ||
|
||
calls += list_files_additonal_calls | ||
|
||
gdrive_constructor_mock.return_value.ListFile.assert_has_calls(calls) | ||
|
||
@mock.patch('photospicker.uploader.gdrive_uploader.GoogleDrive') | ||
def test_upload(self, gdrive_constructor_mock): | ||
""" | ||
Test upload method | ||
:param MagicMock gdrive_constructor_mock: | ||
mock for gdrive constructor | ||
""" | ||
folder_mock = MagicMock() | ||
folder_mock.__getitem__.return_value = 12 | ||
gdrive_mock = self._initialize_gdrive_mock( | ||
gdrive_constructor_mock, | ||
[[folder_mock], []] | ||
) | ||
|
||
created_file_mock = Mock() | ||
gdrive_mock.CreateFile.return_value = created_file_mock | ||
|
||
sut = GDriveUploader(self._gauth) | ||
sut.initialize() | ||
sut.upload('mybinarydata', 'IMG5423.JPG') | ||
|
||
folder_mock.__getitem__.assert_called_with('id') | ||
|
||
gdrive_mock.CreateFile.assert_called_once_with({ | ||
'parents': [{'kind': 'drive#fileLink', 'id': 12}], | ||
'title': 'photo0.jpg' | ||
}) | ||
|
||
self.assertEqual('mybinarydata', created_file_mock.content.getvalue()) | ||
created_file_mock.Upload.assert_called_once() |