gh-115961: Add name and mode attributes for compressed file-like objects (GH-116036)

* Add name and mode attributes for compressed and archived file-like objects
  in modules bz2, lzma, tarfile and zipfile.
* Change the value of the mode attribute of GzipFile from integer (1 or 2)
  to string ('rb' or 'wb').
* Change the value of the mode attribute of ZipExtFile from 'r' to 'rb'.
This commit is contained in:
Serhiy Storchaka 2024-04-21 11:46:39 +03:00 committed by GitHub
parent ccda738284
commit 51ef89cd9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 246 additions and 37 deletions

View File

@ -91,7 +91,7 @@ The :mod:`bz2` module contains:
and :meth:`~io.IOBase.truncate`. and :meth:`~io.IOBase.truncate`.
Iteration and the :keyword:`with` statement are supported. Iteration and the :keyword:`with` statement are supported.
:class:`BZ2File` also provides the following methods: :class:`BZ2File` also provides the following methods and attributes:
.. method:: peek([n]) .. method:: peek([n])
@ -148,6 +148,19 @@ The :mod:`bz2` module contains:
.. versionadded:: 3.3 .. versionadded:: 3.3
.. attribute:: mode
``'rb'`` for reading and ``'wb'`` for writing.
.. versionadded:: 3.13
.. attribute:: name
The bzip2 file name. Equivalent to the :attr:`~io.FileIO.name`
attribute of the underlying :term:`file object`.
.. versionadded:: 3.13
.. versionchanged:: 3.1 .. versionchanged:: 3.1
Support for the :keyword:`with` statement was added. Support for the :keyword:`with` statement was added.

View File

@ -133,6 +133,13 @@ The module defines the following items:
.. versionadded:: 3.2 .. versionadded:: 3.2
.. attribute:: mode
``'rb'`` for reading and ``'wb'`` for writing.
.. versionchanged:: 3.13
In previous versions it was an integer ``1`` or ``2``.
.. attribute:: mtime .. attribute:: mtime
When decompressing, this attribute is set to the last timestamp in the most When decompressing, this attribute is set to the last timestamp in the most
@ -168,14 +175,14 @@ The module defines the following items:
.. versionchanged:: 3.6 .. versionchanged:: 3.6
Accepts a :term:`path-like object`. Accepts a :term:`path-like object`.
.. versionchanged:: 3.12
Remove the ``filename`` attribute, use the :attr:`~GzipFile.name`
attribute instead.
.. deprecated:: 3.9 .. deprecated:: 3.9
Opening :class:`GzipFile` for writing without specifying the *mode* Opening :class:`GzipFile` for writing without specifying the *mode*
argument is deprecated. argument is deprecated.
.. versionchanged:: 3.12
Remove the ``filename`` attribute, use the :attr:`~GzipFile.name`
attribute instead.
.. function:: compress(data, compresslevel=9, *, mtime=None) .. function:: compress(data, compresslevel=9, *, mtime=None)

View File

@ -104,7 +104,7 @@ Reading and writing compressed files
and :meth:`~io.IOBase.truncate`. and :meth:`~io.IOBase.truncate`.
Iteration and the :keyword:`with` statement are supported. Iteration and the :keyword:`with` statement are supported.
The following method is also provided: The following method and attributes are also provided:
.. method:: peek(size=-1) .. method:: peek(size=-1)
@ -117,6 +117,20 @@ Reading and writing compressed files
file object (e.g. if the :class:`LZMAFile` was constructed by passing a file object (e.g. if the :class:`LZMAFile` was constructed by passing a
file object for *filename*). file object for *filename*).
.. attribute:: mode
``'rb'`` for reading and ``'wb'`` for writing.
.. versionadded:: 3.13
.. attribute:: name
The lzma file name. Equivalent to the :attr:`~io.FileIO.name`
attribute of the underlying :term:`file object`.
.. versionadded:: 3.13
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Added support for the ``"x"`` and ``"xb"`` modes. Added support for the ``"x"`` and ``"xb"`` modes.

View File

@ -565,6 +565,10 @@ be finalized; only the internally used file object will be closed. See the
.. versionchanged:: 3.3 .. versionchanged:: 3.3
Return an :class:`io.BufferedReader` object. Return an :class:`io.BufferedReader` object.
.. versionchanged:: 3.13
The returned :class:`io.BufferedReader` object has the :attr:`!mode`
attribute which is always equal to ``'rb'``.
.. attribute:: TarFile.errorlevel .. attribute:: TarFile.errorlevel
:type: int :type: int

View File

@ -301,6 +301,10 @@ ZipFile Objects
attempting to read or write other files in the ZIP file will raise a attempting to read or write other files in the ZIP file will raise a
:exc:`ValueError`. :exc:`ValueError`.
In both cases the file-like object has also attributes :attr:`!name`,
which is equivalent to the name of a file within the archive, and
:attr:`!mode`, which is ``'rb'`` or ``'wb'`` depending on the input mode.
When writing a file, if the file size is not known in advance but may exceed When writing a file, if the file size is not known in advance but may exceed
2 GiB, pass ``force_zip64=True`` to ensure that the header format is 2 GiB, pass ``force_zip64=True`` to ensure that the header format is
capable of supporting large files. If the file size is known in advance, capable of supporting large files. If the file size is known in advance,
@ -325,6 +329,12 @@ ZipFile Objects
Calling :meth:`.open` on a closed ZipFile will raise a :exc:`ValueError`. Calling :meth:`.open` on a closed ZipFile will raise a :exc:`ValueError`.
Previously, a :exc:`RuntimeError` was raised. Previously, a :exc:`RuntimeError` was raised.
.. versionchanged:: 3.13
Added attributes :attr:`!name` and :attr:`!mode` for the writeable
file-like object.
The value of the :attr:`!mode` attribute for the readable file-like
object was changed from ``'r'`` to ``'rb'``.
.. method:: ZipFile.extract(member, path=None, pwd=None) .. method:: ZipFile.extract(member, path=None, pwd=None)

View File

@ -206,6 +206,11 @@ Other Language Changes
(Contributed by Victor Stinner in :gh:`114570`.) (Contributed by Victor Stinner in :gh:`114570`.)
* Added :attr:`!name` and :attr:`!mode` attributes for compressed
and archived file-like objects in modules :mod:`bz2`, :mod:`lzma`,
:mod:`tarfile` and :mod:`zipfile`.
(Contributed by Serhiy Storchaka in :gh:`115961`.)
* Allow controlling Expat >=2.6.0 reparse deferral (:cve:`2023-52425`) * Allow controlling Expat >=2.6.0 reparse deferral (:cve:`2023-52425`)
by adding five new methods: by adding five new methods:
@ -1605,6 +1610,12 @@ Changes in the Python API
than directories only. Users may add a trailing slash to match only than directories only. Users may add a trailing slash to match only
directories. directories.
* The value of the :attr:`!mode` attribute of :class:`gzip.GzipFile` was
changed from integer (``1`` or ``2``) to string (``'rb'`` or ``'wb'``).
The value of the :attr:`!mode` attribute of the readable file-like object
returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``.
(Contributed by Serhiy Storchaka in :gh:`115961`.)
* :c:func:`!PyCode_GetFirstFree` is an unstable API now and has been renamed * :c:func:`!PyCode_GetFirstFree` is an unstable API now and has been renamed
to :c:func:`PyUnstable_Code_GetFirstFree`. to :c:func:`PyUnstable_Code_GetFirstFree`.
(Contributed by Bogdan Romanyuk in :gh:`115781`.) (Contributed by Bogdan Romanyuk in :gh:`115781`.)

View File

@ -17,7 +17,7 @@ import _compression
from _bz2 import BZ2Compressor, BZ2Decompressor from _bz2 import BZ2Compressor, BZ2Decompressor
_MODE_CLOSED = 0 # Value 0 no longer used
_MODE_READ = 1 _MODE_READ = 1
# Value 2 no longer used # Value 2 no longer used
_MODE_WRITE = 3 _MODE_WRITE = 3
@ -54,7 +54,7 @@ class BZ2File(_compression.BaseStream):
""" """
self._fp = None self._fp = None
self._closefp = False self._closefp = False
self._mode = _MODE_CLOSED self._mode = None
if not (1 <= compresslevel <= 9): if not (1 <= compresslevel <= 9):
raise ValueError("compresslevel must be between 1 and 9") raise ValueError("compresslevel must be between 1 and 9")
@ -100,7 +100,7 @@ class BZ2File(_compression.BaseStream):
May be called more than once without error. Once the file is May be called more than once without error. Once the file is
closed, any other operation on it will raise a ValueError. closed, any other operation on it will raise a ValueError.
""" """
if self._mode == _MODE_CLOSED: if self.closed:
return return
try: try:
if self._mode == _MODE_READ: if self._mode == _MODE_READ:
@ -115,13 +115,21 @@ class BZ2File(_compression.BaseStream):
finally: finally:
self._fp = None self._fp = None
self._closefp = False self._closefp = False
self._mode = _MODE_CLOSED
self._buffer = None self._buffer = None
@property @property
def closed(self): def closed(self):
"""True if this file is closed.""" """True if this file is closed."""
return self._mode == _MODE_CLOSED return self._fp is None
@property
def name(self):
self._check_not_closed()
return self._fp.name
@property
def mode(self):
return 'wb' if self._mode == _MODE_WRITE else 'rb'
def fileno(self): def fileno(self):
"""Return the file descriptor for the underlying file.""" """Return the file descriptor for the underlying file."""

View File

@ -15,7 +15,8 @@ __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"]
FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16 FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
READ, WRITE = 1, 2 READ = 'rb'
WRITE = 'wb'
_COMPRESS_LEVEL_FAST = 1 _COMPRESS_LEVEL_FAST = 1
_COMPRESS_LEVEL_TRADEOFF = 6 _COMPRESS_LEVEL_TRADEOFF = 6

View File

@ -29,7 +29,7 @@ from _lzma import _encode_filter_properties, _decode_filter_properties
import _compression import _compression
_MODE_CLOSED = 0 # Value 0 no longer used
_MODE_READ = 1 _MODE_READ = 1
# Value 2 no longer used # Value 2 no longer used
_MODE_WRITE = 3 _MODE_WRITE = 3
@ -92,7 +92,7 @@ class LZMAFile(_compression.BaseStream):
""" """
self._fp = None self._fp = None
self._closefp = False self._closefp = False
self._mode = _MODE_CLOSED self._mode = None
if mode in ("r", "rb"): if mode in ("r", "rb"):
if check != -1: if check != -1:
@ -137,7 +137,7 @@ class LZMAFile(_compression.BaseStream):
May be called more than once without error. Once the file is May be called more than once without error. Once the file is
closed, any other operation on it will raise a ValueError. closed, any other operation on it will raise a ValueError.
""" """
if self._mode == _MODE_CLOSED: if self.closed:
return return
try: try:
if self._mode == _MODE_READ: if self._mode == _MODE_READ:
@ -153,12 +153,20 @@ class LZMAFile(_compression.BaseStream):
finally: finally:
self._fp = None self._fp = None
self._closefp = False self._closefp = False
self._mode = _MODE_CLOSED
@property @property
def closed(self): def closed(self):
"""True if this file is closed.""" """True if this file is closed."""
return self._mode == _MODE_CLOSED return self._fp is None
@property
def name(self):
self._check_not_closed()
return self._fp.name
@property
def mode(self):
return 'wb' if self._mode == _MODE_WRITE else 'rb'
def fileno(self): def fileno(self):
"""Return the file descriptor for the underlying file.""" """Return the file descriptor for the underlying file."""

View File

@ -636,6 +636,10 @@ class _FileInFile(object):
def flush(self): def flush(self):
pass pass
@property
def mode(self):
return 'rb'
def readable(self): def readable(self):
return True return True

View File

@ -540,40 +540,54 @@ class BZ2FileTest(BaseTest):
def testOpenFilename(self): def testOpenFilename(self):
with BZ2File(self.filename, "wb") as f: with BZ2File(self.filename, "wb") as f:
f.write(b'content') f.write(b'content')
self.assertEqual(f.name, self.filename)
self.assertIsInstance(f.fileno(), int) self.assertIsInstance(f.fileno(), int)
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
with BZ2File(self.filename, "ab") as f: with BZ2File(self.filename, "ab") as f:
f.write(b'appendix') f.write(b'appendix')
self.assertEqual(f.name, self.filename)
self.assertIsInstance(f.fileno(), int) self.assertIsInstance(f.fileno(), int)
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
with BZ2File(self.filename, 'rb') as f: with BZ2File(self.filename, 'rb') as f:
self.assertEqual(f.read(), b'contentappendix') self.assertEqual(f.read(), b'contentappendix')
self.assertEqual(f.name, self.filename)
self.assertIsInstance(f.fileno(), int) self.assertIsInstance(f.fileno(), int)
self.assertEqual(f.mode, 'rb')
self.assertIs(f.readable(), True) self.assertIs(f.readable(), True)
self.assertIs(f.writable(), False) self.assertIs(f.writable(), False)
self.assertIs(f.seekable(), True) self.assertIs(f.seekable(), True)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
f.fileno() f.name
self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'rb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -582,13 +596,18 @@ class BZ2FileTest(BaseTest):
with open(self.filename, 'wb') as raw: with open(self.filename, 'wb') as raw:
with BZ2File(raw, 'wb') as f: with BZ2File(raw, 'wb') as f:
f.write(b'content') f.write(b'content')
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -596,13 +615,18 @@ class BZ2FileTest(BaseTest):
with open(self.filename, 'ab') as raw: with open(self.filename, 'ab') as raw:
with BZ2File(raw, 'ab') as f: with BZ2File(raw, 'ab') as f:
f.write(b'appendix') f.write(b'appendix')
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -610,14 +634,18 @@ class BZ2FileTest(BaseTest):
with open(self.filename, 'rb') as raw: with open(self.filename, 'rb') as raw:
with BZ2File(raw, 'rb') as f: with BZ2File(raw, 'rb') as f:
self.assertEqual(f.read(), b'contentappendix') self.assertEqual(f.read(), b'contentappendix')
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'rb')
self.assertIs(f.readable(), True) self.assertIs(f.readable(), True)
self.assertIs(f.writable(), False) self.assertIs(f.writable(), False)
self.assertIs(f.seekable(), True) self.assertIs(f.seekable(), True)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
f.fileno() f.name
self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'rb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -626,61 +654,91 @@ class BZ2FileTest(BaseTest):
bio = BytesIO() bio = BytesIO()
with BZ2File(bio, 'wb') as f: with BZ2File(bio, 'wb') as f:
f.write(b'content') f.write(b'content')
with self.assertRaises(AttributeError):
f.name
self.assertRaises(io.UnsupportedOperation, f.fileno) self.assertRaises(io.UnsupportedOperation, f.fileno)
self.assertEqual(f.mode, 'wb')
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
with BZ2File(bio, 'ab') as f: with BZ2File(bio, 'ab') as f:
f.write(b'appendix') f.write(b'appendix')
with self.assertRaises(AttributeError):
f.name
self.assertRaises(io.UnsupportedOperation, f.fileno) self.assertRaises(io.UnsupportedOperation, f.fileno)
self.assertEqual(f.mode, 'wb')
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
bio.seek(0) bio.seek(0)
with BZ2File(bio, 'rb') as f: with BZ2File(bio, 'rb') as f:
self.assertEqual(f.read(), b'contentappendix') self.assertEqual(f.read(), b'contentappendix')
with self.assertRaises(AttributeError):
f.name
self.assertRaises(io.UnsupportedOperation, f.fileno) self.assertRaises(io.UnsupportedOperation, f.fileno)
self.assertEqual(f.mode, 'rb')
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
f.fileno() f.name
self.assertRaises(ValueError, f.fileno)
def testOpenFileWithIntName(self): def testOpenFileWithIntName(self):
fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
with open(fd, 'wb') as raw: with open(fd, 'wb') as raw:
with BZ2File(raw, 'wb') as f: with BZ2File(raw, 'wb') as f:
f.write(b'content') f.write(b'content')
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'wb')
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND) fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND)
with open(fd, 'ab') as raw: with open(fd, 'ab') as raw:
with BZ2File(raw, 'ab') as f: with BZ2File(raw, 'ab') as f:
f.write(b'appendix') f.write(b'appendix')
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'wb')
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
fd = os.open(self.filename, os.O_RDONLY) fd = os.open(self.filename, os.O_RDONLY)
with open(fd, 'rb') as raw: with open(fd, 'rb') as raw:
with BZ2File(raw, 'rb') as f: with BZ2File(raw, 'rb') as f:
self.assertEqual(f.read(), b'contentappendix') self.assertEqual(f.read(), b'contentappendix')
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'rb')
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
f.fileno() f.name
self.assertRaises(ValueError, f.fileno)
def testOpenBytesFilename(self): def testOpenBytesFilename(self):
str_filename = self.filename str_filename = self.filename
bytes_filename = os.fsencode(str_filename) bytes_filename = os.fsencode(str_filename)
with BZ2File(bytes_filename, "wb") as f: with BZ2File(bytes_filename, "wb") as f:
f.write(self.DATA) f.write(self.DATA)
self.assertEqual(f.name, bytes_filename)
with BZ2File(bytes_filename, "rb") as f: with BZ2File(bytes_filename, "rb") as f:
self.assertEqual(f.read(), self.DATA) self.assertEqual(f.read(), self.DATA)
self.assertEqual(f.name, bytes_filename)
# Sanity check that we are actually operating on the right file. # Sanity check that we are actually operating on the right file.
with BZ2File(str_filename, "rb") as f: with BZ2File(str_filename, "rb") as f:
self.assertEqual(f.read(), self.DATA) self.assertEqual(f.read(), self.DATA)
self.assertEqual(f.name, str_filename)
def testOpenPathLikeFilename(self): def testOpenPathLikeFilename(self):
filename = FakePath(self.filename) filename = FakePath(self.filename)
with BZ2File(filename, "wb") as f: with BZ2File(filename, "wb") as f:
f.write(self.DATA) f.write(self.DATA)
self.assertEqual(f.name, self.filename)
with BZ2File(filename, "rb") as f: with BZ2File(filename, "rb") as f:
self.assertEqual(f.read(), self.DATA) self.assertEqual(f.read(), self.DATA)
self.assertEqual(f.name, self.filename)
def testDecompressLimited(self): def testDecompressLimited(self):
"""Decompressed data buffering should be limited""" """Decompressed data buffering should be limited"""
@ -701,6 +759,9 @@ class BZ2FileTest(BaseTest):
with BZ2File(bio) as bz2f: with BZ2File(bio) as bz2f:
self.assertRaises(TypeError, bz2f.read, float()) self.assertRaises(TypeError, bz2f.read, float())
self.assertEqual(bz2f.read(), self.TEXT) self.assertEqual(bz2f.read(), self.TEXT)
with self.assertRaises(AttributeError):
bz2.name
self.assertEqual(bz2f.mode, 'rb')
self.assertFalse(bio.closed) self.assertFalse(bio.closed)
def testPeekBytesIO(self): def testPeekBytesIO(self):
@ -716,6 +777,9 @@ class BZ2FileTest(BaseTest):
with BZ2File(bio, "w") as bz2f: with BZ2File(bio, "w") as bz2f:
self.assertRaises(TypeError, bz2f.write) self.assertRaises(TypeError, bz2f.write)
bz2f.write(self.TEXT) bz2f.write(self.TEXT)
with self.assertRaises(AttributeError):
bz2.name
self.assertEqual(bz2f.mode, 'wb')
self.assertEqual(ext_decompress(bio.getvalue()), self.TEXT) self.assertEqual(ext_decompress(bio.getvalue()), self.TEXT)
self.assertFalse(bio.closed) self.assertFalse(bio.closed)

View File

@ -587,6 +587,8 @@ class TestGzip(BaseTest):
self.assertRaises(AttributeError, f.fileno) self.assertRaises(AttributeError, f.fileno)
def test_fileobj_mode(self): def test_fileobj_mode(self):
self.assertEqual(gzip.READ, 'rb')
self.assertEqual(gzip.WRITE, 'wb')
gzip.GzipFile(self.filename, "wb").close() gzip.GzipFile(self.filename, "wb").close()
with open(self.filename, "r+b") as f: with open(self.filename, "r+b") as f:
with gzip.GzipFile(fileobj=f, mode='r') as g: with gzip.GzipFile(fileobj=f, mode='r') as g:

View File

@ -551,19 +551,25 @@ class FileTestCase(unittest.TestCase):
with TempFile(filename, COMPRESSED_XZ): with TempFile(filename, COMPRESSED_XZ):
with LZMAFile(filename) as f: with LZMAFile(filename) as f:
self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), INPUT)
self.assertEqual(f.name, TESTFN)
with LZMAFile(filename, "a") as f: with LZMAFile(filename, "a") as f:
f.write(INPUT) f.write(INPUT)
self.assertEqual(f.name, TESTFN)
with LZMAFile(filename) as f: with LZMAFile(filename) as f:
self.assertEqual(f.read(), INPUT * 2) self.assertEqual(f.read(), INPUT * 2)
self.assertEqual(f.name, TESTFN)
def test_init_with_filename(self): def test_init_with_filename(self):
with TempFile(TESTFN, COMPRESSED_XZ): with TempFile(TESTFN, COMPRESSED_XZ):
with LZMAFile(TESTFN) as f: with LZMAFile(TESTFN) as f:
pass self.assertEqual(f.name, TESTFN)
self.assertEqual(f.mode, 'rb')
with LZMAFile(TESTFN, "w") as f: with LZMAFile(TESTFN, "w") as f:
pass self.assertEqual(f.name, TESTFN)
self.assertEqual(f.mode, 'wb')
with LZMAFile(TESTFN, "a") as f: with LZMAFile(TESTFN, "a") as f:
pass self.assertEqual(f.name, TESTFN)
self.assertEqual(f.mode, 'wb')
def test_init_mode(self): def test_init_mode(self):
with TempFile(TESTFN): with TempFile(TESTFN):
@ -586,6 +592,7 @@ class FileTestCase(unittest.TestCase):
unlink(TESTFN) unlink(TESTFN)
with LZMAFile(TESTFN, mode) as f: with LZMAFile(TESTFN, mode) as f:
pass pass
self.assertEqual(f.mode, 'wb')
with self.assertRaises(FileExistsError): with self.assertRaises(FileExistsError):
LZMAFile(TESTFN, mode) LZMAFile(TESTFN, mode)
@ -865,13 +872,18 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(TESTFN) as f: with LZMAFile(TESTFN) as f:
self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), INPUT)
self.assertEqual(f.read(), b"") self.assertEqual(f.read(), b"")
self.assertEqual(f.name, TESTFN)
self.assertIsInstance(f.fileno(), int) self.assertIsInstance(f.fileno(), int)
self.assertEqual(f.mode, 'rb')
self.assertIs(f.readable(), True) self.assertIs(f.readable(), True)
self.assertIs(f.writable(), False) self.assertIs(f.writable(), False)
self.assertIs(f.seekable(), True) self.assertIs(f.seekable(), True)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'rb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -882,6 +894,7 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(bytes_filename) as f: with LZMAFile(bytes_filename) as f:
self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), INPUT)
self.assertEqual(f.read(), b"") self.assertEqual(f.read(), b"")
self.assertEqual(f.name, bytes_filename)
def test_read_from_fileobj(self): def test_read_from_fileobj(self):
with TempFile(TESTFN, COMPRESSED_XZ): with TempFile(TESTFN, COMPRESSED_XZ):
@ -889,13 +902,18 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(raw) as f: with LZMAFile(raw) as f:
self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), INPUT)
self.assertEqual(f.read(), b"") self.assertEqual(f.read(), b"")
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'rb')
self.assertIs(f.readable(), True) self.assertIs(f.readable(), True)
self.assertIs(f.writable(), False) self.assertIs(f.writable(), False)
self.assertIs(f.seekable(), True) self.assertIs(f.seekable(), True)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'rb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -907,13 +925,18 @@ class FileTestCase(unittest.TestCase):
with LZMAFile(raw) as f: with LZMAFile(raw) as f:
self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), INPUT)
self.assertEqual(f.read(), b"") self.assertEqual(f.read(), b"")
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'rb')
self.assertIs(f.readable(), True) self.assertIs(f.readable(), True)
self.assertIs(f.writable(), False) self.assertIs(f.writable(), False)
self.assertIs(f.seekable(), True) self.assertIs(f.seekable(), True)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'rb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -1045,6 +1068,8 @@ class FileTestCase(unittest.TestCase):
with BytesIO() as dst: with BytesIO() as dst:
with LZMAFile(dst, "w") as f: with LZMAFile(dst, "w") as f:
f.write(INPUT) f.write(INPUT)
with self.assertRaises(AttributeError):
f.name
expected = lzma.compress(INPUT) expected = lzma.compress(INPUT)
self.assertEqual(dst.getvalue(), expected) self.assertEqual(dst.getvalue(), expected)
with BytesIO() as dst: with BytesIO() as dst:
@ -1081,23 +1106,31 @@ class FileTestCase(unittest.TestCase):
with BytesIO() as dst: with BytesIO() as dst:
with LZMAFile(dst, "w") as f: with LZMAFile(dst, "w") as f:
f.write(part1) f.write(part1)
self.assertEqual(f.mode, 'wb')
with LZMAFile(dst, "a") as f: with LZMAFile(dst, "a") as f:
f.write(part2) f.write(part2)
self.assertEqual(f.mode, 'wb')
with LZMAFile(dst, "a") as f: with LZMAFile(dst, "a") as f:
f.write(part3) f.write(part3)
self.assertEqual(f.mode, 'wb')
self.assertEqual(dst.getvalue(), expected) self.assertEqual(dst.getvalue(), expected)
def test_write_to_file(self): def test_write_to_file(self):
try: try:
with LZMAFile(TESTFN, "w") as f: with LZMAFile(TESTFN, "w") as f:
f.write(INPUT) f.write(INPUT)
self.assertEqual(f.name, TESTFN)
self.assertIsInstance(f.fileno(), int) self.assertIsInstance(f.fileno(), int)
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -1113,6 +1146,7 @@ class FileTestCase(unittest.TestCase):
try: try:
with LZMAFile(bytes_filename, "w") as f: with LZMAFile(bytes_filename, "w") as f:
f.write(INPUT) f.write(INPUT)
self.assertEqual(f.name, bytes_filename)
expected = lzma.compress(INPUT) expected = lzma.compress(INPUT)
with open(TESTFN, "rb") as f: with open(TESTFN, "rb") as f:
self.assertEqual(f.read(), expected) self.assertEqual(f.read(), expected)
@ -1124,13 +1158,18 @@ class FileTestCase(unittest.TestCase):
with open(TESTFN, "wb") as raw: with open(TESTFN, "wb") as raw:
with LZMAFile(raw, "w") as f: with LZMAFile(raw, "w") as f:
f.write(INPUT) f.write(INPUT)
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -1147,13 +1186,18 @@ class FileTestCase(unittest.TestCase):
with open(fd, 'wb') as raw: with open(fd, 'wb') as raw:
with LZMAFile(raw, "w") as f: with LZMAFile(raw, "w") as f:
f.write(INPUT) f.write(INPUT)
self.assertEqual(f.name, raw.name)
self.assertEqual(f.fileno(), raw.fileno()) self.assertEqual(f.fileno(), raw.fileno())
self.assertEqual(f.mode, 'wb')
self.assertIs(f.readable(), False) self.assertIs(f.readable(), False)
self.assertIs(f.writable(), True) self.assertIs(f.writable(), True)
self.assertIs(f.seekable(), False) self.assertIs(f.seekable(), False)
self.assertIs(f.closed, False) self.assertIs(f.closed, False)
self.assertIs(f.closed, True) self.assertIs(f.closed, True)
with self.assertRaises(ValueError):
f.name
self.assertRaises(ValueError, f.fileno) self.assertRaises(ValueError, f.fileno)
self.assertEqual(f.mode, 'wb')
self.assertRaises(ValueError, f.readable) self.assertRaises(ValueError, f.readable)
self.assertRaises(ValueError, f.writable) self.assertRaises(ValueError, f.writable)
self.assertRaises(ValueError, f.seekable) self.assertRaises(ValueError, f.seekable)
@ -1172,10 +1216,13 @@ class FileTestCase(unittest.TestCase):
try: try:
with LZMAFile(TESTFN, "w") as f: with LZMAFile(TESTFN, "w") as f:
f.write(part1) f.write(part1)
self.assertEqual(f.mode, 'wb')
with LZMAFile(TESTFN, "a") as f: with LZMAFile(TESTFN, "a") as f:
f.write(part2) f.write(part2)
self.assertEqual(f.mode, 'wb')
with LZMAFile(TESTFN, "a") as f: with LZMAFile(TESTFN, "a") as f:
f.write(part3) f.write(part3)
self.assertEqual(f.mode, 'wb')
with open(TESTFN, "rb") as f: with open(TESTFN, "rb") as f:
self.assertEqual(f.read(), expected) self.assertEqual(f.read(), expected)
finally: finally:
@ -1373,11 +1420,13 @@ class OpenTestCase(unittest.TestCase):
with TempFile(filename): with TempFile(filename):
with lzma.open(filename, "wb") as f: with lzma.open(filename, "wb") as f:
f.write(INPUT) f.write(INPUT)
self.assertEqual(f.name, TESTFN)
with open(filename, "rb") as f: with open(filename, "rb") as f:
file_data = lzma.decompress(f.read()) file_data = lzma.decompress(f.read())
self.assertEqual(file_data, INPUT) self.assertEqual(file_data, INPUT)
with lzma.open(filename, "rb") as f: with lzma.open(filename, "rb") as f:
self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), INPUT)
self.assertEqual(f.name, TESTFN)
def test_bad_params(self): def test_bad_params(self):
# Test invalid parameter combinations. # Test invalid parameter combinations.

View File

@ -513,6 +513,7 @@ class CommonReadTest(ReadTest):
with self.tar.extractfile(file) as fobj: with self.tar.extractfile(file) as fobj:
self.assertEqual(fobj.name, 'ustar/regtype') self.assertEqual(fobj.name, 'ustar/regtype')
self.assertRaises(AttributeError, fobj.fileno) self.assertRaises(AttributeError, fobj.fileno)
self.assertEqual(fobj.mode, 'rb')
self.assertIs(fobj.readable(), True) self.assertIs(fobj.readable(), True)
self.assertIs(fobj.writable(), False) self.assertIs(fobj.writable(), False)
if self.is_stream: if self.is_stream:
@ -523,6 +524,7 @@ class CommonReadTest(ReadTest):
self.assertIs(fobj.closed, True) self.assertIs(fobj.closed, True)
self.assertEqual(fobj.name, 'ustar/regtype') self.assertEqual(fobj.name, 'ustar/regtype')
self.assertRaises(AttributeError, fobj.fileno) self.assertRaises(AttributeError, fobj.fileno)
self.assertEqual(fobj.mode, 'rb')
self.assertIs(fobj.readable(), True) self.assertIs(fobj.readable(), True)
self.assertIs(fobj.writable(), False) self.assertIs(fobj.writable(), False)
if self.is_stream: if self.is_stream:
@ -533,11 +535,8 @@ class CommonReadTest(ReadTest):
class MiscReadTestBase(CommonReadTest): class MiscReadTestBase(CommonReadTest):
is_stream = False is_stream = False
def requires_name_attribute(self):
pass
def test_no_name_argument(self): def test_no_name_argument(self):
self.requires_name_attribute()
with open(self.tarname, "rb") as fobj: with open(self.tarname, "rb") as fobj:
self.assertIsInstance(fobj.name, str) self.assertIsInstance(fobj.name, str)
with tarfile.open(fileobj=fobj, mode=self.mode) as tar: with tarfile.open(fileobj=fobj, mode=self.mode) as tar:
@ -570,7 +569,6 @@ class MiscReadTestBase(CommonReadTest):
self.assertIsNone(tar.name) self.assertIsNone(tar.name)
def test_bytes_name_attribute(self): def test_bytes_name_attribute(self):
self.requires_name_attribute()
tarname = os.fsencode(self.tarname) tarname = os.fsencode(self.tarname)
with open(tarname, 'rb') as fobj: with open(tarname, 'rb') as fobj:
self.assertIsInstance(fobj.name, bytes) self.assertIsInstance(fobj.name, bytes)
@ -839,12 +837,10 @@ class GzipMiscReadTest(GzipTest, MiscReadTestBase, unittest.TestCase):
pass pass
class Bz2MiscReadTest(Bz2Test, MiscReadTestBase, unittest.TestCase): class Bz2MiscReadTest(Bz2Test, MiscReadTestBase, unittest.TestCase):
def requires_name_attribute(self): pass
self.skipTest("BZ2File have no name attribute")
class LzmaMiscReadTest(LzmaTest, MiscReadTestBase, unittest.TestCase): class LzmaMiscReadTest(LzmaTest, MiscReadTestBase, unittest.TestCase):
def requires_name_attribute(self): pass
self.skipTest("LZMAFile have no name attribute")
class StreamReadTest(CommonReadTest, unittest.TestCase): class StreamReadTest(CommonReadTest, unittest.TestCase):

View File

@ -389,7 +389,6 @@ class AbstractTestsWithSourceFile:
with zipfp.open(fname) as zipopen: with zipfp.open(fname) as zipopen:
r = repr(zipopen) r = repr(zipopen)
self.assertIn('name=%r' % fname, r) self.assertIn('name=%r' % fname, r)
self.assertIn("mode='r'", r)
if self.compression != zipfile.ZIP_STORED: if self.compression != zipfile.ZIP_STORED:
self.assertIn('compress_type=', r) self.assertIn('compress_type=', r)
self.assertIn('[closed]', repr(zipopen)) self.assertIn('[closed]', repr(zipopen))
@ -455,14 +454,14 @@ class AbstractTestsWithSourceFile:
with zipfp.open(fname) as fid: with zipfp.open(fname) as fid:
self.assertEqual(fid.name, fname) self.assertEqual(fid.name, fname)
self.assertRaises(io.UnsupportedOperation, fid.fileno) self.assertRaises(io.UnsupportedOperation, fid.fileno)
self.assertEqual(fid.mode, 'r') self.assertEqual(fid.mode, 'rb')
self.assertIs(fid.readable(), True) self.assertIs(fid.readable(), True)
self.assertIs(fid.writable(), False) self.assertIs(fid.writable(), False)
self.assertIs(fid.seekable(), True) self.assertIs(fid.seekable(), True)
self.assertIs(fid.closed, False) self.assertIs(fid.closed, False)
self.assertIs(fid.closed, True) self.assertIs(fid.closed, True)
self.assertEqual(fid.name, fname) self.assertEqual(fid.name, fname)
self.assertEqual(fid.mode, 'r') self.assertEqual(fid.mode, 'rb')
self.assertRaises(io.UnsupportedOperation, fid.fileno) self.assertRaises(io.UnsupportedOperation, fid.fileno)
self.assertRaises(ValueError, fid.readable) self.assertRaises(ValueError, fid.readable)
self.assertIs(fid.writable(), False) self.assertIs(fid.writable(), False)
@ -1308,12 +1307,16 @@ class AbstractWriterTests:
fname = "somefile.txt" fname = "somefile.txt"
with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp:
with zipfp.open(fname, 'w') as fid: with zipfp.open(fname, 'w') as fid:
self.assertEqual(fid.name, fname)
self.assertRaises(io.UnsupportedOperation, fid.fileno) self.assertRaises(io.UnsupportedOperation, fid.fileno)
self.assertEqual(fid.mode, 'wb')
self.assertIs(fid.readable(), False) self.assertIs(fid.readable(), False)
self.assertIs(fid.writable(), True) self.assertIs(fid.writable(), True)
self.assertIs(fid.seekable(), False) self.assertIs(fid.seekable(), False)
self.assertIs(fid.closed, False) self.assertIs(fid.closed, False)
self.assertIs(fid.closed, True) self.assertIs(fid.closed, True)
self.assertEqual(fid.name, fname)
self.assertEqual(fid.mode, 'wb')
self.assertRaises(io.UnsupportedOperation, fid.fileno) self.assertRaises(io.UnsupportedOperation, fid.fileno)
self.assertIs(fid.readable(), False) self.assertIs(fid.readable(), False)
self.assertIs(fid.writable(), True) self.assertIs(fid.writable(), True)

View File

@ -940,7 +940,7 @@ class ZipExtFile(io.BufferedIOBase):
result = ['<%s.%s' % (self.__class__.__module__, result = ['<%s.%s' % (self.__class__.__module__,
self.__class__.__qualname__)] self.__class__.__qualname__)]
if not self.closed: if not self.closed:
result.append(' name=%r mode=%r' % (self.name, self.mode)) result.append(' name=%r' % (self.name,))
if self._compress_type != ZIP_STORED: if self._compress_type != ZIP_STORED:
result.append(' compress_type=%s' % result.append(' compress_type=%s' %
compressor_names.get(self._compress_type, compressor_names.get(self._compress_type,
@ -1217,6 +1217,14 @@ class _ZipWriteFile(io.BufferedIOBase):
def _fileobj(self): def _fileobj(self):
return self._zipfile.fp return self._zipfile.fp
@property
def name(self):
return self._zinfo.filename
@property
def mode(self):
return 'wb'
def writable(self): def writable(self):
return True return True
@ -1687,7 +1695,7 @@ class ZipFile:
else: else:
pwd = None pwd = None
return ZipExtFile(zef_file, mode, zinfo, pwd, True) return ZipExtFile(zef_file, mode + 'b', zinfo, pwd, True)
except: except:
zef_file.close() zef_file.close()
raise raise

View File

@ -0,0 +1,7 @@
Added :attr:`!name` and :attr:`!mode` attributes for compressed and archived
file-like objects in modules :mod:`bz2`, :mod:`lzma`, :mod:`tarfile` and
:mod:`zipfile`. The value of the :attr:`!mode` attribute of
:class:`gzip.GzipFile` was changed from integer (``1`` or ``2``) to string
(``'rb'`` or ``'wb'``). The value of the :attr:`!mode` attribute of the
readable file-like object returned by :meth:`zipfile.ZipFile.open` was
changed from ``'r'`` to ``'rb'``.