The public ZipInfo.compress_level attribute lets Python applications choose a compression level for each individual member of a ZIP archive. Instead of forcing every file to use one global setting, you can tune text, images, databases, logs, and already-compressed formats differently. This is valuable for backups, software bundles, report exports, data pipelines, and web services that generate archives on demand.
What ZipInfo represents
ZipInfo describes one member inside a ZIP file. It stores the member name, timestamps, permissions, compression method, and other metadata. When you build a ZipInfo object manually, you can define these properties before writing bytes with ZipFile.writestr(). The public compression-level attribute makes per-entry intent explicit and avoids relying on implementation details.
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
info = ZipInfo("report.txt")
info.compress_type = ZIP_DEFLATED
info.compress_level = 9
with ZipFile("bundle.zip", "w") as zf:
zf.writestr(info, "report contents")
Only report.txt receives level 9 in this example. Other entries may use level 1, level 6, or no compression.
Why per-file levels matter
Plain text, JSON, CSV, XML, Markdown, and source code usually compress well. A higher level may save substantial space. JPEG images, MP4 videos, optimized PDFs, gzip files, and existing ZIP archives often provide little additional reduction. Spending more CPU on those formats can increase latency without producing a meaningful size benefit.
The right choice also depends on workload. For an archive created once and downloaded thousands of times, slower high-level compression may be justified. For an API endpoint that builds archives for every request, fast creation may matter more than a small size reduction. On a busy server, aggressive compression can reduce throughput and increase response times.
A policy based on file extension
from pathlib import Path
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
TEXT = {".txt", ".csv", ".json", ".xml", ".py", ".md"}
COMPRESSED = {".jpg", ".jpeg", ".png", ".mp4", ".gz", ".zip"}
def add_file(zf, path):
path = Path(path)
data = path.read_bytes()
info = ZipInfo(path.name)
if path.suffix.lower() in TEXT:
info.compress_type = ZIP_DEFLATED
info.compress_level = 9
elif path.suffix.lower() in COMPRESSED:
info.compress_type = ZIP_STORED
else:
info.compress_type = ZIP_DEFLATED
info.compress_level = 6
zf.writestr(info, data)
with ZipFile("files.zip", "w") as zf:
for name in ["data.csv", "photo.jpg", "manual.pdf"]:
add_file(zf, name)
This design centralizes policy and makes it easy to test. You can measure time and output size, change extension groups, and add metrics without spreading magic numbers throughout the project.
Compression methods and valid ranges
The meaning and valid range of a compression level depend on the selected algorithm. ZIP_DEFLATED commonly uses levels from 0 through 9, but other methods can use different ranges or semantics. Always validate the combination of compress_type and compress_level. A value suitable for one method may be invalid or behave differently for another.
Consumer compatibility matters too. Older extraction tools may not support newer methods. DEFLATE remains a practical choice when broad interoperability is required. Review the official zipfile documentation and the ZIP format specification before choosing a less common method.
Global defaults versus per-entry overrides
ZipFile accepts a global compresslevel argument. That is a convenient default for most archive members. ZipInfo.compress_level is useful when one member should override the default. A project can keep level 6 for ordinary files, use level 9 for large text exports, and store already-compressed media without additional work.
This granular approach balances speed and size while preserving readability. Reviewers can see the intended behavior directly where the entry metadata is defined.
Measure with representative data
Do not choose levels based only on intuition. Create archives from production-like samples and record compression time, final size, extraction time, peak memory, and CPU utilization. One large text file behaves differently from tens of thousands of small files.
from pathlib import Path
from time import perf_counter
start = perf_counter()
# build the archive here
elapsed = perf_counter() - start
size = Path("bundle.zip").stat().st_size
print({"seconds": elapsed, "bytes": size})
Run each test multiple times, account for filesystem caching, and test under realistic concurrency. For web applications, monitor tail latency rather than only average time.
Security considerations
Validate archive member names before writing. Reject absolute paths, parent-directory traversal such as ../, unexpected control characters, and duplicate names when duplicates could confuse consumers. Limit the number and total size of entries, handle read failures, and avoid including secrets by broad directory globs.
When extracting archives received from others, resolve the destination path and verify that every member remains inside the intended directory. Limit total expanded size and entry count, and treat links carefully. See our guides to safe ZIP archives and safe TAR handling.
Pathlib and automated tests
pathlib makes directory traversal and path normalization easier. In tests, use a temporary directory, create sample files, build the ZIP, reopen it, and verify member names, compression methods, contents, and error behavior. Related tutorials include pathlib file paths and pytest automated testing.
Practical architecture
Keep policy separate from I/O. One function can classify a file, another can build its ZipInfo, and another can write the data. This separation improves testability and lets you adjust compression rules without changing archive traversal. Add structured logs for source size, compressed size, chosen method, level, and duration.
For large inputs, consider streaming and avoid reading every file into memory at once. Check disk space before starting, write to a temporary destination, and rename the archive atomically after successful completion. If creation fails, remove the incomplete file.
Best practices
Use a sensible global default, override only where benchmarks show a benefit, avoid recompressing already-compressed formats, validate method-level combinations, document compatibility requirements, test generated archives with multiple readers, and monitor CPU and latency in production. Prefer named constants or a policy object over unexplained numeric values.
Conclusion
ZipInfo.compress_level gives Python developers precise control over each ZIP member. Its main value is not forcing the maximum level everywhere. The value comes from applying an explicit, measured policy that balances archive size, CPU cost, creation latency, extraction compatibility, and operational simplicity for each type of data.







