Skip to content

Commit 7fb79fc

Browse files
committed
Issue python#14366: Support lzma compression in zip files.
Patch by Serhiy Storchaka.
1 parent bb54b33 commit 7fb79fc

File tree

5 files changed

+257
-27
lines changed

5 files changed

+257
-27
lines changed

Doc/library/zipfile.rst

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,20 @@ The module defines the following items:
9797

9898
.. versionadded:: 3.3
9999

100+
.. data:: ZIP_LZMA
101+
102+
The numeric constant for the LZMA compression method. This requires the
103+
lzma module.
104+
105+
.. versionadded:: 3.3
106+
100107
.. note::
101108

102109
The ZIP file format specification has included support for bzip2 compression
103-
since 2001. However, some tools (including older Python releases) do not
104-
support it, and may either refuse to process the ZIP file altogether, or
105-
fail to extract individual files.
110+
since 2001, and for LZMA compression since 2006. However, some tools
111+
(including older Python releases) do not support these compression
112+
methods, and may either refuse to process the ZIP file altogether,
113+
or fail to extract individual files.
106114

107115

108116
.. seealso::
@@ -133,11 +141,11 @@ ZipFile Objects
133141
adding a ZIP archive to another file (such as :file:`python.exe`). If
134142
*mode* is ``a`` and the file does not exist at all, it is created.
135143
*compression* is the ZIP compression method to use when writing the archive,
136-
and should be :const:`ZIP_STORED`, :const:`ZIP_DEFLATED`; or
137-
:const:`ZIP_DEFLATED`; unrecognized
138-
values will cause :exc:`RuntimeError` to be raised. If :const:`ZIP_DEFLATED` or
139-
:const:`ZIP_BZIP2` is specified but the corresponded module
140-
(:mod:`zlib` or :mod:`bz2`) is not available, :exc:`RuntimeError`
144+
and should be :const:`ZIP_STORED`, :const:`ZIP_DEFLATED`,
145+
:const:`ZIP_BZIP2` or :const:`ZIP_LZMA`; unrecognized
146+
values will cause :exc:`RuntimeError` to be raised. If :const:`ZIP_DEFLATED`,
147+
:const:`ZIP_BZIP2` or :const:`ZIP_LZMA` is specified but the corresponded module
148+
(:mod:`zlib`, :mod:`bz2` or :mod:`lzma`) is not available, :exc:`RuntimeError`
141149
is also raised. The default is :const:`ZIP_STORED`. If *allowZip64* is
142150
``True`` zipfile will create ZIP files that use the ZIP64 extensions when
143151
the zipfile is larger than 2 GB. If it is false (the default) :mod:`zipfile`
@@ -161,7 +169,7 @@ ZipFile Objects
161169
Added the ability to use :class:`ZipFile` as a context manager.
162170

163171
.. versionchanged:: 3.3
164-
Added support for :mod:`bzip2` compression.
172+
Added support for :mod:`bzip2` and :mod:`lzma` compression.
165173

166174

167175
.. method:: ZipFile.close()

Lib/test/support.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
except ImportError:
4646
bz2 = None
4747

48+
try:
49+
import lzma
50+
except ImportError:
51+
lzma = None
52+
4853
__all__ = [
4954
"Error", "TestFailed", "ResourceDenied", "import_module",
5055
"verbose", "use_resources", "max_memuse", "record_original_stdout",
@@ -62,7 +67,7 @@
6267
"get_attribute", "swap_item", "swap_attr", "requires_IEEE_754",
6368
"TestHandler", "Matcher", "can_symlink", "skip_unless_symlink",
6469
"import_fresh_module", "requires_zlib", "PIPE_MAX_SIZE", "failfast",
65-
"anticipate_failure", "run_with_tz", "requires_bz2"
70+
"anticipate_failure", "run_with_tz", "requires_bz2", "requires_lzma"
6671
]
6772

6873
class Error(Exception):
@@ -513,6 +518,8 @@ def _is_ipv6_enabled():
513518

514519
requires_bz2 = unittest.skipUnless(bz2, 'requires bz2')
515520

521+
requires_lzma = unittest.skipUnless(lzma, 'requires lzma')
522+
516523
is_jython = sys.platform.startswith('java')
517524

518525
# Filename used for testing

Lib/test/test_zipfile.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from random import randint, random
1414
from unittest import skipUnless
1515

16-
from test.support import TESTFN, run_unittest, findfile, unlink, requires_zlib, requires_bz2
16+
from test.support import TESTFN, run_unittest, findfile, unlink, requires_zlib, requires_bz2, requires_lzma
1717

1818
TESTFN2 = TESTFN + "2"
1919
TESTFNDIR = TESTFN + "d"
@@ -361,6 +361,55 @@ def test_low_compression_bzip2(self):
361361
self.assertEqual(openobj.read(1), b'1')
362362
self.assertEqual(openobj.read(1), b'2')
363363

364+
@requires_lzma
365+
def test_lzma(self):
366+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
367+
self.zip_test(f, zipfile.ZIP_LZMA)
368+
369+
@requires_lzma
370+
def test_open_lzma(self):
371+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
372+
self.zip_open_test(f, zipfile.ZIP_LZMA)
373+
374+
@requires_lzma
375+
def test_random_open_lzma(self):
376+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
377+
self.zip_random_open_test(f, zipfile.ZIP_LZMA)
378+
379+
@requires_lzma
380+
def test_readline_read_lzma(self):
381+
# Issue #7610: calls to readline() interleaved with calls to read().
382+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
383+
self.zip_readline_read_test(f, zipfile.ZIP_LZMA)
384+
385+
@requires_lzma
386+
def test_readline_lzma(self):
387+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
388+
self.zip_readline_test(f, zipfile.ZIP_LZMA)
389+
390+
@requires_lzma
391+
def test_readlines_lzma(self):
392+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
393+
self.zip_readlines_test(f, zipfile.ZIP_LZMA)
394+
395+
@requires_lzma
396+
def test_iterlines_lzma(self):
397+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
398+
self.zip_iterlines_test(f, zipfile.ZIP_LZMA)
399+
400+
@requires_lzma
401+
def test_low_compression_lzma(self):
402+
"""Check for cases where compressed data is larger than original."""
403+
# Create the ZIP archive
404+
with zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_LZMA) as zipfp:
405+
zipfp.writestr("strfile", '12')
406+
407+
# Get an open object for strfile
408+
with zipfile.ZipFile(TESTFN2, "r", zipfile.ZIP_LZMA) as zipfp:
409+
with zipfp.open("strfile") as openobj:
410+
self.assertEqual(openobj.read(1), b'1')
411+
self.assertEqual(openobj.read(1), b'2')
412+
364413
def test_absolute_arcnames(self):
365414
with zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_STORED) as zipfp:
366415
zipfp.write(TESTFN, "/absolute")
@@ -508,6 +557,13 @@ def test_writestr_compression_bzip2(self):
508557
info = zipfp.getinfo('b.txt')
509558
self.assertEqual(info.compress_type, zipfile.ZIP_BZIP2)
510559

560+
@requires_lzma
561+
def test_writestr_compression_lzma(self):
562+
zipfp = zipfile.ZipFile(TESTFN2, "w")
563+
zipfp.writestr("b.txt", "hello world", compress_type=zipfile.ZIP_LZMA)
564+
info = zipfp.getinfo('b.txt')
565+
self.assertEqual(info.compress_type, zipfile.ZIP_LZMA)
566+
511567
def zip_test_writestr_permissions(self, f, compression):
512568
# Make sure that writestr creates files with mode 0600,
513569
# when it is passed a name rather than a ZipInfo instance.
@@ -686,6 +742,11 @@ def test_bzip2(self):
686742
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
687743
self.zip_test(f, zipfile.ZIP_BZIP2)
688744

745+
@requires_lzma
746+
def test_lzma(self):
747+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
748+
self.zip_test(f, zipfile.ZIP_LZMA)
749+
689750
def test_absolute_arcnames(self):
690751
with zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_STORED,
691752
allowZip64=True) as zipfp:
@@ -826,6 +887,16 @@ class OtherTests(unittest.TestCase):
826887
b'\x00 \x80\x80\x81\x00\x00\x00\x00afilePK'
827888
b'\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00\x00[\x00'
828889
b'\x00\x00\x00\x00'),
890+
zipfile.ZIP_LZMA: (
891+
b'PK\x03\x04\x14\x03\x00\x00\x0e\x00nu\x0c=FA'
892+
b'KE\x1b\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00af'
893+
b'ile\t\x04\x05\x00]\x00\x00\x00\x04\x004\x19I'
894+
b'\xee\x8d\xe9\x17\x89:3`\tq!.8\x00PK'
895+
b'\x01\x02\x14\x03\x14\x03\x00\x00\x0e\x00nu\x0c=FA'
896+
b'KE\x1b\x00\x00\x00n\x00\x00\x00\x05\x00\x00\x00\x00\x00'
897+
b'\x00\x00\x00\x00 \x80\x80\x81\x00\x00\x00\x00afil'
898+
b'ePK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x003\x00\x00'
899+
b'\x00>\x00\x00\x00\x00\x00'),
829900
}
830901

831902
def test_unsupported_version(self):
@@ -1104,6 +1175,10 @@ def test_testzip_with_bad_crc_deflated(self):
11041175
def test_testzip_with_bad_crc_bzip2(self):
11051176
self.check_testzip_with_bad_crc(zipfile.ZIP_BZIP2)
11061177

1178+
@requires_lzma
1179+
def test_testzip_with_bad_crc_lzma(self):
1180+
self.check_testzip_with_bad_crc(zipfile.ZIP_LZMA)
1181+
11071182
def check_read_with_bad_crc(self, compression):
11081183
"""Tests that files with bad CRCs raise a BadZipFile exception when read."""
11091184
zipdata = self.zips_with_bad_crc[compression]
@@ -1136,6 +1211,10 @@ def test_read_with_bad_crc_deflated(self):
11361211
def test_read_with_bad_crc_bzip2(self):
11371212
self.check_read_with_bad_crc(zipfile.ZIP_BZIP2)
11381213

1214+
@requires_lzma
1215+
def test_read_with_bad_crc_lzma(self):
1216+
self.check_read_with_bad_crc(zipfile.ZIP_LZMA)
1217+
11391218
def check_read_return_size(self, compression):
11401219
# Issue #9837: ZipExtFile.read() shouldn't return more bytes
11411220
# than requested.
@@ -1160,6 +1239,10 @@ def test_read_return_size_deflated(self):
11601239
def test_read_return_size_bzip2(self):
11611240
self.check_read_return_size(zipfile.ZIP_BZIP2)
11621241

1242+
@requires_lzma
1243+
def test_read_return_size_lzma(self):
1244+
self.check_read_return_size(zipfile.ZIP_LZMA)
1245+
11631246
def test_empty_zipfile(self):
11641247
# Check that creating a file in 'w' or 'a' mode and closing without
11651248
# adding any files to the archives creates a valid empty ZIP file
@@ -1306,6 +1389,11 @@ def test_bzip2(self):
13061389
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
13071390
self.zip_test(f, zipfile.ZIP_BZIP2)
13081391

1392+
@requires_lzma
1393+
def test_lzma(self):
1394+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1395+
self.zip_test(f, zipfile.ZIP_LZMA)
1396+
13091397
def zip_open_test(self, f, compression):
13101398
self.make_test_archive(f, compression)
13111399

@@ -1351,6 +1439,11 @@ def test_open_bzip2(self):
13511439
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
13521440
self.zip_open_test(f, zipfile.ZIP_BZIP2)
13531441

1442+
@requires_lzma
1443+
def test_open_lzma(self):
1444+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1445+
self.zip_open_test(f, zipfile.ZIP_LZMA)
1446+
13541447
def zip_random_open_test(self, f, compression):
13551448
self.make_test_archive(f, compression)
13561449

@@ -1384,6 +1477,11 @@ def test_random_open_bzip2(self):
13841477
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
13851478
self.zip_random_open_test(f, zipfile.ZIP_BZIP2)
13861479

1480+
@requires_lzma
1481+
def test_random_open_lzma(self):
1482+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1483+
self.zip_random_open_test(f, zipfile.ZIP_LZMA)
1484+
13871485

13881486
@requires_zlib
13891487
class TestsWithMultipleOpens(unittest.TestCase):
@@ -1628,6 +1726,31 @@ def test_iterlines_bzip2(self):
16281726
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
16291727
self.iterlines_test(f, zipfile.ZIP_BZIP2)
16301728

1729+
@requires_lzma
1730+
def test_read_lzma(self):
1731+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1732+
self.read_test(f, zipfile.ZIP_LZMA)
1733+
1734+
@requires_lzma
1735+
def test_readline_read_lzma(self):
1736+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1737+
self.readline_read_test(f, zipfile.ZIP_LZMA)
1738+
1739+
@requires_lzma
1740+
def test_readline_lzma(self):
1741+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1742+
self.readline_test(f, zipfile.ZIP_LZMA)
1743+
1744+
@requires_lzma
1745+
def test_readlines_lzma(self):
1746+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1747+
self.readlines_test(f, zipfile.ZIP_LZMA)
1748+
1749+
@requires_lzma
1750+
def test_iterlines_lzma(self):
1751+
for f in (TESTFN2, TemporaryFile(), io.BytesIO()):
1752+
self.iterlines_test(f, zipfile.ZIP_LZMA)
1753+
16311754
def tearDown(self):
16321755
for sep, fn in self.arcfiles.items():
16331756
os.remove(fn)

0 commit comments

Comments
 (0)