onedrived/od_tasks/merge_dir.py
File `merge_dir.py` has 467 lines of code (exceeds 250 allowed). Consider refactoring.import itertoolsimport loggingimport osimport shutil import onedrivesdk.errorfrom onedrivesdk import Item, Folder, ChildrenCollectionRequestfrom send2trash import send2trash from . import basefrom . import delete_item, download_file, upload_filefrom .. import mkdir, fix_owner_and_timestampfrom ..od_api_helper import get_item_modified_datetime, item_request_callfrom ..od_dateutils import datetime_to_timestamp, diff_timestampsfrom ..od_hashutils import hash_match, sha1_valuefrom ..od_repo import ItemRecordType Cyclomatic complexity is too high in function rename_with_suffix. (7)
Function `rename_with_suffix` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.def rename_with_suffix(parent_abspath, name, host_name): suffix = ' (' + host_name + ')' parent_abspath = parent_abspath + '/' # Calculate the file name without suffix. ent_name, ent_ext = os.path.splitext(name) if ent_name.endswith(suffix): ent_name = ent_name[:-len(suffix)] new_name = ent_name + suffix + ent_ext if os.path.exists(parent_abspath + new_name): count = 1 if ' ' in ent_name: ent_name, count_str = ent_name.rsplit(' ', maxsplit=1) if count_str.isdigit() and count_str[0] != '0': count = int(count_str) + 1Identical blocks of code found in 2 locations. Consider refactoring. new_name = ent_name + ' ' + str(count) + suffix + ent_ext while os.path.exists(parent_abspath + new_name): count += 1Identical blocks of code found in 2 locations. Consider refactoring. new_name = ent_name + ' ' + str(count) + suffix + ent_ext shutil.move(parent_abspath + name, parent_abspath + new_name) return new_name def get_os_stat(path): try: return os.stat(path) except FileNotFoundError: return None Cyclomatic complexity is too high in class MergeDirectoryTask. (8)class MergeDirectoryTask(base.TaskBase): Function `__init__` has 7 arguments (exceeds 4 allowed). Consider refactoring. def __init__(self, repo, task_pool, rel_path, item_request, deep_merge=True, assume_remote_unchanged=False, parent_remote_unchanged=False): """ :param onedrived.od_repo.OneDriveLocalRepository repo: :param onedrived.od_task.TaskPool task_pool: :param str rel_path: Path of the target item relative to repository root. Assume not ending with '/'. :param onedrivesdk.request.item_request_builder.ItemRequestBuilder item_request: :param True | False deep_merge: If False, only sync files under the specified directory. :param True | False assume_remote_unchanged: If True, assume there is no change in remote repository. Can be set True if ctag and etag of the folder Item match its database record. :param True | False parent_remote_unchanged: If parent remote dir item wasn't changed. """ super().__init__(repo, task_pool) self.rel_path = rel_path self.item_request = item_request self.local_abspath = repo.local_root + rel_path self.deep_merge = deep_merge self.assume_remote_unchanged = assume_remote_unchanged self.parent_remote_unchanged = parent_remote_unchanged def __repr__(self): return type(self).__name__ + '(%s, deep=%s, remote_unchanged=%s, parent_remote_unchanged=%s)' % ( self.local_abspath, self.deep_merge, self.assume_remote_unchanged, self.parent_remote_unchanged) Cyclomatic complexity is too high in method list_local_names. (8)
Function `list_local_names` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring. def list_local_names(self): """ List all names under the task local directory. Try resolving naming conflict (same name case-INsensitive) as it goes. :return [str]: A list of entry names. """TODO found # TODO: This logic can be improved if remote info is provided. ents_orig = os.listdir(self.local_abspath) ents_lower = [s.lower() for s in ents_orig] ents_lower_uniq = set(ents_lower) if len(ents_orig) == len(ents_lower_uniq): return set(ents_orig) ents_ret = set() ents_ret_lower = set() for ent, ent_lower in zip(ents_orig, ents_lower): ent_abspath = self.local_abspath + '/' + ent if ent_lower in ents_ret_lower: ent_name, ent_ext = os.path.splitext(ent) count = 1 new_ent = ent_name + ' ' + str(count) + ent_ext new_ent_lower = new_ent.lower() while new_ent_lower in ents_ret_lower or new_ent_lower in ents_lower_uniq: count += 1 new_ent = ent_name + ' ' + str(count) + ent_ext new_ent_lower = new_ent.lower() try: shutil.move(ent_abspath, self.local_abspath + '/' + new_ent) ents_ret.add(new_ent) ents_ret_lower.add(new_ent_lower) except (IOError, OSError) as e: logging.error('Error occurred when solving name conflict of "%s": %s.', ent_abspath, e) continue else: ents_ret.add(ent) ents_ret_lower.add(ent_lower) return ents_ret Cyclomatic complexity is too high in method handle. (12)
Function `handle` has a Cognitive Complexity of 18 (exceeds 5 allowed). Consider refactoring. def handle(self): if not os.path.isdir(self.local_abspath): logging.error('Error: Local path "%s" is not a directory.' % self.local_abspath) return self.repo.context.watcher.rm_watch(self.repo, self.local_abspath) try: all_local_items = self.list_local_names() except (IOError, OSError) as e: logging.error('Error merging dir "%s": %s.', self.local_abspath, e) return all_records = self.repo.get_immediate_children_of_dir(self.rel_path) if not self.assume_remote_unchanged or not self.parent_remote_unchanged: try: remote_item_page = item_request_call(self.repo, self.item_request.children.get) all_remote_items = remote_item_page while True:HACK found # HACK: ChildrenCollectionPage is not guaranteed to have # the _next_page_link attribute and # ChildrenCollectionPage.get_next_page_request doesn't # implement the check correctly if not hasattr(remote_item_page, '_next_page_link'): break logging.debug('Paging for more items: %s', self.rel_path) remote_item_page = item_request_call( self.repo, ChildrenCollectionRequest.get_next_page_request( remote_item_page, self.repo.authenticator.client).get) all_remote_items = itertools.chain( all_remote_items, remote_item_page) except onedrivesdk.error.OneDriveError as e: logging.error('Encountered API Error: %s. Skip directory "%s".', e, self.rel_path) return for remote_item in all_remote_items: remote_is_folder = remote_item.folder is not None all_local_items.discard(remote_item.name) # Remove remote item from untouched list. if not self.repo.path_filter.should_ignore(self.rel_path + '/' + remote_item.name, remote_is_folder): self._handle_remote_item(remote_item, all_local_items, all_records) else: logging.debug('Ignored remote path "%s/%s".', self.rel_path, remote_item.name) for n in all_local_items: self._handle_local_item(n, all_records) for rec_name, rec in all_records.items(): logging.info('Record for item %s (%s/%s) is dead. Delete it it.', rec.item_id, rec.parent_path, rec_name) self.repo.delete_item(rec_name, rec.parent_path, is_folder=rec.type == ItemRecordType.FOLDER) self.repo.context.watcher.add_watch(self.repo, self.local_abspath) def _rename_local_and_download_remote(self, remote_item, all_local_items): all_local_items.add(rename_with_suffix(self.local_abspath, remote_item.name, self.repo.context.host_name)) self.task_pool.add_task( download_file.DownloadFileTask(self.repo, self.task_pool, remote_item, self.rel_path)) Function `_handle_remote_file_with_record` has a Cognitive Complexity of 42 (exceeds 5 allowed). Consider refactoring.
Cyclomatic complexity is too high in method _handle_remote_file_with_record. (27)
Function `_handle_remote_file_with_record` has 5 arguments (exceeds 4 allowed). Consider refactoring. def _handle_remote_file_with_record(self, remote_item, item_record, item_stat, item_local_abspath, all_local_items): """ :param onedrivesdk.model.item.Item remote_item: :param onedrived.od_repo.ItemRecord item_record: :param posix.stat_result | None item_stat: :param str item_local_abspath: :param [str] all_local_items: """ # In this case we have all three pieces of information -- remote item metadata, database record, and local inode # stats. The best case is that all of them agree, and the worst case is that they all disagree. if os.path.isdir(item_local_abspath): # Remote item is a file yet the local item is a folder. if item_record and item_record.type == ItemRecordType.FOLDER:TODO found # TODO: Use the logic in handle_local_folder to solve this. send2trash(item_local_abspath) self.repo.delete_item(remote_item.name, self.rel_path, True) else: # When db record does not exist or says the path is a file, then it does not agree with local inode # and the information is useless. We delete it and sync both remote and local items. if item_record: self.repo.delete_item(remote_item.name, self.rel_path, False) return self._handle_remote_file_without_record(remote_item, None, item_local_abspath, all_local_items) remote_mtime, _ = get_item_modified_datetime(remote_item) local_mtime_ts = item_stat.st_mtime if item_stat else None remote_mtime_ts = datetime_to_timestamp(remote_mtime) record_mtime_ts = datetime_to_timestamp(item_record.modified_time) try: remote_sha1_hash = remote_item.file.hashes.sha1_hash except AttributeError: remote_sha1_hash = None Line break after binary operator if (remote_item.id == item_record.item_id and remote_item.c_tag == item_record.c_tag orLine break after binary operator remote_item.size == item_record.size and diff_timestamps(remote_mtime_ts, record_mtime_ts) == 0): # The remote item metadata matches the database record. So this item has been synced before. if item_stat is None: # The local file was synced but now is gone. Delete remote one as well. logging.debug('Local file "%s" is gone but remote item matches db record. Delete remote item.', item_local_abspath)Similar blocks of code found in 2 locations. Consider refactoring. self.task_pool.add_task(delete_item.DeleteRemoteItemTask( self.repo, self.task_pool, self.rel_path, remote_item.name, remote_item.id, False))Line break after binary operator elif (item_stat.st_size == item_record.size_local andLine break after binary operator (diff_timestamps(local_mtime_ts, record_mtime_ts) == 0 or remote_sha1_hash and remote_sha1_hash == sha1_value(item_local_abspath))): # If the local file matches the database record (i.e., same mtime timestamp or same content), # simply return. This is the best case. if diff_timestamps(local_mtime_ts, remote_mtime_ts) != 0: logging.info('File "%s" seems to have same content but different timestamp (%f, %f). Fix it.', item_local_abspath, local_mtime_ts, remote_mtime_ts) fix_owner_and_timestamp(item_local_abspath, self.repo.context.user_uid, remote_mtime_ts) self.repo.update_item(remote_item, self.rel_path, item_stat.st_size) else: # Content of local file has changed. Because we assume the remote item was synced before, we overwrite # the remote item with local one. # API Issue: size field may not match file size. # Refer to https://github.com/OneDrive/onedrive-sdk-python/issues/88 # Workaround -- storing both remote and local sizes. logging.debug('File "%s" was changed locally and the remote version is known old. Upload it.', item_local_abspath) self.task_pool.add_task(upload_file.UploadFileTask( self.repo, self.task_pool, self.item_request, self.rel_path, remote_item.name)) else: # The remote file metadata and database record disagree.Similar blocks of code found in 2 locations. Consider refactoring. if item_stat is None: # If the remote file is the one on record, then the remote one is newer than the deleted local file # so it should be downloaded. If they are not the same, then the remote one should definitely # be kept. So the remote file needs to be kept and downloaded anyway. logging.debug('Local file "%s" is gone but remote item disagrees with db record. Download it.', item_local_abspath) self.task_pool.add_task( download_file.DownloadFileTask(self.repo, self.task_pool, remote_item, self.rel_path))Similar blocks of code found in 2 locations. Consider refactoring. elif item_stat.st_size == item_record.size_local and \Line break after binary operator (diff_timestamps(local_mtime_ts, record_mtime_ts) == 0 or item_record.sha1_hash and item_record.sha1_hash == sha1_value(item_local_abspath)): # Local file agrees with database record. This means that the remote file is strictly newer. # The local file can be safely overwritten. logging.debug('Local file "%s" agrees with db record but remote item is different. Overwrite local.', item_local_abspath) self.task_pool.add_task( download_file.DownloadFileTask(self.repo, self.task_pool, remote_item, self.rel_path)) else: # So both the local file and remote file have been changed after the record was created. equal_ts = diff_timestamps(local_mtime_ts, remote_mtime_ts) == 0 if (item_stat.st_size == remote_item.size and ( (equal_ts or remote_sha1_hash and remote_sha1_hash == sha1_value(item_local_abspath)))): # Fortunately the two files seem to be the same. # Here the logic is written as if there is no size mismatch issue. logging.debug( 'Local file "%s" seems to have same content with remote but record disagrees. Fix db record.', item_local_abspath) if not equal_ts: fix_owner_and_timestamp(item_local_abspath, self.repo.context.user_uid, remote_mtime_ts) self.repo.update_item(remote_item, self.rel_path, item_stat.st_size) else: # Worst case we keep both files. logging.debug('Local file "%s" differs from db record and remote item. Keep both versions.', item_local_abspath) self._rename_local_and_download_remote(remote_item, all_local_items) Cyclomatic complexity is too high in method _handle_remote_file_without_record. (7)
Function `_handle_remote_file_without_record` has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring. def _handle_remote_file_without_record(self, remote_item, item_stat, item_local_abspath, all_local_items): """ Handle the case in which a remote item is not found in the database. The local item may or may not exist. :param onedrivesdk.model.item.Item remote_item: :param posix.stat_result | None item_stat: :param str item_local_abspath: :param [str] all_local_items: """ if item_stat is None: # The file does not exist locally, and there is no record in database. The safest approach is probably # download the file and update record. self.task_pool.add_task( download_file.DownloadFileTask(self.repo, self.task_pool, remote_item, self.rel_path)) elif os.path.isdir(item_local_abspath): # Remote path is file yet local path is a dir. logging.info('Path "%s" is a folder yet the remote item is a file. Keep both.', item_local_abspath) self._rename_local_and_download_remote(remote_item, all_local_items) else: # We first compare timestamp and size -- if both properties match then we think the items are identical # and just update the database record. Otherwise if sizes are equal, we calculate hash of local item to # determine if they are the same. If so we update timestamp of local item and update database record. # If the remote item has different hash, then we rename the local one and download the remote one so that no # information is lost. remote_mtime, remote_mtime_w = get_item_modified_datetime(remote_item) remote_mtime_ts = datetime_to_timestamp(remote_mtime) equal_ts = diff_timestamps(remote_mtime_ts, item_stat.st_mtime) == 0 equal_attr = remote_item.size == item_stat.st_size and equal_ts # Because of the size mismatch issue, we can't use size not being equal as a shortcut for hash not being # equal. When the bug is fixed we can do it. if equal_attr or hash_match(item_local_abspath, remote_item): if not equal_ts: logging.info('Local file "%s" has same content but wrong timestamp. ' 'Remote: mtime=%s, w=%s, ts=%s, size=%d. ' 'Local: ts=%s, size=%d. Fix it.', item_local_abspath, remote_mtime, remote_mtime_w, remote_mtime_ts, remote_item.size, item_stat.st_mtime, item_stat.st_size) fix_owner_and_timestamp(item_local_abspath, self.repo.context.user_uid, remote_mtime_ts) self.repo.update_item(remote_item, self.rel_path, item_stat.st_size) else: self._rename_local_and_download_remote(remote_item, all_local_items) @staticmethod def _remote_dir_matches_record(remote_item, record): return record and record.type == ItemRecordType.FOLDER and record.size == remote_item.size and \ record.c_tag == remote_item.c_tag and record.e_tag == remote_item.e_tag Cyclomatic complexity is too high in method _handle_remote_folder. (7)
Function `_handle_remote_folder` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring. def _handle_remote_folder(self, remote_item, item_local_abspath, record, all_local_items): if not self.deep_merge: return try: remote_dir_matches_record = self._remote_dir_matches_record(remote_item, record) if os.path.isfile(item_local_abspath): # Remote item is a directory but local item is a file. if remote_dir_matches_record: # The remote item is very LIKELY to be outdated. logging.warning('Local path "%s" is a file but its remote counterpart is a folder which seems to ' 'be synced before. Will delete the remote folder. To restore it, go to ' 'OneDrive.com and move the folder out of Recycle Bin.', item_local_abspath) delete_item.DeleteRemoteItemTask( self.repo, self.task_pool, self.rel_path, remote_item.name, remote_item.id, True).handle() self.task_pool.add_task(upload_file.UploadFileTask( self.repo, self.task_pool, self.item_request, self.rel_path, remote_item.name)) return # If the remote metadata doesn't agree with record, keep both by renaming the local file. all_local_items.add( rename_with_suffix(self.local_abspath, remote_item.name, self.repo.context.host_name)) if not os.path.exists(item_local_abspath): if remote_dir_matches_record: logging.debug('Local dir "%s" is gone but db record matches remote metadata. Delete remote dir.', item_local_abspath)Similar blocks of code found in 2 locations. Consider refactoring. self.task_pool.add_task(delete_item.DeleteRemoteItemTask( self.repo, self.task_pool, self.rel_path, remote_item.name, remote_item.id, True)) return # Local directory does not exist. Create it. logging.debug('Create missing directory "%s".', item_local_abspath) mkdir(item_local_abspath, uid=self.repo.context.user_uid, exist_ok=True) # The database is temporarily corrupted until the whole dir is merged. But unfortunately we returned early. self.repo.update_item(remote_item, self.rel_path, 0) self.task_pool.add_task(MergeDirectoryTask( repo=self.repo, task_pool=self.task_pool, rel_path=self.rel_path + '/' + remote_item.name, item_request=self.repo.authenticator.client.item(drive=self.repo.drive.id, id=remote_item.id), assume_remote_unchanged=remote_dir_matches_record, parent_remote_unchanged=self.assume_remote_unchanged)) except OSError as e: logging.error('Error occurred when merging directory "%s": %s', item_local_abspath, e) Cyclomatic complexity is too high in method _handle_remote_item. (7)
Function `_handle_remote_item` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring. def _handle_remote_item(self, remote_item, all_local_items, all_records): """ :param onedrivesdk.model.item.Item remote_item: :param [str] all_local_items: :param dict(str, onedrived.od_repo.ItemRecord) all_records: """ # So we have three pieces of information -- the remote item metadata, the record in database, and the inode # on local file system. For the case of handling a remote item, the last two may be missing. item_local_abspath = self.local_abspath + '/' + remote_item.name record = all_records.pop(remote_item.name, None) try: stat = get_os_stat(item_local_abspath) except OSError as e: logging.error('Error occurred when accessing path "%s": %s.', item_local_abspath, e) return if remote_item.folder is not None: return self._handle_remote_folder(remote_item, item_local_abspath, record, all_local_items) if remote_item.file is None: if stat: logging.info('Remote item "%s/%s" is neither a file nor a directory yet local counterpart exists. ' 'Rename local item.', self.rel_path, remote_item.name) try: new_name = rename_with_suffix(self.local_abspath, remote_item.name, self.repo.context.host_name) all_local_items.add(new_name) except OSError as e: logging.error('Error renaming "%s/%s": %s. Skip this item due to unsolvable type conflict.', self.rel_path, remote_item.name, e) else: logging.info('Remote item "%s/%s" is neither a file nor a directory. Skip it.', self.rel_path, remote_item.name) return if record is None: self._handle_remote_file_without_record(remote_item, stat, item_local_abspath, all_local_items) else: self._handle_remote_file_with_record(remote_item, record, stat, item_local_abspath, all_local_items) Cyclomatic complexity is too high in method _handle_local_folder. (9)
Function `_handle_local_folder` has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring. def _handle_local_folder(self, item_name, item_record, item_local_abspath): """ :param str item_name: :param onedrived.od_repo.ItemRecord | None item_record: :param str item_local_abspath: """ if not self.deep_merge: return Similar blocks of code found in 2 locations. Consider refactoring. if self.repo.path_filter.should_ignore(self.rel_path + '/' + item_name, True): logging.debug('Ignored local directory "%s/%s".', self.rel_path, item_name) return if item_record is not None and item_record.type == ItemRecordType.FOLDER: if self.assume_remote_unchanged: rel_path = self.rel_path + '/' + item_name self.task_pool.add_task(MergeDirectoryTask( repo=self.repo, task_pool=self.task_pool, rel_path=rel_path, item_request=self.repo.authenticator.client.item(drive=self.repo.drive.id, path=rel_path), assume_remote_unchanged=True, parent_remote_unchanged=self.assume_remote_unchanged)) else: send2trash(item_local_abspath) self.repo.delete_item(item_name, self.rel_path, True) return # try: # # If there is any file accessed after the time when the record was created, do not delete the dir. # # Instead, upload it back. # # As a note, the API will return HTTP 404 Not Found after the item was deleted. So we cannot know from # # API when the item was deleted. Otherwise this deletion time should be the timestamp to use.TODO found # # TODO: A second best timestamp is the latest timestamp of any children item under this dir. # visited_files = subprocess.check_output( # ['find', item_local_abspath, '-type', 'f', # '(', '-newermt', item_record.record_time_str, '-o', # '-newerat', item_record.record_time_str, ')', '-print'], universal_newlines=True) # if visited_files == '': # logging.info('Local item "%s" was deleted remotely and not used since %s. Delete it locally.', # item_local_abspath, item_record.record_time_str) # send2trash(item_local_abspath) # self.repo.delete_item(item_name, self.rel_path, True) # return # logging.info('Local directory "%s" was deleted remotely but locally used. Upload it back.') # except subprocess.CalledProcessError as e: # logging.error('Error enumerating files in "%s" accessed after "%s": %s.', # item_local_abspath, item_record.record_time_str, e) # except OSError as e: # logging.error('Error checking local folder "%s": %s.', item_local_abspath, e)Similar blocks of code found in 2 locations. Consider refactoring. elif item_record is not None: if self.assume_remote_unchanged: logging.info('Remote item for local dir "%s" is a file that has been deleted locally. ' 'Delete the remote item and upload the file.', item_local_abspath) if not delete_item.DeleteRemoteItemTask( repo=self.repo, task_pool=self.task_pool, parent_relpath=self.rel_path, item_name=item_name, item_id=item_record.item_id, is_folder=False).handle(): logging.error('Failed to delete outdated remote directory "%s/%s" of Drive %s.', self.rel_path, item_name, self.repo.drive.id) # Keep the record so that the branch can be revisited next time. return # Either we decide to upload the item above, or the folder does not exist remotely and we have no reference # whether it existed remotely or not in the past. Better upload it back. logging.info('Local directory "%s" seems new. Upload it.', item_local_abspath) self.task_pool.add_task(CreateFolderTask( self.repo, self.task_pool, item_name, self.rel_path, True, True)) Function `_handle_local_file` has a Cognitive Complexity of 26 (exceeds 5 allowed). Consider refactoring.
Cyclomatic complexity is too high in method _handle_local_file. (16) def _handle_local_file(self, item_name, item_record, item_stat, item_local_abspath): """ :param str item_name: :param onedrived.od_repo.ItemRecord | None item_record: :param posix.stat_result | None item_stat: :param str item_local_abspath: """Similar blocks of code found in 2 locations. Consider refactoring. if self.repo.path_filter.should_ignore(self.rel_path + '/' + item_name, False): logging.debug('Ignored local file "%s/%s".', self.rel_path, item_name) return if item_stat is None: logging.info('Local-only file "%s" existed when scanning but is now gone. Skip it.', item_local_abspath) if item_record is not None: self.repo.delete_item(item_record.item_name, item_record.parent_path, False) if self.assume_remote_unchanged: self.task_pool.add_task(delete_item.DeleteRemoteItemTask( repo=self.repo, task_pool=self.task_pool, parent_relpath=self.rel_path, item_name=item_name, item_id=item_record.item_id, is_folder=False)) return if item_record is not None and item_record.type == ItemRecordType.FILE: record_ts = datetime_to_timestamp(item_record.modified_time) equal_ts = diff_timestamps(item_stat.st_mtime, record_ts) == 0 if item_stat.st_size == item_record.size_local and \ (equal_ts or item_record.sha1_hash and item_record.sha1_hash == sha1_value(item_local_abspath)): # Local file matches record. if self.assume_remote_unchanged: if not equal_ts: fix_owner_and_timestamp(item_local_abspath, self.repo.context.user_uid, record_ts) else: logging.debug('Local file "%s" used to exist remotely but not found. Delete it.', item_local_abspath) send2trash(item_local_abspath) self.repo.delete_item(item_record.item_name, item_record.parent_path, False) return logging.debug('Local file "%s" is different from when it was last synced. Upload it.', item_local_abspath) elif item_record is not None: # Record is a dir but local entry is a file.Similar blocks of code found in 2 locations. Consider refactoring. if self.assume_remote_unchanged: logging.info('Remote item for local file "%s" is a directory that has been deleted locally. ' 'Delete the remote item and upload the file.', item_local_abspath) if not delete_item.DeleteRemoteItemTask( repo=self.repo, task_pool=self.task_pool, parent_relpath=self.rel_path, item_name=item_name, item_id=item_record.item_id, is_folder=True).handle(): logging.error('Failed to delete outdated remote directory "%s/%s" of Drive %s.', self.rel_path, item_name, self.repo.drive.id) # Keep the record so that the branch can be revisited next time. return logging.debug('Local file "%s" is new to OneDrive. Upload it.', item_local_abspath) self.task_pool.add_task(upload_file.UploadFileTask( self.repo, self.task_pool, self.item_request, self.rel_path, item_name)) Function `_handle_local_item` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring. def _handle_local_item(self, item_name, all_records): """ :param str item_name: :param dict(str, onedrived.od_repo.ItemRecord) all_records: :return: """ item_local_abspath = self.local_abspath + '/' + item_name record = all_records.pop(item_name, None) try: if os.path.isfile(item_local_abspath): # stat can be None because the function can be called long after dir is listed. stat = get_os_stat(item_local_abspath) self._handle_local_file(item_name, record, stat, item_local_abspath) elif os.path.isdir(item_local_abspath): self._handle_local_folder(item_name, record, item_local_abspath) else: logging.warning('Unsupported type of local item "%s". Skip it and remove record.', item_local_abspath) if record is not None: self.repo.delete_item(record.item_name, record.parent_path, record.type == ItemRecordType.FOLDER) except OSError as e: logging.error('Error occurred when accessing path "%s": %s.', item_local_abspath, e) class CreateFolderTask(base.TaskBase): Function `__init__` has 6 arguments (exceeds 4 allowed). Consider refactoring. def __init__(self, repo, task_pool, item_name, parent_relpath, upload_if_success=True, abort_if_local_gone=True): """ :param onedrived.od_repo.OneDriveLocalRepository repo: :param onedrived.od_task.TaskPool task_pool: :param str item_name: :param str parent_relpath: :param True | False upload_if_success: :param True | False abort_if_local_gone: """ super().__init__(repo, task_pool) self.item_name = item_name self.parent_relpath = parent_relpath self.local_abspath = repo.local_root + parent_relpath + '/' + item_name self.upload_if_success = upload_if_success self.abort_if_local_gone = abort_if_local_gone Similar blocks of code found in 2 locations. Consider refactoring. def __repr__(self): return type(self).__name__ + '(%s, upload=%s)' % (self.local_abspath, self.upload_if_success) @staticmethod def _get_folder_pseudo_item(item_name): item = Item() item.name = item_name item.folder = Folder() return item def _get_item_request(self): if self.parent_relpath == '': return self.repo.authenticator.client.item(drive=self.repo.drive.id, id='root') else: return self.repo.authenticator.client.item(drive=self.repo.drive.id, path=self.parent_relpath) def handle(self): logging.info('Creating remote item for local dir "%s".', self.local_abspath) try: if self.abort_if_local_gone and not os.path.isdir(self.local_abspath): logging.warning('Local dir "%s" is gone. Skip creating remote item for it.', self.local_abspath) return item = self._get_folder_pseudo_item(self.item_name) item_request = self._get_item_request() item = item_request_call(self.repo, item_request.children.add, item) self.repo.update_item(item, self.parent_relpath, 0) logging.info('Created remote item for local dir "%s".', self.local_abspath) if self.upload_if_success: logging.info('Adding task to merge "%s" after remote item was created.', self.local_abspath) self.task_pool.add_task(MergeDirectoryTask( self.repo, self.task_pool, self.parent_relpath + '/' + self.item_name, self.repo.authenticator.client.item(drive=self.repo.drive.id, id=item.id))) return True except (onedrivesdk.error.OneDriveError, OSError) as e: logging.error('Error when creating remote dir of "%s": %s.', self.local_abspath, e) return False