diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -77,6 +77,9 @@ Some facts and figures: is not suitable to open a certain (compressed) file for reading, :exc:`ReadError` is raised. Use *mode* ``'r'`` to avoid this. If a compression method is not supported, :exc:`CompressionError` is raised. + As a :term:`LBYL` alternative to catching this exception, a program can + inspect :data:`tarfile.compression_formats` to find what formats are + supported. If *fileobj* is specified, it is used as an alternative to a :term:`file object` opened in binary mode for *name*. It is supposed to be at position 0. @@ -207,6 +210,14 @@ The following variables are available on :func:`sys.getfilesystemencoding` otherwise. +.. data:: compression_formats + + A list containing all supported compression formats, sorted by strength; it + may contain ``'xz'``, ``'bz2'`` and ``'gz'``, in this order. + + .. versionadded:: 3.3 + + .. seealso:: Module :mod:`zipfile` diff --git a/Lib/tarfile.py b/Lib/tarfile.py --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -53,6 +53,21 @@ try: except ImportError: grp = pwd = None +try: + import zlib +except ImportError: + zlib = None + +try: + import lzma +except ImportError: + lzma = None + +try: + import bz2 +except ImportError: + bz2 = None + # os.symlink on Windows prior to 6.0 raises NotImplementedError symlink_exception = (AttributeError, NotImplementedError) try: @@ -63,7 +78,8 @@ except NameError: pass # from tarfile import * -__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"] +__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", + "compression_formats"] from builtins import open as _open # Since 'open' is TarFile.open @@ -413,11 +429,8 @@ class _Stream: try: if comptype == "gz": - try: - import zlib - except ImportError: + if zlib is None: raise CompressionError("zlib module is not available") - self.zlib = zlib self.crc = zlib.crc32(b"") if mode == "r": self._init_read_gz() @@ -426,9 +439,7 @@ class _Stream: self._init_write_gz() elif comptype == "bz2": - try: - import bz2 - except ImportError: + if bz2 is None: raise CompressionError("bz2 module is not available") if mode == "r": self.dbuf = b"" @@ -438,9 +449,7 @@ class _Stream: self.cmp = bz2.BZ2Compressor() elif comptype == "xz": - try: - import lzma - except ImportError: + if lzma is None: raise CompressionError("lzma module is not available") if mode == "r": self.dbuf = b"" @@ -465,10 +474,10 @@ class _Stream: def _init_write_gz(self): """Initialize for writing with gzip compression. """ - self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED, - -self.zlib.MAX_WBITS, - self.zlib.DEF_MEM_LEVEL, - 0) + self.cmp = zlib.compressobj(9, zlib.DEFLATED, + -zlib.MAX_WBITS, + zlib.DEF_MEM_LEVEL, + 0) timestamp = struct.pack(" 1 or mode not in "rw": raise ValueError("mode must be 'r' or 'w'.") - try: - import bz2 - except ImportError: + if bz2 is None: raise CompressionError("bz2 module is not available") fileobj = bz2.BZ2File(filename=name if fileobj is None else None, @@ -1814,9 +1821,7 @@ class TarFile(object): if mode not in ("r", "w"): raise ValueError("mode must be 'r' or 'w'") - try: - import lzma - except ImportError: + if lzma is None: raise CompressionError("lzma module is not available") fileobj = lzma.LZMAFile(filename=name if fileobj is None else None, @@ -2559,9 +2564,9 @@ class TarIter: self.index += 1 return tarinfo -#-------------------- -# exported functions -#-------------------- +#--------------------------------- +# exported functions and constants +#--------------------------------- def is_tarfile(name): """Return True if name points to a tar archive that we are able to handle, else return False. @@ -2575,3 +2580,8 @@ def is_tarfile(name): bltn_open = open open = TarFile.open + +# supported compression formats, sorted by strength +compression_formats = [name for (name, module) in + [("xz", lzma), ("bz2", bz2), ("gz", zlib)] + if module is not None] diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1657,6 +1657,18 @@ class MiscTest(unittest.TestCase): self.assertRaises(ValueError, tarfile.itn, -0x10000000001, 6, tarfile.GNU_FORMAT) self.assertRaises(ValueError, tarfile.itn, 0x10000000000, 6, tarfile.GNU_FORMAT) + def test_compression_formats(self): + expected = [] + # make sure the available formats are sorted by strength + if lzma is not None: + expected.append("xz") + if bz2 is not None: + expected.append("bz2") + if gzip is not None: + expected.append("gz") + self.assertEqual(tarfile.compression_formats, expected) + # test that if an item is deleted, nothing changes? + class ContextManagerTest(unittest.TestCase):