'Create a zip of folders using zipfile

I have a folder called my_folder at the following Path /Users/my_user_name/Desktop/my_folder. The folder my_folder contains more folders like 323456, 987654 etc. Those folders contain some content. I want to create a zip of all those folders called myzip.zip such that when someone unzips it they see all those folders like 323456, 987654 at the root.

My Code

import os
from pathlib import Path
from zipfile import ZipFile


DOWNLOAD_DIR = Path("/Users/my_user_name/Desktop/my_folder")
ZIPPED_FILE_DIR = Path("/Users/my_user_name/Desktop/my_zip")


def get_list_of_all_folders(download_dir: Path):
    return [f for f in download_dir.iterdir() if download_dir.is_dir()]


def zip_files():
    folder_list = get_list_of_all_folders(DOWNLOAD_DIR)
    with ZipFile(ZIPPED_FILE_DIR / "my_zip.zip", "w") as zip:
        # writing each file one by one
        for folder in folder_list:
            zip.write(folder)


zip_files()

I have a function called get_list_of_all_folders where it goes to my_folder and gets a list of all the folders inside it that we want to zip. Then I use that folder_list to zip up each folder as part of my final zip called my_zip.zip. However there is something really wrong with my code and I am not sure what? The my_zip.zip is only 35 kb small when I know for a fact I am zipping up content over 2 gigabytes.

I looked at the zipfile document but did not find much help here as there are not many examples.



Solution 1:[1]

ZipFile.write expects to be supplied with the name of a file to write to the zip, not a folder.

You will need to iterate over the files in each folder and call write for each one. For example:

from pathlib import Path
from zipfile import ZipFile


DOWNLOAD_DIR = Path("/Users/my_user_name/Desktop/my_folder")
ZIPPED_FILE_DIR = Path("/Users/my_user_name/Desktop/my_zip")


def scan_dir(zip, dir, base_dir):
    for f in dir.iterdir():
        if f.is_dir():
            scan_dir(zip, f, base_dir)
        else:
            # First param is the path to the file, second param is
            # the path to use in the zip and when extracted. I just
            # trim base_dir off the front.
            zip.write(f, str(f)[len(str(base_dir)):])


def zip_files():
    with ZipFile(ZIPPED_FILE_DIR / "my_zip.zip", "w") as zip:
        for f in DOWNLOAD_DIR.iterdir():
            scan_dir(zip, f, DOWNLOAD_DIR)


zip_files()

There's probably a neater way to trim off the base directory, but this was done quickly :)

Solution 2:[2]

You can use: shutil.make_archive

Below example taken from: https://docs.python.org/3/library/shutil.html#archiving-example

>>> import os
>>> from shutil import make_archive
>>> archive_name = os.path.expanduser(os.path.join('~', 'myarchive'))
>>> root_dir = os.path.expanduser(os.path.join('~', '.ssh'))
>>> make_archive(archive_name, 'zip', root_dir)
'/Users/tarek/myarchive.zip'

EDIT:

Code using ZipFile library

import zipfile
import os

class ZipUtilities:
    def toZip(self, file, filename):
        zip_file = zipfile.ZipFile(filename, 'w')
        if os.path.isfile(file):
            zip_file.write(file)
        else:
            self.addFolderToZip(zip_file, file)
        zip_file.close()

    def addFolderToZip(self, zip_file, folder):
        for file in os.listdir(folder):
            full_path = os.path.join(folder, file)
            if os.path.isfile(full_path):
                print('File added: ' + str(full_path))
                zip_file.write(full_path)
            elif os.path.isdir(full_path):
                print('Entering folder: ' + str(full_path))
                self.addFolderToZip(zip_file, full_path)

if __name__ == '__main__':
    utilities = ZipUtilities()
    filename = 'newfile.zip'
    directory = 'foldername'
    utilities.toZip(directory, filename)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2