Skip to main content

ZipFly

forthebadge forthebadge forthebadge

Build Status Build Status Build Status Build Status

Python library to construct a ZIP64 archive on the fly without having to store the entire ZIP in memory or disk. This is useful in memory-constrained environments, or when you would like to start returning compressed data before you've even retrieved all the uncompressed data. Generating ZIPs on-demand in a web server is a typical use case for zipFly.

  • No temporary files, data is streamed directly
  • Support for async interface
  • Calculates archive size before streaming even begins
  • Supports deflate compression method
  • Small memory usage, streaming is done using yield statement
  • Archive structure is created on the fly, and all data can be created during stream
  • Files included into archive can be generated on the fly using Python generators
  • Independent of the goofy 🤮🤮 python's standard ZipFile implementation
  • Only 1 dependency
  • Automatic detection and changing of duplicate names
  • Zip64 format compatible files
  • 21.37% test coverage

This library is based upon this library (this library was a piece of work...)

How to install

pip install zipfly64

https://pypi.org/project/zipFly64

Usage

from zipFly import ZipFly, LocalFile, consts
# compression_method is optional, defaults to consts.NO_COMPRESSION
file1 = LocalFile(file_path='files/lqbfa61deebf1.mp4', compression_method=consts.NO_COMPRESSION) #  or consts.COMPRESSION_DEFLATE 
file2 = LocalFile(file_path='public/2ae9dcd01a3aa.mp4', name="files/my_file2.mp4")  # override the file name
file3 = LocalFile(file_path='files/4shaw1dax4da.mp4', name="my_file3.mp4")  # you control the directory path by specifying it in name

files = [file1, file2, file3]

zipFly = ZipFly(files)

# save to file, or do something else with the stream() generator
with open("out/file.zip", 'wb') as f_out:
    for chunk in zipFly.stream():
        f_out.write(chunk)

Supports dynamically created files

from zipFly import ZipFly, GenFile, LocalFile, consts


def file_generator():
    yield b"uga buga"
    yield b"a29jaGFtIGFsdGVybmF0eXdraQ=="
    yield b"2137"
    
# size is optional, it allows to calculate the total size of the archive before any data is generated
# modification_time in epoch time, defaults to time.time()
file1 = GenFile(name="file.txt", generator=file_generator(), modification_time=time.time(), size=size, compression_method=consts.COMPRESSION_DEFLATE)
file2 = LocalFile(file_path='files/as61aade2ebfd.mp4', compression_method=consts.NO_COMPRESSION) #  or consts.COMPRESSION_DEFLATE 

files = [file1, file2]

zipFly = ZipFly(files)
archive_size = zipFly.calculate_archive_size() # raises RuntimeError if it can't calculate size

# for example you can set as content length in http response
response['Content-Length'] = archive_size

for chunk in zipFly.stream():
       # do something

Async interface

import asyncio
from zipFly import ZipFly, LocalFile, consts, GenFile
file1 = GenFile(name="file.txt", generator=file_generator())
file2 = LocalFile(file_path='public/2ae9dcd01a3aa.mp4', name="files/my_file2.mp4")

files = [file1, file2]

zipFly = ZipFly(files)

async def save_zip_async():
    with open("out/file.zip", 'wb') as f_out:
        async for chunk in zipFly.async_stream():
            f_out.write(chunk)

asyncio.run(save_zip_async())

Byte offset mode

This mode allows to start generating archive from offset. It finds the file within that offset and starts streaming from it. Sadly it must fetch the entire file as otherwise a correct crc cannot be calculated. If you use LocalFile then it's not a problem as it can very fast go tru the entire local file and calculate crc. However, if u use a GenFile it still has to fetch the entire file which may take a while depending on the file's size.

file1 = GenFile(name="file.txt", generator=file_generator(), crc=crc)
file2 = LocalFile(file_path='public/2ae9dcd01a3aa.mp4', name="files/my_file2.mp4")
files1 = [file1, file2]
zipFly1 = ZipFly(files1)

# Simulating pause/resume
STOP_BYTE = 300
async def async_save_pause():
    byte_offset = 0
    with open("out/file.zip", 'wb') as f_out:
        async for chunk in zipFly1.async_stream():
            remaining_bytes = STOP_BYTE - byte_offset
            if len(chunk) > remaining_bytes:
                chunk = chunk[:remaining_bytes]
            f_out.write(chunk)
            byte_offset += len(chunk)
            if byte_offset >= STOP_BYTE:
                break
                
# Later...                
file3 = GenFile(name="file.txt", generator=file_generator(), crc=crc)
file4 = LocalFile(file_path='public/2ae9dcd01a3aa.mp4', name="files/my_file2.mp4")
files2 = [file3, file4]
resumeZipFly = ZipFly(files2, byte_offset=STOP_BYTE)

async def async_save_resume():
    with open("out/file.zip", 'ab') as f_out: # Append mode
        async for chunk in resumeZipFly.async_stream():
            f_out.write(chunk)

async def pause_resume_save():
    await async_save_pause()
    await async_save_resume()

asyncio.run(pause_resume_save())

If resume ZipFly instance has different files than pause ZipFly instance there will be a corrupted Zip file generated

Parallel async streaming

If your GenFile's rely on network requests to fetch data, network latency can limit throughput below the available bandwidth. To address this, I introduce async_stream_parallel.

zipFly = ZipFly(files)
zipFly.async_stream_parallel(prefetch_files=20, max_chunks_per_file=2)

Other

Python is not optimized for async I/O operations, thus to speed up the async streaming the chunk_size is changed to 4MB, you can override this by passing chunksize as argument to LocalFile.

I created this library for my iDrive project.

If you have a different use case scenario, and LocalFile and GenFile are not enough, you can extend BaseFile and everything else should work out of the box.

I found myself needing to write custom payloads after local file headers and before the actual file data. You can pass custom_payload to File instances.

Testing

With pytest and pytest-asyncio installed, call pytest from the top-level directory (same as this README.md) to run tests. The 4GB tests are slow. If your machine has enough memory (~4GB free) and a fast disk/SSD, pytest-xdist can speed things up by running tests in parallel. Use it by calling pytest -n auto.

PS

I wholeheartedly hope everyone responsible for creating ZIP documentation gets slaughtered in the most gore and painful way 😊 (in game)

(pls redo ur docs)

Metadata

Release files for zipFly64 1.5.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zipFly64 1.5.3
File Size Uploaded
zipfly64-1.5.3.tar.gz 28.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zipFly64 1.5.3
File Interpreter ABI Platform
zipfly64-1.5.3-py3-none-any.whl Python 3 none any Details

Total release size: 48.6 kB

Release files / zipfly64-1.5.3.tar.gz

Download URL zipfly64-1.5.3.tar.gz
Size 28.0 kB
Tags Source
SHA-256 checksum
How to use checksums
1f6db34bcbee4be3403eac02ed746baa71ae2ecda37d93540aa47c539bbdaccb
BLAKE2b-256 checksum
How to use checksums
381111c6463bcf733479ea8f92e7f53d467504dd4ef60a7d30643376f5ab4f02
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.25

Release files / zipfly64-1.5.3-py3-none-any.whl

Download URL zipfly64-1.5.3-py3-none-any.whl
Size 20.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6b8a41baa1453b40424459facd231abb14dc27a664374c885fdc5d64fbd93d94
BLAKE2b-256 checksum
How to use checksums
2209e77ee9e3ee42fb970d0ef1902aef4ef4c01c4605f21378edd5539478438f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.25

Release history Release notifications | RSS feed

This release

1.5.3 This release

2 release files

1.5.2

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.3

2 release files

1.2.1

3 release files

1.2.0

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page