I’ve written a small utility tool capable of

  • backing up a directory,
  • restoring a backed up directory,
  • syncing two directories (copying missing or changed files from src to dst).

I’d love your feedback - any code smells, improvements, potential bugs, non-idiomatic approaches, etc., please let me know.

Here's how I backup and revert:

SHA1_DIGEST_REGEX = "[a-fA-F0-9]{40}"


def backup(directory, repo=REPO, backup_file=BACKUP):
    if not os.path.isdir(repo):
        os.makedirs(repo)
    key = str(time.time())
    sha = hashlib.sha1(key).hexdigest()
    archive_path = os.path.join(repo, sha)
    assert not os.path.isfile(archive_path)
    shutil.make_archive(archive_path, "zip", directory)
    backup_path = os.path.join(directory, backup_file)
    with open(backup_path, 'w') as b:
        b.write(sha)


def revert(directory, repo=REPO, backup_file=BACKUP):
    try:
        with open(os.path.join(directory, backup_file)) as b:
            sha = b.readline()
        if re.compile(SHA1_DIGEST_REGEX).match(sha):
            backup_path = os.path.join(repo, sha + ".zip")
            if not os.path.isfile(backup_path):
                raise IOError
            if os.listdir(directory):
                shutil.rmtree(directory)
            with zipfile.ZipFile(backup_path) as zf:
                zf.extractall(directory)
        else:
            print "Backup file corrupted."
    except IOError:
        print "Either no history is available or the backup files are missing."

Comparing two directories and applying the changes:

def find_diff(src, dst):
    if os.path.isdir(dst):
        diff = filecmp.dircmp(src, dst)
        return diff.left_only + diff.diff_files
    else:
        return [f for f in os.listdir(src)]


def apply_diff(src, dst, diff_list=None, auto_backup=True):
    if diff_list is None:
        diff_list = find_diff(src, dst)
    if diff_list:
        if auto_backup:
            backup(dst)
        for item in diff_list:
            src_item = os.path.join(src, item)
            dst_item = os.path.join(dst, item)
            if os.path.isdir(src_item):
                shutil.copytree(src_item, dst_item)
            else:
                shutil.copy(src_item, dst_item)

Sync (read multiple src dirs from a file):

def sync(directory, sync_file=SYNC):
    try:
        with open(os.path.join(directory, sync_file)) as s:
            src_list = s.read().splitlines()
        if src_list:
            for src in src_list:
                if not os.path.isdir(src):
                    raise IOError
            backup(directory)
            for src in src_list:
                apply_diff(src, directory, auto_backup=False)
        else:
            print "Sync file is empty."
    except IOError:
        print "Sync file is either missing or contains invalid sources."

I hope the code speaks for itself, but I'm happy to clarify things if they don't make sense.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.