diff --git a/CHANGELOG.md b/CHANGELOG.md index ba0ef8c..1d6e7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 0.4.1 + +- Add code and git style guide for contributions. + ## pending ## 0.3.2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d5c585..64afe0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,3 +66,16 @@ Commands: ... ... ``` + +## Code & Commit Conventions (CCC) + +This library conforms to the `black` python coding format and expect that contributions follow the git commit style conventions in the [Style Guide](). + +Synopsis: + - Use `black` code [formatter](https://black.readthedocs.io/en/stable/) + - Make commits [atomic](https://www.freshconsulting.com/insights/blog/atomic-commits/) + - Separate subject from body with a blank line + - Limit the subject line to 50 characters + - Use the imperative mood in the subject line + - Wrap the body at 72 characters + - Use the body to explain what and why vs. how \ No newline at end of file diff --git a/README.md b/README.md index 292d635..a6022f6 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ It could raise `MissingGoogleDriveFolderException` when parent directories do no [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) -Contributions are welcome! Please see our [Contributing Guide]() for more +Contributions are welcome! Please see our [Contributing Guide]() and [Style Guide]() for more details. You can visit our [TODO](TODO.md) list :) diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..c2ac1c1 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,209 @@ +# Style Guide + +A project’s long-term success rests (among other things) on its maintainability, and a maintainer has few tools more powerful than his or her project’s log. It’s worth taking the time to learn how to care for one properly. What may be a hassle at first soon becomes habit, and eventually a source of pride and productivity for all involved. + +Most programming languages have well-established conventions as to what constitutes idiomatic style, i.e. naming, formatting and so on. There are variations on these conventions, of course, but most developers agree that picking one and sticking to it is far better than the chaos that ensues when everybody does their own thing. + +### Code style +Use `black` to format your python code before commiting for consistency across such a large pool of contributors. Black's code [style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#code-style) ensures consistent and opinionated code formatting. It automatically formats your Python code according to the Black style guide, enhancing code readability and maintainability. + +Key Features of Black: + + Consistency: Black enforces a single, consistent coding style across your project, eliminating style debates and allowing developers to focus on code logic. + + Readability: By applying a standard formatting style, Black improves code readability, making it easier to understand and collaborate on projects. + + Automation: Black automates the code formatting process, saving time and effort. It eliminates the need for manual formatting and reduces the likelihood of inconsistencies. + +### Git commit style + +Here’s a model Git commit message when contributing: +``` +Summarize changes in around 50 characters or less + +More detailed explanatory text, if necessary. Wrap it to about 72 +characters or so. In some contexts, the first line is treated as the +subject of the commit and the rest of the text as the body. The +blank line separating the summary from the body is critical (unless +you omit the body entirely); various tools like `log`, `shortlog` +and `rebase` can get confused if you run the two together. + +Explain the problem that this commit is solving. Focus on why you +are making this change as opposed to how (the code explains that). +Are there side effects or other unintuitive consequences of this +change? Here's the place to explain them. + +Further paragraphs come after blank lines. + + - Bullet points are okay, too + + - Typically a hyphen or asterisk is used for the bullet, preceded + by a single space, with blank lines in between, but conventions + vary here + +If you use an issue tracker, put references to them at the bottom, +like this: + +Resolves: #123 +See also: #456, #789 +``` + + +## The six rules of a great commit. + +#### 1. Atomic Commits +An “atomic” change revolves around one task or one fix. + +Atomic Approach + - Commit each fix or task as a separate change + - Only commit when a block of work is complete + - Commit each layout change separately + - Joint commit for layout file, code behind file, and additional resources + +Benefits + +- Easy to roll back without affecting other changes +- Easy to make other changes on the fly +- Easy to merge features to other branches + +*Caveat*: When working with new features, an atomic commit will often consist of multiple files, since a layout file, code behind file, and additional resources may have been added/modified. You don’t want to commit all of these separately, because if you had to roll back the application to a state before the feature was added, it would involve multiple commit entries, and that can get confusing + +#### 2. Separate subject from body with a blank line + +Not every commit requires both a subject and a body. Sometimes a single line is fine, especially when the change is so simple that no further context is necessary. + +For example: + + Fix typo in introduction to user guide + +Nothing more need be said; if the reader wonders what the typo was, she can simply take a look at the change itself, i.e. use git show or git diff or git log -p. + +If you’re committing something like this at the command line, it’s easy to use the -m option to git commit: + + $ git commit -m"Fix typo in introduction to user guide" + +However, when a commit merits a bit of explanation and context, you need to write a body. For example: + + Derezz the master control program + + MCP turned out to be evil and had become intent on world domination. + This commit throws Tron's disc into MCP (causing its deresolution) + and turns it back into a chess game. + +Commit messages with bodies are not so easy to write with the -m option. You’re better off writing the message in a proper text editor. [See Pro Git](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration). + +In any case, the separation of subject from body pays off when browsing the log. Here’s the full log entry: + + $ git log + commit 42e769bdf4894310333942ffc5a15151222a87be + Author: Kevin Flynn + Date: Fri Jan 01 00:00:00 1982 -0200 + + Derezz the master control program + + MCP turned out to be evil and had become intent on world domination. + This commit throws Tron's disc into MCP (causing its deresolution) + and turns it back into a chess game. + + +#### 3. Limit the subject line to 50 characters +50 characters is not a hard limit, just a rule of thumb. Keeping subject lines at this length ensures that they are readable, and forces the author to think for a moment about the most concise way to explain what’s going on. + +GitHub’s UI is fully aware of these conventions. It will warn you if you go past the 50 character limit. Git will truncate any subject line longer than 72 characters with an ellipsis, thus keeping it to 50 is best practice. + +#### 4. Use the imperative mood in the subject line +Imperative mood just means “spoken or written as if giving a command or instruction”. A few examples: + + Clean your room + Close the door + Take out the trash + +Each of the seven rules you’re reading about right now are written in the imperative (“Wrap the body at 72 characters”, etc.). + +The imperative can sound a little rude; that’s why we don’t often use it. But it’s perfect for Git commit subject lines. One reason for this is that Git itself uses the imperative whenever it creates a commit on your behalf. + +For example, the default message created when using git merge reads: + + Merge branch 'myfeature' + +And when using git revert: + + Revert "Add the thing with the stuff" + + This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d. + +Or when clicking the “Merge” button on a GitHub pull request: + + Merge pull request #123 from someuser/somebranch + +So when you write your commit messages in the imperative, you’re following Git’s own built-in conventions. For example: + + Refactor subsystem X for readability + Update getting started documentation + Remove deprecated methods + Release version 1.0.0 + +Writing this way can be a little awkward at first. We’re more used to speaking in the indicative mood, which is all about reporting facts. That’s why commit messages often end up reading like this: + + Fixed bug with Y + Changing behavior of X + +And sometimes commit messages get written as a description of their contents: + + More fixes for broken stuff + Sweet new API methods + +To remove any confusion, here’s a simple rule to get it right every time. + +**A properly formed Git commit subject line should always be able to complete the following sentence:** + + If applied, this commit will + +For example: + + If applied, this commit will refactor subsystem X for readability + If applied, this commit will update getting started documentation + If applied, this commit will remove deprecated methods + If applied, this commit will release version 1.0.0 + If applied, this commit will merge pull request #123 from user/branch + +#### 5. Wrap the body at 72 characters +Git never wraps text automatically. When you write the body of a commit message, you must mind its right margin, and wrap text manually. + +The recommendation is to do this at 72 characters, so that Git has plenty of room to indent text while still keeping everything under 80 characters overall. + +A good text editor can help here. It’s easy to configure Vim, for example, to wrap text at 72 characters when you’re writing a Git commit. + +#### 6. Use the body to explain what and why vs. how +This [commit](https://github.com/bitcoin/bitcoin/commit/eb0b56b19017ab5c16c745e6da39c53126924ed6) from Bitcoin Core is a great example of explaining what changed and why: + +``` +commit eb0b56b19017ab5c16c745e6da39c53126924ed6 +Author: Pieter Wuille +Date: Fri Aug 1 22:57:55 2014 +0200 + + Simplify serialize.h's exception handling + + Remove the 'state' and 'exceptmask' from serialize.h's stream + implementations, as well as related methods. + + As exceptmask always included 'failbit', and setstate was always + called with bits = failbit, all it did was immediately raise an + exception. Get rid of those variables, and replace the setstate + with direct exception throwing (which also removes some dead + code). + + As a result, good() is never reached after a failure (there are + only 2 calls, one of which is in tests), and can just be replaced + by !eof(). + + fail(), clear(n) and exceptions() are just never called. Delete + them. +``` + +Take a look at the [full diff](https://github.com/bitcoin/bitcoin/commit/eb0b56b19017ab5c16c745e6da39c53126924ed6) and just think how much time the author is saving fellow and future committers by taking the time to provide this context here and now. If he didn’t, it would probably be lost forever. + +In most cases, you can leave out details about how a change has been made. Code is generally self-explanatory in this regard (and if the code is so complex that it needs to be explained in prose, that’s what source comments are for). Just focus on making clear the reasons why you made the change in the first place—the way things worked before the change (and what was wrong with that), the way they work now, and why you decided to solve it the way you did. + +The future maintainer that thanks you may be yourself! +Tips diff --git a/docs/source/conf.py b/docs/source/conf.py index 9bce515..adc79d7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,12 +17,12 @@ # -- Project information ----------------------------------------------------- -project = 'google-drive' -copyright = '2020, Eduardo' -author = 'Eduardo' +project = "google-drive" +copyright = "2020, Eduardo" +author = "Eduardo" # The full version, including alpha/beta/rc tags -release = '0.3.2' +release = "0.3.2" # -- General configuration --------------------------------------------------- @@ -30,11 +30,10 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ -] +extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -47,9 +46,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ["_static"] diff --git a/google_auth.py b/google_auth.py index 4c50943..1a70388 100644 --- a/google_auth.py +++ b/google_auth.py @@ -6,25 +6,25 @@ SCOPES = [ # drive: Full, permissive scope to access all of a user's files, # excluding the Application Data folder. - 'https://www.googleapis.com/auth/drive', + "https://www.googleapis.com/auth/drive", # gmail: Send messages only. No read or modify privileges on mailbox. - 'https://www.googleapis.com/auth/gmail.send', + "https://www.googleapis.com/auth/gmail.send", # docs: Per-file access to files that the app created or opened. - 'https://www.googleapis.com/auth/drive.file', + "https://www.googleapis.com/auth/drive.file", # sheets: # Allows read-only access to the user's sheets and their properties. - 'https://www.googleapis.com/auth/spreadsheets.readonly', + "https://www.googleapis.com/auth/spreadsheets.readonly", # appscripts: #'https://www.googleapis.com/auth/script.projects', ] -CREDENTIALS = 'credentials.json' +CREDENTIALS = "credentials.json" + def main(): - GoogleAuth().authenticate( - CREDENTIALS, - SCOPES) + GoogleAuth().authenticate(CREDENTIALS, SCOPES) print('File "token.pickle" should be generated') -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/googledrive/api.py b/googledrive/api.py index 8b23928..ed53384 100644 --- a/googledrive/api.py +++ b/googledrive/api.py @@ -14,8 +14,8 @@ from googledrive.exceptions import MissingGoogleDriveFileException from googledrive.exceptions import GoogleApiClientHttpErrorException -class GoogleAuth: +class GoogleAuth: def authenticate(self, credentials, scopes): """ Obtaining auth with needed apis @@ -24,24 +24,23 @@ def authenticate(self, credentials, scopes): # The file token.pickle stores the user's access # and refresh tokens, and is created automatically # when the authorization flow completes for the first time. - if os.path.exists('token.pickle'): - with open('token.pickle', 'rb') as token: + if os.path.exists("token.pickle"): + with open("token.pickle", "rb") as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: - flow = InstalledAppFlow.from_client_secrets_file( - credentials, - scopes) + flow = InstalledAppFlow.from_client_secrets_file(credentials, scopes) creds = flow.run_local_server(port=0) # Save the credentials for the next run - with open('token.pickle', 'wb') as token: + with open("token.pickle", "wb") as token: pickle.dump(creds, token) return creds + class GoogleService: __services_by_id = {} @@ -53,42 +52,44 @@ def __init__(self, credentials_file, scopes): def get_service(self, service_id, service_version): if not service_id in self.__services_by_id: - self.__services_by_id.update({ - service_id: {} - }) + self.__services_by_id.update({service_id: {}}) if not service_version in self.__services_by_id[service_id]: - self.__services_by_id[service_id].update({ - service_version: build( - service_id, - service_version, - credentials=self.__get_credentials(), - cache_discovery=False) - }) + self.__services_by_id[service_id].update( + { + service_version: build( + service_id, + service_version, + credentials=self.__get_credentials(), + cache_discovery=False, + ) + } + ) return self.__services_by_id[service_id][service_version] def __get_credentials(self): if self.__credentials is None: self.__credentials = GoogleAuth().authenticate( - self.__credentials_file, - self.__scopes) + self.__credentials_file, self.__scopes + ) return self.__credentials + class GoogleDrive(GoogleService): - DRIVE_SERVICE_ID = 'drive' - DRIVE_SERVICE_VERSION = 'v3' + DRIVE_SERVICE_ID = "drive" + DRIVE_SERVICE_VERSION = "v3" - PERMISSION_ROLE_COMMENTER = 'commenter' + PERMISSION_ROLE_COMMENTER = "commenter" - MIMETYPE_FOLDER = 'application/vnd.google-apps.folder' - MIMETYPE_DOCUMENT = 'application/vnd.google-apps.document' - MIMETYPE_SPREADSHEET = 'application/vnd.google-apps.spreadsheet' - MIMETYPE_PDF = 'application/pdf' - MIMETYPE_DRAWING = 'application/vnd.google-apps.drawing' - MIMETYPE_FORM = 'application/vnd.google-apps.form' - MIMETYPE_PRESENTATION = 'application/vnd.google-apps.presentation' + MIMETYPE_FOLDER = "application/vnd.google-apps.folder" + MIMETYPE_DOCUMENT = "application/vnd.google-apps.document" + MIMETYPE_SPREADSHEET = "application/vnd.google-apps.spreadsheet" + MIMETYPE_PDF = "application/pdf" + MIMETYPE_DRAWING = "application/vnd.google-apps.drawing" + MIMETYPE_FORM = "application/vnd.google-apps.form" + MIMETYPE_PRESENTATION = "application/vnd.google-apps.presentation" ALL_MIMETYPES = [ MIMETYPE_FOLDER, MIMETYPE_DOCUMENT, @@ -99,8 +100,8 @@ class GoogleDrive(GoogleService): MIMETYPE_PRESENTATION, ] - FIELDS_BASIC_FILE_METADATA = 'id, name, parents, mimeType' - FIELDS_FILE_METADATA = f'{FIELDS_BASIC_FILE_METADATA}, exportLinks' + FIELDS_BASIC_FILE_METADATA = "id, name, parents, mimeType" + FIELDS_FILE_METADATA = f"{FIELDS_BASIC_FILE_METADATA}, exportLinks" QUERY_IS_FOLDER = f"mimeType='{MIMETYPE_FOLDER}'" QUERY_IS_DOCUMENT = f"mimeType='{MIMETYPE_DOCUMENT}'" @@ -119,18 +120,16 @@ def get_mymetypes(self): return GoogleDrive.ALL_MIMETYPES def create_folder(self, name): - file_metadata = { - 'name': name, - 'mimeType': GoogleDrive.MIMETYPE_FOLDER, - } + file_metadata = {"name": name, "mimeType": GoogleDrive.MIMETYPE_FOLDER} drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: - folder = drive_service.files().create( - body=file_metadata, - fields=self.FIELDS_BASIC_FILE_METADATA).execute() + folder = ( + drive_service.files() + .create(body=file_metadata, fields=self.FIELDS_BASIC_FILE_METADATA) + .execute() + ) except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) @@ -147,22 +146,20 @@ def create_file(self, name, mimetype): parent_folder = self.get_folder(parent_name) if parent_folder == None: raise MissingGoogleDriveFolderException( - "Missing folder: {}".format(parent_name)) + "Missing folder: {}".format(parent_name) + ) parents = [parent_folder.id] - file_metadata = { - 'name': name, - 'mimeType': mimetype, - 'parents': parents - } + file_metadata = {"name": name, "mimeType": mimetype, "parents": parents} drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: - file = drive_service.files().create( - body=file_metadata, - fields=self.FIELDS_BASIC_FILE_METADATA).execute() + file = ( + drive_service.files() + .create(body=file_metadata, fields=self.FIELDS_BASIC_FILE_METADATA) + .execute() + ) except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) @@ -171,14 +168,12 @@ def create_file(self, name, mimetype): def update_file_parent(self, file_id, current_parent, new_parent): drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: file_update = drive_service.files().update( - fileId=file_id, - addParents=new_parent, - removeParents=current_parent) + fileId=file_id, addParents=new_parent, removeParents=current_parent + ) file_update.execute() except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) @@ -186,74 +181,79 @@ def update_file_parent(self, file_id, current_parent, new_parent): def get_file_from_id(self, file_id: str): drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: - google_file_dict = drive_service.files().get( - fileId=file_id, - fields=self.FIELDS_FILE_METADATA).execute() - - return GoogleFileDictToGoogleFile().google_file_dict_to_google_file(google_file_dict) + google_file_dict = ( + drive_service.files() + .get(fileId=file_id, fields=self.FIELDS_FILE_METADATA) + .execute() + ) + + return GoogleFileDictToGoogleFile().google_file_dict_to_google_file( + google_file_dict + ) except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) def list_files(self, page_token: str, query: str): drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: - response = drive_service.files().list( - q=query, - pageSize=100, - spaces='drive', - corpora='user', - fields=f'nextPageToken, files({self.FIELDS_FILE_METADATA})', - pageToken=page_token).execute() + response = ( + drive_service.files() + .list( + q=query, + pageSize=100, + spaces="drive", + corpora="user", + fields=f"nextPageToken, files({self.FIELDS_FILE_METADATA})", + pageToken=page_token, + ) + .execute() + ) except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) google_files = [ - GoogleFileDictToGoogleFile().google_file_dict_to_google_file(google_file_dict) - for google_file_dict in response.get('files', [])] - next_page_token = response.get('nextPageToken', None) + GoogleFileDictToGoogleFile().google_file_dict_to_google_file( + google_file_dict + ) + for google_file_dict in response.get("files", []) + ] + next_page_token = response.get("nextPageToken", None) return google_files, next_page_token def copy_file(self, file_id, new_filename): drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: - results = drive_service.files().copy( - fileId=file_id, - body={ - 'name': new_filename, - 'mimeType': self.MIMETYPE_DOCUMENT - } - ).execute() - return results.get('id') + results = ( + drive_service.files() + .copy( + fileId=file_id, + body={"name": new_filename, "mimeType": self.MIMETYPE_DOCUMENT}, + ) + .execute() + ) + return results.get("id") except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) def create_permission(self, document_id: str, role: str, email_address): drive_service = super().get_service( - self.DRIVE_SERVICE_ID, - self.DRIVE_SERVICE_VERSION + self.DRIVE_SERVICE_ID, self.DRIVE_SERVICE_VERSION ) try: drive_service.permissions().create( fileId=document_id, - body={ - 'type': 'user', - 'emailAddress': email_address, - 'role': role, - } + body={"type": "user", "emailAddress": email_address, "role": role}, ).execute() except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) @@ -283,7 +283,8 @@ def googledrive_ls(self, path: str): folder = self.get_folder(splitted_path[0]) if folder is None: raise MissingGoogleDriveFolderException( - "Missing folder: {}".format(splitted_path[0])) + "Missing folder: {}".format(splitted_path[0]) + ) for path_element in splitted_path[1:]: query = f"{GoogleDrive.QUERY_IS_FOLDER} and '{folder.id}' in parents" @@ -291,7 +292,8 @@ def googledrive_ls(self, path: str): if folder is None: raise MissingGoogleDriveFolderException( - "Missing folder: {}".format(path_element)) + "Missing folder: {}".format(path_element) + ) query = f"{GoogleDrive.QUERY_IS_FILE} and '{folder.id}' in parents" @@ -314,10 +316,11 @@ def googledrive_get_file(self, path: str): folder = self.get_folder(splitted_path[0]) if folder is None: raise MissingGoogleDriveFolderException( - "Missing folder: {}".format(path[0])) + "Missing folder: {}".format(path[0]) + ) - path_elements = splitted_path[1 : len(splitted_path)-1] - filename = splitted_path[len(splitted_path)-1] + path_elements = splitted_path[1 : len(splitted_path) - 1] + filename = splitted_path[len(splitted_path) - 1] for path_element in path_elements: query = f"{GoogleDrive.QUERY_IS_FOLDER} and '{folder.id}' in parents" @@ -325,9 +328,10 @@ def googledrive_get_file(self, path: str): if folder is None: raise MissingGoogleDriveFolderException( - "Missing folder: {}".format(path_element)) + "Missing folder: {}".format(path_element) + ) - query = f"{GoogleDrive.QUERY_IS_FILE} and '{folder.id}' in parents" + query = f"{GoogleDrive.QUERY_IS_FILE} and '{folder.id}' in parents" google_file = self.__get_file(query, filename) if google_file is not None: @@ -336,7 +340,7 @@ def googledrive_get_file(self, path: str): return google_file def __split_path(self, path): - return filter(lambda x: x != '', path.split('/')) + return filter(lambda x: x != "", path.split("/")) def __get_file(self, query: str, filename): files = self.__get_files(query) @@ -351,8 +355,7 @@ def __get_files(self, query: str): total_google_files = [] while True: google_files, next_page_token = self.list_files( - page_token=page_token, - query=query + page_token=page_token, query=query ) total_google_files = total_google_files + google_files @@ -365,26 +368,24 @@ def __get_files(self, query: str): http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) + class SheetsService(GoogleService): - SHEETS_SERVICE_ID = 'sheets' - SHEETS_SERVICE_VERSION = 'v4' + SHEETS_SERVICE_ID = "sheets" + SHEETS_SERVICE_VERSION = "v4" cached_file_values = {} def create_spreadsheet(self, filename): - file_metadata = { - 'properties': { - 'title': filename, - } - } + file_metadata = {"properties": {"title": filename}} sheets_service = super().get_service( - self.SHEETS_SERVICE_ID, - self.SHEETS_SERVICE_VERSION + self.SHEETS_SERVICE_ID, self.SHEETS_SERVICE_VERSION + ) + spreadsheet = ( + sheets_service.spreadsheets() + .create(body=file_metadata, fields="spreadsheetId") + .execute() ) - spreadsheet = sheets_service.spreadsheets().create( - body=file_metadata, - fields='spreadsheetId').execute() return spreadsheet # TODO: @@ -395,11 +396,12 @@ def create_spreadsheet(self, filename): # def get_file_values(self, spreadsheet_id, rows_range): if spreadsheet_id is None: - raise MissingGoogleDriveFileException('Missing file: {}'.format(spreadsheet_id)) + raise MissingGoogleDriveFileException( + "Missing file: {}".format(spreadsheet_id) + ) sheets_service = super().get_service( - self.SHEETS_SERVICE_ID, - self.SHEETS_SERVICE_VERSION + self.SHEETS_SERVICE_ID, self.SHEETS_SERVICE_VERSION ) if spreadsheet_id in self.cached_file_values: @@ -409,40 +411,40 @@ def get_file_values(self, spreadsheet_id, rows_range): sheet = sheets_service.spreadsheets() try: - result = sheet.values().get( - spreadsheetId=spreadsheet_id, - range=rows_range - ).execute() - values = result.get('values', []) + result = ( + sheet.values() + .get(spreadsheetId=spreadsheet_id, range=rows_range) + .execute() + ) + values = result.get("values", []) if len(values) > 0: - self.cached_file_values.update({ - spreadsheet_id: { - rows_range: values - } - }) + self.cached_file_values.update({spreadsheet_id: {rows_range: values}}) return values except HttpError as e: http_error = GoogleApiClientHttpErrorBuilder().from_http_error(e) raise GoogleApiClientHttpErrorException(http_error) - def update_file_values(self, spreadsheet_id, rows_range, value_input_option, values): + def update_file_values( + self, spreadsheet_id, rows_range, value_input_option, values + ): sheets_service = super().get_service( - self.SHEETS_SERVICE_ID, - self.SHEETS_SERVICE_VERSION + self.SHEETS_SERVICE_ID, self.SHEETS_SERVICE_VERSION ) sheet = sheets_service.spreadsheets() - value_range_body = { - 'values': values - } - result = sheet.values().update( - spreadsheetId=spreadsheet_id, - range=rows_range, - valueInputOption=value_input_option, - body=value_range_body - ).execute() - return result.get('values', []) + value_range_body = {"values": values} + result = ( + sheet.values() + .update( + spreadsheetId=spreadsheet_id, + range=rows_range, + valueInputOption=value_input_option, + body=value_range_body, + ) + .execute() + ) + return result.get("values", []) # # High level API access @@ -451,86 +453,80 @@ def update_file_values(self, spreadsheet_id, rows_range, value_input_option, val def open_file(self, spreadheet_id): pass + class DocsService(GoogleService): - DOCS_SERVICE_ID = 'docs' - DOCS_SERVICE_VERSION = 'v1' + DOCS_SERVICE_ID = "docs" + DOCS_SERVICE_VERSION = "v1" - ELEMENTS = 'elements' - START_INDEX = 'startIndex' - END_INDEX = 'endIndex' + ELEMENTS = "elements" + START_INDEX = "startIndex" + END_INDEX = "endIndex" - PARAGRAPH = 'paragraph' - HORIZONTAL_RULE = 'horizontalRule' + PARAGRAPH = "paragraph" + HORIZONTAL_RULE = "horizontalRule" - TEXT_RUN = 'textRun' - CONTENT = 'content' + TEXT_RUN = "textRun" + CONTENT = "content" def get_document(self, document_id): docs_service = super().get_service( - self.DOCS_SERVICE_ID, - self.DOCS_SERVICE_VERSION + self.DOCS_SERVICE_ID, self.DOCS_SERVICE_VERSION ) return docs_service.documents().get(documentId=document_id).execute() def batch_update(self, document_id, requests): docs_service = super().get_service( - self.DOCS_SERVICE_ID, - self.DOCS_SERVICE_VERSION + self.DOCS_SERVICE_ID, self.DOCS_SERVICE_VERSION ) docs_service.documents().batchUpdate( - documentId=document_id, - body={'requests': requests}).execute() + documentId=document_id, body={"requests": requests} + ).execute() + class FilesAPI(GoogleDrive, SheetsService, DocsService): - ''' + """ Composition of google APIs - ''' + """ def create_sheet(self, folder_parent, folder, filename: str): spreadsheet = super().create_spreadsheet(filename) - spreadsheet_id = spreadsheet.get('spreadsheetId') + spreadsheet_id = spreadsheet.get("spreadsheetId") super().update_file_parent( - file_id=spreadsheet_id, - current_parent=folder_parent, - new_parent=folder.id + file_id=spreadsheet_id, current_parent=folder_parent, new_parent=folder.id ) # TODO: # return create('/folder_parent/folder/filename', mimetype='blablabal.spreadsheet') return spreadsheet_id - def get_file_rows_from_folder(self, - foldername: str, - filename: str, - rows_range: str): + def get_file_rows_from_folder( + self, foldername: str, filename: str, rows_range: str + ): file_path = f"/{foldername}/{filename}" google_file = super().googledrive_get_file(file_path) if google_file is None: - raise MissingGoogleDriveFileException('Missing file: {}'.format(filename)) + raise MissingGoogleDriveFileException("Missing file: {}".format(filename)) - values = super().get_file_values( - google_file.id, - rows_range) + values = super().get_file_values(google_file.id, rows_range) return values def empty_document(self, document_id, insert_index, end_index): document = super().get_document(document_id) - content = document.get('body').get('content') + content = document.get("body").get("content") # Empty file - if end_index in range(0, 2) or \ - insert_index >= end_index: + if end_index in range(0, 2) or insert_index >= end_index: return requests = [ { - 'deleteContentRange':{ - 'range': { - 'segmentId': '', - 'startIndex': insert_index, - 'endIndex': end_index + "deleteContentRange": { + "range": { + "segmentId": "", + "startIndex": insert_index, + "endIndex": end_index, } } } diff --git a/googledrive/cli.py b/googledrive/cli.py index 51f1aeb..9a98582 100644 --- a/googledrive/cli.py +++ b/googledrive/cli.py @@ -11,27 +11,25 @@ class Config: SCOPES = [ # drive: Full, permissive scope to access all of a user's files, # excluding the Application Data folder. - 'https://www.googleapis.com/auth/drive', + "https://www.googleapis.com/auth/drive", # docs: Per-file access to files that the app created or opened. - 'https://www.googleapis.com/auth/drive.file', + "https://www.googleapis.com/auth/drive.file", # sheets: # Allows read-only access to the user's sheets and their properties. - 'https://www.googleapis.com/auth/spreadsheets.readonly', + "https://www.googleapis.com/auth/spreadsheets.readonly", ] @click.command() -@click.argument('credentials', envvar='CREDENTIALS', type=click.Path(exists=True)) +@click.argument("credentials", envvar="CREDENTIALS", type=click.Path(exists=True)) def login(credentials): """Perform a login with google oauth""" - GoogleAuth().authenticate( - credentials=credentials, - scopes=Config.SCOPES) + GoogleAuth().authenticate(credentials=credentials, scopes=Config.SCOPES) @click.command() -@click.argument('path') -@click.argument('credentials', envvar='CREDENTIALS', type=click.Path(exists=True)) +@click.argument("path") +@click.argument("credentials", envvar="CREDENTIALS", type=click.Path(exists=True)) def ls(credentials, path): """List directory contents""" try: @@ -39,21 +37,21 @@ def ls(credentials, path): files = google_drive.googledrive_ls(path) except GoogleApiClientHttpErrorException as e: error = e.get_google_api_client_http_error() - print(f'An http exception occured requesting google\'s API:\n') - print(f' - Code: {error.code}') - print(f' - Message: {error.message}') - print(f' - Status: {error.status}') - print(f' - Details: {error.details}') - print(f' - Errors: {error.errors}\n') + print(f"An http exception occured requesting google's API:\n") + print(f" - Code: {error.code}") + print(f" - Message: {error.message}") + print(f" - Status: {error.status}") + print(f" - Details: {error.details}") + print(f" - Errors: {error.errors}\n") return for file in files: - print(f'- {file}') # TODO: nice print + print(f"- {file}") # TODO: nice print @click.command() -@click.argument('id') -@click.argument('credentials', envvar='CREDENTIALS', type=click.Path(exists=True)) +@click.argument("id") +@click.argument("credentials", envvar="CREDENTIALS", type=click.Path(exists=True)) def get(id, credentials): """Get file metadata""" try: @@ -61,28 +59,28 @@ def get(id, credentials): google_file = google_drive.get_file_from_id(id) except GoogleApiClientHttpErrorException as e: error = e.get_google_api_client_http_error() - print(f'An http exception occured requesting google\'s API:\n') - print(f' - Code: {error.code}') - print(f' - Message: {error.message}') - print(f' - Status: {error.status}') - print(f' - Details: {error.details}') - print(f' - Errors: {error.errors}\n') + print(f"An http exception occured requesting google's API:\n") + print(f" - Code: {error.code}") + print(f" - Message: {error.message}") + print(f" - Status: {error.status}") + print(f" - Details: {error.details}") + print(f" - Errors: {error.errors}\n") return - print('\nFile Metadata:\n==') - print(f'id: {google_file.id}') - print(f'name: {google_file.name}') - print(f'parents: {google_file.parents}') - print(f'mime_type: {google_file.mime_type}') - print(f'export_links:') + print("\nFile Metadata:\n==") + print(f"id: {google_file.id}") + print(f"name: {google_file.name}") + print(f"parents: {google_file.parents}") + print(f"mime_type: {google_file.mime_type}") + print(f"export_links:") for link_type, link in google_file.export_links.items(): - print(f' - {link_type}: {link}') + print(f" - {link_type}: {link}") @click.command() -@click.argument('name') -@click.argument('credentials', envvar='CREDENTIALS', type=click.Path(exists=True)) +@click.argument("name") +@click.argument("credentials", envvar="CREDENTIALS", type=click.Path(exists=True)) def mkdir(credentials, name): """Make directory""" try: @@ -90,19 +88,19 @@ def mkdir(credentials, name): folder = google_drive.create_folder(name) except GoogleApiClientHttpErrorException as e: error = e.get_google_api_client_http_error() - print(f'An http exception occured requesting google\'s API:\n') - print(f' - Code: {error.code}') - print(f' - Message: {error.message}') - print(f' - Status: {error.status}') - print(f' - Details: {error.details}') - print(f' - Errors: {error.errors}\n') + print(f"An http exception occured requesting google's API:\n") + print(f" - Code: {error.code}") + print(f" - Message: {error.message}") + print(f" - Status: {error.status}") + print(f" - Details: {error.details}") + print(f" - Errors: {error.errors}\n") return - print(folder) # TODO: nice print + print(folder) # TODO: nice print @click.command() -@click.argument('credentials', envvar='CREDENTIALS', type=click.Path(exists=True)) +@click.argument("credentials", envvar="CREDENTIALS", type=click.Path(exists=True)) def get_mimetypes(credentials): """Get Mimetypes availables in this API implementation""" try: @@ -110,22 +108,22 @@ def get_mimetypes(credentials): mimetypes = google_drive.get_mymetypes() except GoogleApiClientHttpErrorException as e: error = e.get_google_api_client_http_error() - print(f'An http exception occured requesting google\'s API:\n') - print(f' - Code: {error.code}') - print(f' - Message: {error.message}') - print(f' - Status: {error.status}') - print(f' - Details: {error.details}') - print(f' - Errors: {error.errors}\n') + print(f"An http exception occured requesting google's API:\n") + print(f" - Code: {error.code}") + print(f" - Message: {error.message}") + print(f" - Status: {error.status}") + print(f" - Details: {error.details}") + print(f" - Errors: {error.errors}\n") return for mimetype in mimetypes: - print(f' - {mimetype}') # TODO: nice print + print(f" - {mimetype}") # TODO: nice print @click.command() -@click.argument('name') -@click.argument('mymetype') -@click.argument('credentials', envvar='CREDENTIALS', type=click.Path(exists=True)) +@click.argument("name") +@click.argument("mymetype") +@click.argument("credentials", envvar="CREDENTIALS", type=click.Path(exists=True)) def touch(credentials, mymetype, name): """Create empty file of specified mimetype""" try: @@ -133,15 +131,15 @@ def touch(credentials, mymetype, name): file = google_drive.create_file(name=name, mimetype=mymetype) except GoogleApiClientHttpErrorException as e: error = e.get_google_api_client_http_error() - print(f'An http exception occured requesting google\'s API:\n') - print(f' - Code: {error.code}') - print(f' - Message: {error.message}') - print(f' - Status: {error.status}') - print(f' - Details: {error.details}') - print(f' - Errors: {error.errors}\n') + print(f"An http exception occured requesting google's API:\n") + print(f" - Code: {error.code}") + print(f" - Message: {error.message}") + print(f" - Status: {error.status}") + print(f" - Details: {error.details}") + print(f" - Errors: {error.errors}\n") return - print(file) # TODO: nice print + print(file) # TODO: nice print @click.group() diff --git a/googledrive/exceptions.py b/googledrive/exceptions.py index e20be78..054b196 100644 --- a/googledrive/exceptions.py +++ b/googledrive/exceptions.py @@ -10,9 +10,10 @@ def __init__(self, *args): def get_str(self, class_name): if self.message: - return '{0}, {1} '.format(self.message, class_name) + return "{0}, {1} ".format(self.message, class_name) else: - return '{0} has been raised'.format(class_name) + return "{0} has been raised".format(class_name) + class GoogleApiClientHttpErrorException(Exception): def __init__(self, google_api_client_http_error: GoogleApiClientHttpError): @@ -21,12 +22,12 @@ def __init__(self, google_api_client_http_error: GoogleApiClientHttpError): def get_google_api_client_http_error(self): return self.google_api_client_http_error -class MissingGoogleDriveFolderException(CustomException): +class MissingGoogleDriveFolderException(CustomException): def __str__(self): - return super().get_str('MissingGoogleDriveFolderException') + return super().get_str("MissingGoogleDriveFolderException") -class MissingGoogleDriveFileException(CustomException): +class MissingGoogleDriveFileException(CustomException): def __str__(self): - return super().get_str('MissingGoogleDriveFileException') + return super().get_str("MissingGoogleDriveFileException") diff --git a/googledrive/mappers.py b/googledrive/mappers.py index ed3085d..c4c34e6 100644 --- a/googledrive/mappers.py +++ b/googledrive/mappers.py @@ -1,21 +1,21 @@ from googledrive.models import GoogleFile -class GoogleFileDictToGoogleFile: +class GoogleFileDictToGoogleFile: def google_file_dict_to_google_file(self, google_file_dict): if google_file_dict is None: return None - name = google_file_dict.get('name') or '' - id = google_file_dict.get('id') or '' - parents = google_file_dict.get('parents') or [] - mime_type = google_file_dict.get('mimeType') or '' - export_links = google_file_dict.get('exportLinks') or {} + name = google_file_dict.get("name") or "" + id = google_file_dict.get("id") or "" + parents = google_file_dict.get("parents") or [] + mime_type = google_file_dict.get("mimeType") or "" + export_links = google_file_dict.get("exportLinks") or {} return GoogleFile( name=name, id=id, parents=parents, mime_type=mime_type, - export_links=export_links + export_links=export_links, ) diff --git a/googledrive/models.py b/googledrive/models.py index 76cb056..535a4e5 100644 --- a/googledrive/models.py +++ b/googledrive/models.py @@ -3,25 +3,22 @@ from googleapiclient.errors import HttpError + @dataclass class GoogleFile: - EXPORT_TYPE_RTF = 'application/rtf' - EXPORT_TYPE_VND_OASIS = 'application/vnd.oasis.opendocument.text' - EXPORT_TYPE_HTML = 'text/html' - EXPORT_TYPE_PDF = 'application/pdf' - EXPORT_TYPE_EPUB_ZIP = 'application/epub+zip' - EXPORT_TYPE_ZIP = 'application/zip' - EXPORT_TYPE_VND_WORDPROCESSINGML = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' - EXPORT_TYPE_PLAIN = 'text/plain' - - def __init__( - self, - name, - id, - parents, - mime_type, - export_links): + EXPORT_TYPE_RTF = "application/rtf" + EXPORT_TYPE_VND_OASIS = "application/vnd.oasis.opendocument.text" + EXPORT_TYPE_HTML = "text/html" + EXPORT_TYPE_PDF = "application/pdf" + EXPORT_TYPE_EPUB_ZIP = "application/epub+zip" + EXPORT_TYPE_ZIP = "application/zip" + EXPORT_TYPE_VND_WORDPROCESSINGML = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + EXPORT_TYPE_PLAIN = "text/plain" + + def __init__(self, name, id, parents, mime_type, export_links): self.id = id self.name = name self.parents = parents @@ -29,15 +26,15 @@ def __init__( self.export_links = export_links def get_export_link(self, export_type): - export_link = self.export_links[export_type] or '' + export_link = self.export_links[export_type] or "" return export_link def __str__(self): - return f'({self.id}, {self.name}, {self.mime_type})' + return f"({self.id}, {self.name}, {self.mime_type})" + @dataclass class GoogleApiClientHttpError: - def __init__(self, code, message, status, details, errors): self.code = code self.message = message @@ -45,15 +42,15 @@ def __init__(self, code, message, status, details, errors): self.details = details self.errors = errors -class GoogleApiClientHttpErrorBuilder: +class GoogleApiClientHttpErrorBuilder: def from_http_error(self, http_error: HttpError): error_reason = json.loads(http_error.content) - error = error_reason['error'] + error = error_reason["error"] return GoogleApiClientHttpError( - error['code'], - error['message'], - error.get('status', ''), - error.get('details', []), - error.get('errors', []) + error["code"], + error["message"], + error.get("status", ""), + error.get("details", []), + error.get("errors", []), ) diff --git a/setup.py b/setup.py index 4a148a7..de68fc2 100644 --- a/setup.py +++ b/setup.py @@ -15,13 +15,12 @@ def read_requirements(path): with pathlib.Path(path).open() as requirements_txt: return [ str(requirement) - for requirement - in pkg_resources.parse_requirements(requirements_txt) + for requirement in pkg_resources.parse_requirements(requirements_txt) ] -requirements = read_requirements('requirements/prod.txt') -extra_requirements_dev = read_requirements('requirements/dev.txt') +requirements = read_requirements("requirements/prod.txt") +extra_requirements_dev = read_requirements("requirements/dev.txt") setup( @@ -35,19 +34,15 @@ def read_requirements(path): license="Apache", keywords="google drive", url="https://github.com/eduardogr/google-drive-python", - packages=['googledrive'], + packages=["googledrive"], install_requires=requirements, - extras_require={ - 'dev': extra_requirements_dev - }, + extras_require={"dev": extra_requirements_dev}, long_description=read("README.md"), - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", classifiers=[ "Development Status :: 5 - Production/Stable", "Topic :: Software Development :: Libraries", "License :: OSI Approved :: Apache Software License", ], - entry_points={ - 'console_scripts': ['google-drive = googledrive.cli:googledrive'], - }, + entry_points={"console_scripts": ["google-drive = googledrive.cli:googledrive"]}, ) diff --git a/tests/common/mocks.py b/tests/common/mocks.py index 8500141..8f7f1ee 100644 --- a/tests/common/mocks.py +++ b/tests/common/mocks.py @@ -26,25 +26,22 @@ def get_service(self, service_id, service_version): def set_service(self, service_id, service_version, service): if service_id not in self.__services_by_id: - self.__services_by_id.update({ - service_id: {} - }) + self.__services_by_id.update({service_id: {}}) if service_version not in self.__services_by_id[service_id]: - self.__services_by_id[service_id].update({ - service_version: None - }) + self.__services_by_id[service_id].update({service_version: None}) self.__services_by_id[service_id][service_version] = service -class RawSheetsServiceMock: +class RawSheetsServiceMock: def spreadsheets(self): class Spreadsheets: def create(self, body, fields): class Create: def execute(self): return {} + return Create() def values(self): @@ -52,39 +49,31 @@ class Values: def get(self, spreadsheetId, range): class Get: def execute(self): - return { - 'values': ['something'] - } + return {"values": ["something"]} + return Get() - def update( - self, - spreadsheetId, - range, - valueInputOption, - body): + def update(self, spreadsheetId, range, valueInputOption, body): class Update: def execute(self): - return { - 'values': [] - } + return {"values": []} + return Update() + return Values() return Spreadsheets() -class RawGoogleListMock: +class RawGoogleListMock: def __init__(self, files=[]): self.files = files def execute(self): - return { - 'files': self.files - } + return {"files": self.files} -class RawGoogleServiceFilesMock: +class RawGoogleServiceFilesMock: def __init__(self, raw_google_list_by_query): self.raw_google_list_by_query = raw_google_list_by_query @@ -92,12 +81,14 @@ def create(self, body, fields): class Create: def execute(self): return {} + return Create() def update(self, fileId, addParents, removeParents): class Update: def execute(self): return {} + return Update() def list(self, q, pageSize, spaces, corpora, fields, pageToken): @@ -107,10 +98,11 @@ def copy(self, fileId, body): class Copy: def execute(self): return {} + return Copy() -class RawGoogleServiceMock: +class RawGoogleServiceMock: def __init__(self, raw_google_service_files): self.raw_google_service_files = raw_google_service_files @@ -123,28 +115,32 @@ def create(self, fileId, body): class Create: def execute(self): return {} + return Create() + return Permissions() class RawDocsServiceMock: - def documents(self): class Documents: def get(self, documentId): class Execute: def execute(self): return + return Execute() def batchUpdate(self, documentId, body): class Execute: def execute(self): return + return Execute() return Documents() + class MockGoogleDrive(GoogleDrive): calls = {} @@ -167,50 +163,31 @@ def __init__(self): # def create_folder(self, name): - self.__update_calls( - 'create_folder', - params={ - 'name': name - } - ) - return { - 'folder': name - } + self.__update_calls("create_folder", params={"name": name}) + return {"folder": name} def create_file(self, path, mimetype): - self.__update_calls( - 'create_file', - params={ - 'path': path, - 'mimetype': mimetype, - } - ) - return { - 'file': name - } + self.__update_calls("create_file", params={"path": path, "mimetype": mimetype}) + return {"file": name} def update_file_parent(self, file_id, current_parent, new_parent): self.__update_calls( - 'update_file_parent', + "update_file_parent", params={ - 'file_id': file_id, - 'current_parent': current_parent, - 'new_parent': new_parent, - } + "file_id": file_id, + "current_parent": current_parent, + "new_parent": new_parent, + }, ) def list_files(self, page_token: str, query: str): self.__update_calls( - 'list_files', - params={ - 'page_token': page_token, - 'query': query, - } + "list_files", params={"page_token": page_token, "query": query} ) if self.pages_requested > 0: self.pages_requested -= 1 files = self.response_files - next_page_token = 'pagetoken::{}'.format(self.pages_requested) + next_page_token = "pagetoken::{}".format(self.pages_requested) else: files = [] next_page_token = None @@ -222,22 +199,17 @@ def copy_file(self, file_id, new_filename): def create_permission(self, document_id: str, role: str, email_address): self.__update_calls( - 'create_permission', + "create_permission", params={ - 'document_id': document_id, - 'role': role, - 'email_address': email_address - } + "document_id": document_id, + "role": role, + "email_address": email_address, + }, ) return def googledrive_ls(self, path: str): - self.__update_calls( - 'googledrive_ls', - params={ - 'path': path - } - ) + self.__update_calls("googledrive_ls", params={"path": path}) if path in self.googledrive_ls_raise_exceptions: raise MissingGoogleDriveFolderException(f'Path "{path}" does not exist') @@ -245,12 +217,7 @@ def googledrive_ls(self, path: str): return self.googledrive_ls_response.get(path, None) def googledrive_get_file(self, path: str): - self.__update_calls( - 'googledrive_get_file', - params={ - 'path': path - } - ) + self.__update_calls("googledrive_get_file", params={"path": path}) if path in self.googledrive_get_file_raise_exceptions: raise MissingGoogleDriveFolderException(f'File "{path}" does not exist') @@ -268,9 +235,7 @@ def set_pages_requested(self, pages_requested): self.pages_requested = pages_requested def set_googledrive_ls_response(self, path, response): - self.googledrive_ls_response.update({ - path: response - }) + self.googledrive_ls_response.update({path: response}) def set_googledrive_ls_raise_exception(self, path): self.googledrive_ls_raise_exceptions.append(path) @@ -280,9 +245,7 @@ def clear_googledrive_ls_fixture(self): MockGoogleDrive.googledrive_ls_raise_exceptions = [] def set_googledrive_get_file_response(self, path, response): - self.googledrive_get_file_response.update({ - path: response - }) + self.googledrive_get_file_response.update({path: response}) def set_googledrive_get_file_raise_exception(self, path): self.googledrive_get_file_raise_exceptions.append(path) @@ -302,15 +265,10 @@ def __update_calls(self, function, params): sorted_keys.reverse() current_call_number = sorted_keys[0] + 1 - self.calls[function].update({ - current_call_number: params - }) + self.calls[function].update({current_call_number: params}) else: - self.calls.update({ - function: { - current_call_number: params - } - }) + self.calls.update({function: {current_call_number: params}}) + class MockSheetsService(SheetsService): @@ -322,15 +280,8 @@ def __init__(self): self.__get_file_values_will_raise_exception = [] def create_spreadsheet(self, filename): - self.__update_calls( - 'create_spreadsheet', - params={ - 'filename': filename - } - ) - return { - 'spreadsheetId': filename - } + self.__update_calls("create_spreadsheet", params={"filename": filename}) + return {"spreadsheetId": filename} def get_file_values(self, spreadsheet_id, rows_range): if spreadsheet_id in self.__get_file_values_will_raise_exception: @@ -339,32 +290,29 @@ def get_file_values(self, spreadsheet_id, rows_range): message="this is a test error message", status=429, details=[], - errors=[] + errors=[], ) raise GoogleApiClientHttpErrorException(error) self.__update_calls( - 'get_file_values', - params={ - 'spreadsheet_id': spreadsheet_id, - 'rows_range': rows_range, - } + "get_file_values", + params={"spreadsheet_id": spreadsheet_id, "rows_range": rows_range}, ) return self.__get_file_values_response.get(spreadsheet_id, []) - def update_file_values(self, spreadsheet_id, rows_range, value_input_option, values): + def update_file_values( + self, spreadsheet_id, rows_range, value_input_option, values + ): self.__update_calls( - 'update_file_values', + "update_file_values", params={ - 'spreadsheet_id': spreadsheet_id, - 'rows_range': rows_range, - 'value_input_option': value_input_option, - 'values': values, - } + "spreadsheet_id": spreadsheet_id, + "rows_range": rows_range, + "value_input_option": value_input_option, + "values": values, + }, ) - return { - 'values': ['whatever'] - } + return {"values": ["whatever"]} def raise_exception_for_get_file_values_for_ids(self, spreadsheet_collection): self.__get_file_values_will_raise_exception = spreadsheet_collection @@ -373,9 +321,7 @@ def get_calls(self): return self.calls def set_get_file_values_response(self, spreadsheet_id, response): - self.__get_file_values_response.update({ - spreadsheet_id: response - }) + self.__get_file_values_response.update({spreadsheet_id: response}) def __update_calls(self, function, params): current_call_number = 0 @@ -385,15 +331,10 @@ def __update_calls(self, function, params): sorted_keys.reverse() current_call_number = sorted_keys[0] + 1 - self.calls[function].update({ - current_call_number: params - }) + self.calls[function].update({current_call_number: params}) else: - self.calls.update({ - function: { - current_call_number: params - } - }) + self.calls.update({function: {current_call_number: params}}) + class MockDocsService(DocsService): @@ -405,34 +346,27 @@ def __init__(self): self.end_index = 99 self.start_index = 1 - super().__init__('') + super().__init__("") def get_document(self, document_id): - self.__update_calls( - 'get_document', - params={ - 'document_id': document_id, - } - ) + self.__update_calls("get_document", params={"document_id": document_id}) return { - 'body': { - 'content': [ + "body": { + "content": [ { - 'paragraph': { - 'elements': [ + "paragraph": { + "elements": [ { - 'horizontalRule': {}, - 'endIndex': self.end_index, - 'startIndex': self.start_index + "horizontalRule": {}, + "endIndex": self.end_index, + "startIndex": self.start_index, }, { - 'textRun': { - 'content': 'i am content' - }, - 'horizontalRule': {}, - 'endIndex': self.end_index, - 'startIndex': self.start_index - } + "textRun": {"content": "i am content"}, + "horizontalRule": {}, + "endIndex": self.end_index, + "startIndex": self.start_index, + }, ] } } @@ -442,11 +376,7 @@ def get_document(self, document_id): def batch_update(self, document_id, requests): self.__update_calls( - 'batch_update', - params={ - 'document_id': document_id, - 'requests': requests - } + "batch_update", params={"document_id": document_id, "requests": requests} ) def get_calls(self): @@ -466,12 +396,6 @@ def __update_calls(self, function, params): sorted_keys.reverse() current_call_number = sorted_keys[0] + 1 - self.calls[function].update({ - current_call_number: params - }) + self.calls[function].update({current_call_number: params}) else: - self.calls.update({ - function: { - current_call_number: params - } - }) + self.calls.update({function: {current_call_number: params}}) diff --git a/tests/test_api.py b/tests/test_api.py index af24f33..9cef113 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -16,80 +16,70 @@ from tests.common.mocks import MockGoogleDrive, MockSheetsService from tests.common.mocks import MockDocsService + class DocServiceSut(DocsService, MockGoogleService): - 'Inject a mock into the DocsService dependency' + "Inject a mock into the DocsService dependency" def __init__(self): - super().__init__('', []) + super().__init__("", []) + class SheetsServiceSut(SheetsService, MockGoogleService): - 'Inject a mock into the SheetsService dependency' + "Inject a mock into the SheetsService dependency" def __init__(self): - super().__init__('', []) + super().__init__("", []) + class GoogleDriveSut(GoogleDrive, MockGoogleService): - 'Inject a mock into the GoogleDrive dependency' + "Inject a mock into the GoogleDrive dependency" def __init__(self): - super().__init__('', []) + super().__init__("", []) -class FilesAPISut( - FilesAPI, - MockGoogleDrive, - MockSheetsService, - MockDocsService): - 'Inject mocks into FilesAPI dependencies' -class TestGoogleDrive(TestCase): +class FilesAPISut(FilesAPI, MockGoogleDrive, MockSheetsService, MockDocsService): + "Inject mocks into FilesAPI dependencies" + +class TestGoogleDrive(TestCase): def setUp(self): self.sut = GoogleDriveSut() - files_by_query = { - '': RawGoogleListMock() - } + files_by_query = {"": RawGoogleListMock()} raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) def test_create_folder_ok(self): - name = '' + name = "" self.sut.create_folder(name) def test_create_file_when_no_parent_ok(self): - path = 'filename' + path = "filename" mimetype = GoogleDrive.MIMETYPE_DOCUMENT self.sut.create_file(path, mimetype) def test_create_file_when_parent_ok(self): # given: - parent_folder = 'parentfolder' + parent_folder = "parentfolder" path = f"{parent_folder}/filename" mimetype = GoogleDrive.MIMETYPE_DOCUMENT - files = [ - { - 'name': parent_folder, - 'id': parent_folder, - 'parents': [] - } - ] - files_by_query = { - GoogleDrive.QUERY_IS_FOLDER: RawGoogleListMock(files), - } + files = [{"name": parent_folder, "id": parent_folder, "parents": []}] + files_by_query = {GoogleDrive.QUERY_IS_FOLDER: RawGoogleListMock(files)} raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: @@ -98,32 +88,20 @@ def test_create_file_when_parent_ok(self): def test_googledrive_ls_when_folder_no_exists(self): # when: with self.assertRaises(MissingGoogleDriveFolderException): - self.sut.googledrive_ls('/unexistent') + self.sut.googledrive_ls("/unexistent") def test_googledrive_ls_when_for(self): # given: - files = [ - { - 'name': 'basefolder', - 'id': 'basefolder', - 'parents': [] - } - ] + files = [{"name": "basefolder", "id": "basefolder", "parents": []}] listed_files = [ - { - 'name': 'file_1', - 'id': 'file_1', - 'parents': ['basefolder'] - }, - { - 'name': 'file_2', - 'id': 'file_2', - 'parents': ['basefolder'] - } + {"name": "file_1", "id": "file_1", "parents": ["basefolder"]}, + {"name": "file_2", "id": "file_2", "parents": ["basefolder"]}, ] files_by_query = { "mimeType='application/vnd.google-apps.folder'": RawGoogleListMock(files), - f"{GoogleDrive.QUERY_IS_FILE} and 'basefolder' in parents": RawGoogleListMock(listed_files) + f"{GoogleDrive.QUERY_IS_FILE} and 'basefolder' in parents": RawGoogleListMock( + listed_files + ), } raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) @@ -131,26 +109,22 @@ def test_googledrive_ls_when_for(self): self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: - files = self.sut.googledrive_ls('/basefolder') + files = self.sut.googledrive_ls("/basefolder") # then: self.assertEqual(2, len(files)) def test_googledrive_ls_when_for_none(self): # given: - listed_files = [ - { - 'name': 'something', - 'id': 'something', - 'parents': [] - } - ] + listed_files = [{"name": "something", "id": "something", "parents": []}] files_by_query = { - "mimeType='application/vnd.google-apps.folder'": RawGoogleListMock(listed_files) + "mimeType='application/vnd.google-apps.folder'": RawGoogleListMock( + listed_files + ) } raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) @@ -158,48 +132,38 @@ def test_googledrive_ls_when_for_none(self): self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: with self.assertRaises(MissingGoogleDriveFolderException): - self.sut.googledrive_ls('/something/unexistent') + self.sut.googledrive_ls("/something/unexistent") def test_googledrive_ls_when_no_for(self): # given: listed_files = [ - { - 'name': 'file_1', - 'id': 'file_1', - 'parents': [] - }, - { - 'name': 'file_2', - 'id': 'file_2', - 'parents': [] - } + {"name": "file_1", "id": "file_1", "parents": []}, + {"name": "file_2", "id": "file_2", "parents": []}, ] - files_by_query = { - GoogleDrive.QUERY_IS_FILE: RawGoogleListMock(listed_files) - } + files_by_query = {GoogleDrive.QUERY_IS_FILE: RawGoogleListMock(listed_files)} raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: - files = self.sut.googledrive_ls('/') + files = self.sut.googledrive_ls("/") # then: self.assertEqual(2, len(files)) def test_googledrive_get_file_when_incorrect_path(self): # when: - google_file = self.sut.googledrive_get_file('/') + google_file = self.sut.googledrive_get_file("/") # then: self.assertEqual(None, google_file) @@ -207,38 +171,26 @@ def test_googledrive_get_file_when_incorrect_path(self): def test_googledrive_get_file_when_unexistent_file(self): # when: with self.assertRaises(MissingGoogleDriveFolderException): - self.sut.googledrive_get_file('/unexistent/path/file') + self.sut.googledrive_get_file("/unexistent/path/file") def test_googledrive_get_file_for(self): # given: files = [ - { - 'name': 'base', - 'id': 'base', - 'parents': [] - }, - { - 'name': 'path', - 'id': 'path', - 'parents': ['base'] - } + {"name": "base", "id": "base", "parents": []}, + {"name": "path", "id": "path", "parents": ["base"]}, ] listed_files = [ - { - 'name': 'file_1', - 'id': 'file_1', - 'parents': ['path'] - }, - { - 'name': 'existent_file', - 'id': 'existent_file', - 'parents': ['path'] - } + {"name": "file_1", "id": "file_1", "parents": ["path"]}, + {"name": "existent_file", "id": "existent_file", "parents": ["path"]}, ] files_by_query = { GoogleDrive.QUERY_IS_FOLDER: RawGoogleListMock(files), - f"{GoogleDrive.QUERY_IS_FOLDER} and 'base' in parents": RawGoogleListMock(files), - f"{GoogleDrive.QUERY_IS_FILE} and 'path' in parents": RawGoogleListMock(listed_files) + f"{GoogleDrive.QUERY_IS_FOLDER} and 'base' in parents": RawGoogleListMock( + files + ), + f"{GoogleDrive.QUERY_IS_FILE} and 'path' in parents": RawGoogleListMock( + listed_files + ), } raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) @@ -246,23 +198,17 @@ def test_googledrive_get_file_for(self): self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: - file = self.sut.googledrive_get_file('/base/path/existent_file') + file = self.sut.googledrive_get_file("/base/path/existent_file") - self.assertEqual('existent_file', file.name) + self.assertEqual("existent_file", file.name) def test_googledrive_get_file_for_none(self): # given: - files = [ - { - 'name': 'base', - 'id': 'base', - 'parents': [] - } - ] + files = [{"name": "base", "id": "base", "parents": []}] files_by_query = { GoogleDrive.QUERY_IS_FOLDER: RawGoogleListMock(files), f"{GoogleDrive.QUERY_IS_FOLDER} and 'base' in parents": RawGoogleListMock(), @@ -273,37 +219,25 @@ def test_googledrive_get_file_for_none(self): self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: with self.assertRaises(MissingGoogleDriveFolderException): - self.sut.googledrive_get_file('/base/unexistent/unexistent_file') + self.sut.googledrive_get_file("/base/unexistent/unexistent_file") def test_googledrive_get_file_no_for(self): # given: - files = [ - { - 'name': 'base', - 'id': 'base', - 'parents': [] - } - ] + files = [{"name": "base", "id": "base", "parents": []}] listed_files = [ - { - 'name': 'file_1', - 'id': 'file_1', - 'parents': ['path'] - }, - { - 'name': 'existent_file', - 'id': 'existent_file', - 'parents': ['path'] - } + {"name": "file_1", "id": "file_1", "parents": ["path"]}, + {"name": "existent_file", "id": "existent_file", "parents": ["path"]}, ] files_by_query = { GoogleDrive.QUERY_IS_FOLDER: RawGoogleListMock(files), - f"{GoogleDrive.QUERY_IS_FILE} and 'base' in parents": RawGoogleListMock(listed_files) + f"{GoogleDrive.QUERY_IS_FILE} and 'base' in parents": RawGoogleListMock( + listed_files + ), } raw_google_service_files = RawGoogleServiceFilesMock(files_by_query) drive_service = RawGoogleServiceMock(raw_google_service_files) @@ -311,49 +245,48 @@ def test_googledrive_get_file_no_for(self): self.sut.set_service( GoogleDrive.DRIVE_SERVICE_ID, GoogleDrive.DRIVE_SERVICE_VERSION, - drive_service + drive_service, ) # when: - file = self.sut.googledrive_get_file('/base/existent_file') + file = self.sut.googledrive_get_file("/base/existent_file") - self.assertEqual('existent_file', file.name) + self.assertEqual("existent_file", file.name) def test_update_file_parent_ok(self): - file_id = 'ID' - current_parent = 'father' - new_parent = 'i am your father' + file_id = "ID" + current_parent = "father" + new_parent = "i am your father" self.sut.update_file_parent(file_id, current_parent, new_parent) def test_list_files_ok(self): - page_token = '' - query = '' + page_token = "" + query = "" self.sut.list_files(page_token, query) def test_copy_file_ok(self): - file_id = 'EFAWEF' - new_filename = 'brand new file' + file_id = "EFAWEF" + new_filename = "brand new file" self.sut.copy_file(file_id, new_filename) def test_create_permission_ok(self): - document_id = 'ASDFASDF' - role = 'comenter' - email_address = 'myemail@email.com' + document_id = "ASDFASDF" + role = "comenter" + email_address = "myemail@email.com" self.sut.create_permission(document_id, role, email_address) class TestSheetsService(TestCase): - def setUp(self): self.sut = SheetsServiceSut() self.sut.set_service( SheetsService.SHEETS_SERVICE_ID, SheetsService.SHEETS_SERVICE_VERSION, - RawSheetsServiceMock() + RawSheetsServiceMock(), ) def test_create_spreadsheet_ok(self): @@ -361,49 +294,49 @@ def test_create_spreadsheet_ok(self): self.sut.create_spreadsheet(file_metadata) def test_get_file_values_ok(self): - spreadsheet_id = 'id' - self.sut.get_file_values(spreadsheet_id, 'A1:D3') + spreadsheet_id = "id" + self.sut.get_file_values(spreadsheet_id, "A1:D3") def test_get_file_values_ok_when_cached(self): - spreadsheet_id = 'id' + spreadsheet_id = "id" - self.sut.get_file_values(spreadsheet_id, 'A1:D3') - self.sut.get_file_values(spreadsheet_id, 'A1:D3') + self.sut.get_file_values(spreadsheet_id, "A1:D3") + self.sut.get_file_values(spreadsheet_id, "A1:D3") # TODO: do assertion for cache def test_update_file_values_ok(self): - spreadsheet_id = 'id' - rows_range = 'A3:D4' - value_input_option = 'RAW' + spreadsheet_id = "id" + rows_range = "A3:D4" + value_input_option = "RAW" values = [] - self.sut.update_file_values(spreadsheet_id, rows_range, value_input_option, values) + self.sut.update_file_values( + spreadsheet_id, rows_range, value_input_option, values + ) class TestDocsService(TestCase): - def setUp(self): self.sut = DocServiceSut() self.sut.set_service( DocsService.DOCS_SERVICE_ID, DocsService.DOCS_SERVICE_VERSION, - RawDocsServiceMock() + RawDocsServiceMock(), ) def test_get_document_ok(self): - document_id = 'ID' + document_id = "ID" self.sut.get_document(document_id) def test_batch_update(self): - document_id = 'ID' + document_id = "ID" requests = [] self.sut.batch_update(document_id, requests) class TestFilesAPI(TestCase): - def setUp(self): self.sut = FilesAPISut() self.sut.raise_exception_for_get_file_values_for_ids([]) @@ -414,7 +347,7 @@ def tearDown(self): def test_create_folder(self): # given: - folder_name = 'test folder' + folder_name = "test folder" # when: self.sut.create_folder(folder_name) @@ -422,19 +355,19 @@ def test_create_folder(self): # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('create_folder', calls) + self.assertIn("create_folder", calls) - call = calls['create_folder'][0] - self.assertIn('name', call) - self.assertEqual( - folder_name, - call['name']) + call = calls["create_folder"][0] + self.assertIn("name", call) + self.assertEqual(folder_name, call["name"]) def test_create_spreadsheet(self): # given: - folder_parent = 'parent' - filename = 'filename' - folder = GoogleFile(id='new_parent_id', name='folder', parents=[], mime_type='', export_links={}) + folder_parent = "parent" + filename = "filename" + folder = GoogleFile( + id="new_parent_id", name="folder", parents=[], mime_type="", export_links={} + ) # when: self.sut.create_sheet(folder_parent, folder, filename) @@ -443,32 +376,24 @@ def test_create_spreadsheet(self): calls = self.sut.get_calls() self.assertEqual(2, len(calls)) - self.assertIn('create_spreadsheet', calls) - self.assertIn('update_file_parent', calls) - - call = calls['create_spreadsheet'][0] - self.assertIn('filename', call) - self.assertEqual( - filename, - call['filename']) - - call = calls['update_file_parent'][0] - self.assertIn('file_id', call) - self.assertIn('current_parent', call) - self.assertIn('new_parent', call) - self.assertEqual( - filename, - call['file_id']) - self.assertEqual( - folder_parent, - call['current_parent']) - self.assertEqual( - 'new_parent_id', - call['new_parent']) + self.assertIn("create_spreadsheet", calls) + self.assertIn("update_file_parent", calls) + + call = calls["create_spreadsheet"][0] + self.assertIn("filename", call) + self.assertEqual(filename, call["filename"]) + + call = calls["update_file_parent"][0] + self.assertIn("file_id", call) + self.assertIn("current_parent", call) + self.assertIn("new_parent", call) + self.assertEqual(filename, call["file_id"]) + self.assertEqual(folder_parent, call["current_parent"]) + self.assertEqual("new_parent_id", call["new_parent"]) def test_get_folder_when_not_exists(self): # given: - folder_name = 'name' + folder_name = "name" self.sut.set_pages_requested(0) # when: @@ -482,11 +407,19 @@ def test_get_folder_when_not_exists(self): def test_correct_number_executions_when_get_folder_and_exists(self): # given: - folder_name = 'my_folder' + folder_name = "my_folder" self.sut.set_pages_requested(1) - self.sut.set_response_files([ - GoogleFile(id='some id', name=folder_name, parents=[], mime_type='', export_links={}) - ]) + self.sut.set_response_files( + [ + GoogleFile( + id="some id", + name=folder_name, + parents=[], + mime_type="", + export_links={}, + ) + ] + ) # when: folder = self.sut.get_folder(folder_name) @@ -494,13 +427,13 @@ def test_correct_number_executions_when_get_folder_and_exists(self): # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('list_files', calls) + self.assertIn("list_files", calls) self.assertEqual(folder_name, folder.name) def test_correct_query_when_get_folder(self): # given: - folder_name = 'name' + folder_name = "name" # when: self.sut.get_folder(folder_name) @@ -508,22 +441,22 @@ def test_correct_query_when_get_folder(self): # when: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('list_files', calls) + self.assertIn("list_files", calls) - self.assertEqual( - GoogleDrive.QUERY_IS_FOLDER, - calls['list_files'][0]['query'] - ) + self.assertEqual(GoogleDrive.QUERY_IS_FOLDER, calls["list_files"][0]["query"]) def test_get_file_rows_from_folder(self): # given: - foldername = 'my_folder' - filename = 'filename' - file_id = 'file_id' + foldername = "my_folder" + filename = "filename" + file_id = "file_id" self.sut.set_googledrive_get_file_response( f"/{foldername}/{filename}", - GoogleFile(id=file_id, name=filename, parents=[], mime_type='', export_links={})) - rows_range = 'A2::F4' + GoogleFile( + id=file_id, name=filename, parents=[], mime_type="", export_links={} + ), + ) + rows_range = "A2::F4" # when: self.sut.get_file_rows_from_folder(foldername, filename, rows_range) @@ -532,19 +465,19 @@ def test_get_file_rows_from_folder(self): calls = self.sut.get_calls() self.assertEqual(2, len(calls)) - self.assertIn('googledrive_get_file', calls) - self.assertEqual(1, len(calls['googledrive_get_file'])) - self.assertIn('get_file_values', calls) - self.assertEqual(1, len(calls['get_file_values'])) + self.assertIn("googledrive_get_file", calls) + self.assertEqual(1, len(calls["googledrive_get_file"])) + self.assertIn("get_file_values", calls) + self.assertEqual(1, len(calls["get_file_values"])) - call = calls['get_file_values'][0] - self.assertEqual(file_id, call['spreadsheet_id']) + call = calls["get_file_values"][0] + self.assertEqual(file_id, call["spreadsheet_id"]) def test_get_file_rows_from_folder_when_missing_google_folder_exception(self): # given: - foldername = 'my_folder' - filename = 'filename' - rows_range = 'A2::F4' + foldername = "my_folder" + filename = "filename" + rows_range = "A2::F4" self.sut.set_googledrive_get_file_raise_exception(f"/{foldername}/{filename}") # when: @@ -554,17 +487,15 @@ def test_get_file_rows_from_folder_when_missing_google_folder_exception(self): # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('googledrive_get_file', calls) - self.assertEqual(1, len(calls['googledrive_get_file'])) + self.assertIn("googledrive_get_file", calls) + self.assertEqual(1, len(calls["googledrive_get_file"])) def test_get_file_rows_from_folder_when_missing_google_file_exception(self): # given: - foldername = 'my_folder' - filename = 'filename' - self.sut.set_googledrive_get_file_response( - f"/{foldername}/{filename}", - None) - rows_range = 'A2::F4' + foldername = "my_folder" + filename = "filename" + self.sut.set_googledrive_get_file_response(f"/{foldername}/{filename}", None) + rows_range = "A2::F4" # when: with self.assertRaises(MissingGoogleDriveFileException): @@ -573,13 +504,13 @@ def test_get_file_rows_from_folder_when_missing_google_file_exception(self): # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('googledrive_get_file', calls) - self.assertEqual(1, len(calls['googledrive_get_file'])) + self.assertIn("googledrive_get_file", calls) + self.assertEqual(1, len(calls["googledrive_get_file"])) def test_get_file_rows_when_ok(self): # given: - file_id = 'file_id' - rows_range = 'A2::F4' + file_id = "file_id" + rows_range = "A2::F4" # when: self.sut.get_file_values(file_id, rows_range) @@ -587,13 +518,13 @@ def test_get_file_rows_when_ok(self): # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('get_file_values', calls) - self.assertEqual(1, len(calls['get_file_values'])) + self.assertIn("get_file_values", calls) + self.assertEqual(1, len(calls["get_file_values"])) def test_get_file_rows_when_http_error(self): # given: - file_id = 'file_id' - rows_range = 'A2::F4' + file_id = "file_id" + rows_range = "A2::F4" self.sut.raise_exception_for_get_file_values_for_ids([file_id]) # when: @@ -606,21 +537,21 @@ def test_get_file_rows_when_http_error(self): def test_update_file_values_when_ok(self): # given: - file_id = 'file_id' - rows_range = 'A2::F4' + file_id = "file_id" + rows_range = "A2::F4" # when: - self.sut.update_file_values(file_id, rows_range, 'RAW', []) + self.sut.update_file_values(file_id, rows_range, "RAW", []) # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('update_file_values', calls) - self.assertEqual(1, len(calls['update_file_values'])) + self.assertIn("update_file_values", calls) + self.assertEqual(1, len(calls["update_file_values"])) def test_empty_document_when_empty_file(self): # given: - document_id = 'ID' + document_id = "ID" start_index = 4 end_index = 0 @@ -630,11 +561,11 @@ def test_empty_document_when_empty_file(self): # then: calls = self.sut.get_calls() self.assertEqual(1, len(calls)) - self.assertIn('get_document', calls) + self.assertIn("get_document", calls) def test_empty_document_when_document_exist(self): # given: - document_id = 'ID' + document_id = "ID" start_index = 0 end_index = 4 self.sut.set_start_index(start_index) @@ -646,5 +577,5 @@ def test_empty_document_when_document_exist(self): # then: calls = self.sut.get_calls() self.assertEqual(2, len(calls)) - self.assertIn('get_document', calls) - self.assertIn('batch_update', calls) + self.assertIn("get_document", calls) + self.assertIn("batch_update", calls) diff --git a/tests/test_mappers.py b/tests/test_mappers.py index 391e31b..3ec8f61 100644 --- a/tests/test_mappers.py +++ b/tests/test_mappers.py @@ -5,20 +5,12 @@ class TestGoogleFileDictToGoogleFile(TestCase): - def test_to_json(self): # given: - google_file_dict = { - 'name': 'id', - 'id': 'name', - 'parents': '[]' - } + google_file_dict = {"name": "id", "id": "name", "parents": "[]"} expected_google_file = GoogleFile( - id='id', - name='name', - parents=[], - mime_type='', - export_links={}) + id="id", name="name", parents=[], mime_type="", export_links={} + ) sut = GoogleFileDictToGoogleFile() # when: