Index: Doc/library/zipfile.rst =================================================================== --- Doc/library/zipfile.rst (revision 59715) +++ Doc/library/zipfile.rst (working copy) @@ -179,7 +179,19 @@ .. versionadded:: 2.6 +.. method:: ZipFile.extract(member[, path[, pwd]]) + Extract a member from the archive to the current working directory, using its + full name. Its file information is extracted as accurately as possible. *member* + may be a filename or a :class:`ZipInfo` object. You can specify a different + directory using *path*. + +.. method:: ZipFile.extractall([path[, members]]) + + Extract all members from the archive to the current working directory. *path* + specifies a different directory to extract to. *members* is optional and must + be a subset of the list returned by :meth:`namelist`. + .. method:: ZipFile.printdir() Print a table of contents for the archive to ``sys.stdout``. @@ -249,6 +261,13 @@ created with mode ``'r'`` will raise a :exc:`RuntimeError`. Calling :meth:`writestr` on a closed ZipFile will raise a :exc:`RuntimeError`. + .. note:: + + When passing a :class:`ZipInfo` instance as the *zinfo_or_acrname* parameter, + the compression method used will be that specified in the *compress_type* + member of the given :class:`ZipInfo` instance. By default, the + :class:`ZipInfo` constructor sets this member to :const:`ZIP_STORED`. + The following data attribute is also available: Index: Lib/zipfile.py =================================================================== --- Lib/zipfile.py (revision 59715) +++ Lib/zipfile.py (working copy) @@ -1,7 +1,7 @@ """ Read and write ZIP files. """ -import struct, os, time, sys +import struct, os, time, sys, shutil import binascii, cStringIO try: @@ -807,6 +807,62 @@ zef.set_univ_newlines(True) return zef + def extract(self, member, path=None, pwd=None): + """Extract a member from the archive to the current working directory, + using its full name. Its file information is extracted as accurately + as possible. `member' may be a filename or a ZipInfo object. You can + specify a different directory using `path'. + """ + if not isinstance(member, ZipInfo): + member = self.getinfo(member) + + if path is None: + path = os.getcwd() + + return self._extract_member(member, path) + + def extractall(self, path=None, members=None): + """Extract all members from the archive to the current working + directory. `path' specifies a different directory to extract to. + `members' is optional and must be a subset of the list returned + by namelist(). + """ + if members is None: + members = self.namelist() + + for zipinfo in members: + self.extract(zipinfo, path) + + def _extract_member(self, member, targetpath): + """Extract the ZipInfo object 'member' to a physical + file on the path targetpath. + """ + # build the destination pathname, replacing + # forward slashes to platform specific separators. + if targetpath[-1:] == "/": + targetpath = targetpath[:-1] + + # don't include leading "/" from file name if present + if os.path.isabs(member.filename): + targetpath = os.path.join(targetpath, member.filename[1:]) + else: + targetpath = os.path.join(targetpath, member.filename) + + targetpath = os.path.normpath(targetpath) + + # Create all upper directories if necessary. + upperdirs = os.path.dirname(targetpath) + if upperdirs and not os.path.exists(upperdirs): + os.makedirs(upperdirs) + + source = self.open(member.filename) + target = file(targetpath, "wb") + shutil.copyfileobj(source, target) + source.close() + target.close() + + return targetpath + def _writecheck(self, zinfo): """Check for errors before writing a file to the archive.""" if zinfo.filename in self.NameToInfo: Index: Lib/test/test_zipfile.py =================================================================== --- Lib/test/test_zipfile.py (revision 59715) +++ Lib/test/test_zipfile.py (working copy) @@ -16,6 +16,11 @@ TESTFN2 = TESTFN + "2" FIXEDTEST_SIZE = 1000 +SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'), + ('ziptest2dir/_ziptest2', 'qawsedrftg'), + ('/ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), + ('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')] + class TestsWithSourceFile(unittest.TestCase): def setUp(self): self.line_gen = ["Zipfile test line %d. random float: %f" % (i, random()) @@ -296,6 +301,57 @@ self.assertRaises(RuntimeError, zipf.write, TESTFN) zipf.close() + def testExtract(self): + zipfp = zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_STORED) + for fpath, fdata in SMALL_TEST_DATA: + zipfp.writestr(fpath, fdata) + zipfp.close() + + zipfp = zipfile.ZipFile(TESTFN2, "r") + for fpath, fdata in SMALL_TEST_DATA: + writtenfile = zipfp.extract(fpath) + + # make sure it was written to the right place + if os.path.isabs(fpath): + correctfile = os.path.join(os.getcwd(), fpath[1:]) + else: + correctfile = os.path.join(os.getcwd(), fpath) + + self.assertEqual(writtenfile, correctfile) + + # make sure correct data is in correct file + self.assertEqual(fdata, file(writtenfile, "rb").read()) + + os.remove(writtenfile) + + zipfp.close() + + # remove the test file subdirectories + shutil.rmtree(os.path.join(os.getcwd(), 'ziptest2dir')) + + def testExtractAll(self): + zipfp = zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_STORED) + for fpath, fdata in SMALL_TEST_DATA: + zipfp.writestr(fpath, fdata) + zipfp.close() + + zipfp = zipfile.ZipFile(TESTFN2, "r") + zipfp.extractall() + for fpath, fdata in SMALL_TEST_DATA: + if os.path.isabs(fpath): + outfile = os.path.join(os.getcwd(), fpath[1:]) + else: + outfile = os.path.join(os.getcwd(), fpath) + + self.assertEqual(fdata, file(outfile, "rb").read()) + + os.remove(outfile) + + zipfp.close() + + # remove the test file subdirectories + shutil.rmtree(os.path.join(os.getcwd(), 'ziptest2dir')) + def tearDown(self): os.remove(TESTFN) os.remove(TESTFN2)