-
-
Notifications
You must be signed in to change notification settings - Fork 35k
bpo-32728: Add compresslevel support for zipfile and LZMA #5534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
63a9350
4ca4ba6
47d7dbc
c97397a
3218ba6
916be26
37b8945
3ed3f24
b7499b0
978bf3f
538e956
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -633,6 +633,20 @@ class LzmaTestsWithSourceFile(AbstractTestsWithSourceFile, | |
| unittest.TestCase): | ||
| compression = zipfile.ZIP_LZMA | ||
|
|
||
| def test_write_preset(self): | ||
| # Initialize an LZMA compressor with a preset and get the generated | ||
| # property header. | ||
| props_header = zipfile.LZMACompressor(preset=1).compress(b'') | ||
|
|
||
| # Write a ZIP archive with that LZMA compression preset and ensure | ||
| # that the property header above is written to that archive. | ||
| with io.BytesIO() as fp: | ||
| kwargs = {'compression': self.compression, 'compresslevel': 1} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there's a need for this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quite right. I put it there to avoid a long line. |
||
| with zipfile.ZipFile(fp, 'w', **kwargs) as zipfp: | ||
| zipfp.writestr('dummy_file', b'dummy_contents') | ||
| written_bytes = fp.getvalue() | ||
| self.assertIn(props_header, written_bytes) | ||
|
|
||
|
|
||
| class AbstractTestZip64InSmallFiles: | ||
| # These tests test the ZIP64 functionality without using large files, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -596,15 +596,69 @@ def decrypter(data): | |
|
|
||
|
|
||
| class LZMACompressor: | ||
|
|
||
| def __init__(self): | ||
| # LZMA uses "filter chains" to control compression settings (e.g., how | ||
| # much memory and CPU to use) in details. liblzma defines certain filter | ||
| # chain configurations as "preset levels," akin to the compression levels | ||
| # in zlib or bzip2. | ||
| _PRESET_OPTIONS_MAP = { | ||
|
bbayles marked this conversation as resolved.
|
||
| # Levels 0 through 9, without the "extreme" setting | ||
| 0x00000000: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 18, | ||
| 'mode': 1, 'mf': 3, 'nice_len': 128, 'depth': 4}, | ||
| 0x00000001: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 20, | ||
| 'mode': 1, 'mf': 4, 'nice_len': 128, 'depth': 8}, | ||
| 0x00000002: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 21, | ||
| 'mode': 1, 'mf': 4, 'nice_len': 273, 'depth': 24}, | ||
| 0x00000003: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 22, | ||
| 'mode': 1, 'mf': 4, 'nice_len': 273, 'depth': 48}, | ||
| 0x00000004: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 22, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 16, 'depth': 0}, | ||
| 0x00000005: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 23, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 32, 'depth': 0}, | ||
| 0x00000006: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 23, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 64, 'depth': 0}, | ||
| 0x00000007: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 24, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 64, 'depth': 0}, | ||
| 0x00000008: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 25, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 64, 'depth': 0}, | ||
| 0x00000009: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 26, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 64, 'depth': 0}, | ||
| # Levels 0 through 9, OR-ed with the "extreme" setting (PRESET_EXTREME) | ||
| 0x80000000: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 18, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000001: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 20, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000002: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 21, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000003: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 22, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 192, 'depth': 0}, | ||
| 0x80000004: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 22, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000005: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 23, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 192, 'depth': 0}, | ||
| 0x80000006: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 23, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000007: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 24, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000008: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 25, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| 0x80000009: {'lc': 3, 'lp': 0, 'pb': 2, 'dict_size': 1 << 26, | ||
| 'mode': 2, 'mf': 20, 'nice_len': 273, 'depth': 512}, | ||
| } | ||
|
|
||
| def __init__(self, preset=None): | ||
| self._comp = None | ||
| if (preset is not None) and (preset not in self._PRESET_OPTIONS_MAP): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the parentheses here are needed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. My thought was that it helped with readability. |
||
| raise ValueError(f'invalid preset {preset}') | ||
| self._preset = preset | ||
|
|
||
| def _init(self): | ||
| props = lzma._encode_filter_properties({'id': lzma.FILTER_LZMA1}) | ||
| self._comp = lzma.LZMACompressor(lzma.FORMAT_RAW, filters=[ | ||
| lzma._decode_filter_properties(lzma.FILTER_LZMA1, props) | ||
| ]) | ||
| # Translate the encoder settings into the value to be written to | ||
| # the LZMA Properties Header in the ZIP file. | ||
| opts = {'id': lzma.FILTER_LZMA1} | ||
| if self._preset is not None: | ||
| opts.update(self._PRESET_OPTIONS_MAP[self._preset]) | ||
| props = lzma._encode_filter_properties(opts) | ||
| self._comp = lzma.LZMACompressor(lzma.FORMAT_RAW, filters=[opts]) | ||
| return struct.pack('<BBH', 9, 4, len(props)) + props | ||
|
|
||
| def compress(self, data): | ||
|
|
@@ -694,9 +748,8 @@ def _get_compressor(compress_type, compresslevel=None): | |
| if compresslevel is not None: | ||
| return bz2.BZ2Compressor(compresslevel) | ||
| return bz2.BZ2Compressor() | ||
| # compresslevel is ignored for ZIP_LZMA | ||
| elif compress_type == ZIP_LZMA: | ||
| return LZMACompressor() | ||
| return LZMACompressor(preset=compresslevel) | ||
| else: | ||
| return None | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Archives created by the :mod:`zipfile` module can now use LZMA compression | ||
| presets. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.