https://github.com/python/cpython/commit/51ef89cd9acbfbfa7fd8d82f0c6baa388bb650c9
commit: 51ef89cd9acbfbfa7fd8d82f0c6baa388bb650c9
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-04-21T11:46:39+03:00
summary:

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'.

files:
A Misc/NEWS.d/next/Library/2024-02-28-10-41-24.gh-issue-115961.P-_DU0.rst
M Doc/library/bz2.rst
M Doc/library/gzip.rst
M Doc/library/lzma.rst
M Doc/library/tarfile.rst
M Doc/library/zipfile.rst
M Doc/whatsnew/3.13.rst
M Lib/bz2.py
M Lib/gzip.py
M Lib/lzma.py
M Lib/tarfile.py
M Lib/test/test_bz2.py
M Lib/test/test_gzip.py
M Lib/test/test_lzma.py
M Lib/test/test_tarfile.py
M Lib/test/test_zipfile/test_core.py
M Lib/zipfile/__init__.py

diff --git a/Doc/library/bz2.rst b/Doc/library/bz2.rst
index 5e0aa3e493f224..eaf0a096455fad 100644
--- a/Doc/library/bz2.rst
+++ b/Doc/library/bz2.rst
@@ -91,7 +91,7 @@ The :mod:`bz2` module contains:
    and :meth:`~io.IOBase.truncate`.
    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])
 
@@ -148,6 +148,19 @@ The :mod:`bz2` module contains:
 
       .. 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
       Support for the :keyword:`with` statement was added.
diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst
index 044be8c1c1bf41..e6106e72a83d25 100644
--- a/Doc/library/gzip.rst
+++ b/Doc/library/gzip.rst
@@ -133,6 +133,13 @@ The module defines the following items:
 
       .. 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
 
       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
       Accepts a :term:`path-like object`.
 
-   .. versionchanged:: 3.12
-      Remove the ``filename`` attribute, use the :attr:`~GzipFile.name`
-      attribute instead.
-
    .. deprecated:: 3.9
       Opening :class:`GzipFile` for writing without specifying the *mode*
       argument is deprecated.
 
+   .. versionchanged:: 3.12
+      Remove the ``filename`` attribute, use the :attr:`~GzipFile.name`
+      attribute instead.
+
 
 .. function:: compress(data, compresslevel=9, *, mtime=None)
 
diff --git a/Doc/library/lzma.rst b/Doc/library/lzma.rst
index 0d69c3bc01d1e2..74bf2670f9def6 100644
--- a/Doc/library/lzma.rst
+++ b/Doc/library/lzma.rst
@@ -104,7 +104,7 @@ Reading and writing compressed files
    and :meth:`~io.IOBase.truncate`.
    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)
 
@@ -117,6 +117,20 @@ Reading and writing compressed files
          file object (e.g. if the :class:`LZMAFile` was constructed by passing 
a
          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
       Added support for the ``"x"`` and ``"xb"`` modes.
 
diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
index afcad9bf722934..cf21dee83e6e7a 100644
--- a/Doc/library/tarfile.rst
+++ b/Doc/library/tarfile.rst
@@ -565,6 +565,10 @@ be finalized; only the internally used file object will be 
closed. See the
    .. versionchanged:: 3.3
       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
    :type: int
 
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index ee53f162ac9080..6192689c3a1194 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -301,6 +301,10 @@ ZipFile Objects
    attempting to read or write other files in the ZIP file will raise a
    :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
    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,
@@ -325,6 +329,12 @@ ZipFile Objects
       Calling :meth:`.open` on a closed ZipFile will raise a :exc:`ValueError`.
       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)
 
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index 0f0fca5d23597b..5be562030b507b 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -206,6 +206,11 @@ Other Language Changes
 
   (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`)
   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
   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
   to :c:func:`PyUnstable_Code_GetFirstFree`.
   (Contributed by Bogdan Romanyuk in :gh:`115781`.)
diff --git a/Lib/bz2.py b/Lib/bz2.py
index fabe4f73c8d808..2420cd019069b4 100644
--- a/Lib/bz2.py
+++ b/Lib/bz2.py
@@ -17,7 +17,7 @@
 from _bz2 import BZ2Compressor, BZ2Decompressor
 
 
-_MODE_CLOSED   = 0
+# Value 0 no longer used
 _MODE_READ     = 1
 # Value 2 no longer used
 _MODE_WRITE    = 3
@@ -54,7 +54,7 @@ def __init__(self, filename, mode="r", *, compresslevel=9):
         """
         self._fp = None
         self._closefp = False
-        self._mode = _MODE_CLOSED
+        self._mode = None
 
         if not (1 <= compresslevel <= 9):
             raise ValueError("compresslevel must be between 1 and 9")
@@ -100,7 +100,7 @@ def close(self):
         May be called more than once without error. Once the file is
         closed, any other operation on it will raise a ValueError.
         """
-        if self._mode == _MODE_CLOSED:
+        if self.closed:
             return
         try:
             if self._mode == _MODE_READ:
@@ -115,13 +115,21 @@ def close(self):
             finally:
                 self._fp = None
                 self._closefp = False
-                self._mode = _MODE_CLOSED
                 self._buffer = None
 
     @property
     def closed(self):
         """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):
         """Return the file descriptor for the underlying file."""
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 1d6faaa82c6a68..0d19c84c59cfa7 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -15,7 +15,8 @@
 
 FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
 
-READ, WRITE = 1, 2
+READ = 'rb'
+WRITE = 'wb'
 
 _COMPRESS_LEVEL_FAST = 1
 _COMPRESS_LEVEL_TRADEOFF = 6
diff --git a/Lib/lzma.py b/Lib/lzma.py
index 800f52198fbb79..c1e3d33deb69a1 100644
--- a/Lib/lzma.py
+++ b/Lib/lzma.py
@@ -29,7 +29,7 @@
 import _compression
 
 
-_MODE_CLOSED   = 0
+# Value 0 no longer used
 _MODE_READ     = 1
 # Value 2 no longer used
 _MODE_WRITE    = 3
@@ -92,7 +92,7 @@ def __init__(self, filename=None, mode="r", *,
         """
         self._fp = None
         self._closefp = False
-        self._mode = _MODE_CLOSED
+        self._mode = None
 
         if mode in ("r", "rb"):
             if check != -1:
@@ -137,7 +137,7 @@ def close(self):
         May be called more than once without error. Once the file is
         closed, any other operation on it will raise a ValueError.
         """
-        if self._mode == _MODE_CLOSED:
+        if self.closed:
             return
         try:
             if self._mode == _MODE_READ:
@@ -153,12 +153,20 @@ def close(self):
             finally:
                 self._fp = None
                 self._closefp = False
-                self._mode = _MODE_CLOSED
 
     @property
     def closed(self):
         """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):
         """Return the file descriptor for the underlying file."""
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 78bb10c80820d7..5fc6183ffcf93c 100755
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -636,6 +636,10 @@ def __init__(self, fileobj, offset, size, name, 
blockinfo=None):
     def flush(self):
         pass
 
+    @property
+    def mode(self):
+        return 'rb'
+
     def readable(self):
         return True
 
diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py
index 772f0eacce28f5..e4d1381be5f340 100644
--- a/Lib/test/test_bz2.py
+++ b/Lib/test/test_bz2.py
@@ -540,40 +540,54 @@ def testMultiStreamOrdering(self):
     def testOpenFilename(self):
         with BZ2File(self.filename, "wb") as f:
             f.write(b'content')
+            self.assertEqual(f.name, self.filename)
             self.assertIsInstance(f.fileno(), int)
+            self.assertEqual(f.mode, 'wb')
             self.assertIs(f.readable(), False)
             self.assertIs(f.writable(), True)
             self.assertIs(f.seekable(), False)
             self.assertIs(f.closed, False)
         self.assertIs(f.closed, True)
+        with self.assertRaises(ValueError):
+            f.name
         self.assertRaises(ValueError, f.fileno)
+        self.assertEqual(f.mode, 'wb')
         self.assertRaises(ValueError, f.readable)
         self.assertRaises(ValueError, f.writable)
         self.assertRaises(ValueError, f.seekable)
 
         with BZ2File(self.filename, "ab") as f:
             f.write(b'appendix')
+            self.assertEqual(f.name, self.filename)
             self.assertIsInstance(f.fileno(), int)
+            self.assertEqual(f.mode, 'wb')
             self.assertIs(f.readable(), False)
             self.assertIs(f.writable(), True)
             self.assertIs(f.seekable(), False)
             self.assertIs(f.closed, False)
         self.assertIs(f.closed, True)
+        with self.assertRaises(ValueError):
+            f.name
         self.assertRaises(ValueError, f.fileno)
+        self.assertEqual(f.mode, 'wb')
         self.assertRaises(ValueError, f.readable)
         self.assertRaises(ValueError, f.writable)
         self.assertRaises(ValueError, f.seekable)
 
         with BZ2File(self.filename, 'rb') as f:
             self.assertEqual(f.read(), b'contentappendix')
+            self.assertEqual(f.name, self.filename)
             self.assertIsInstance(f.fileno(), int)
+            self.assertEqual(f.mode, 'rb')
             self.assertIs(f.readable(), True)
             self.assertIs(f.writable(), False)
             self.assertIs(f.seekable(), True)
             self.assertIs(f.closed, False)
         self.assertIs(f.closed, True)
         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.writable)
         self.assertRaises(ValueError, f.seekable)
@@ -582,13 +596,18 @@ def testOpenFileWithName(self):
         with open(self.filename, 'wb') as raw:
             with BZ2File(raw, 'wb') as f:
                 f.write(b'content')
+                self.assertEqual(f.name, raw.name)
                 self.assertEqual(f.fileno(), raw.fileno())
+                self.assertEqual(f.mode, 'wb')
                 self.assertIs(f.readable(), False)
                 self.assertIs(f.writable(), True)
                 self.assertIs(f.seekable(), False)
                 self.assertIs(f.closed, False)
             self.assertIs(f.closed, True)
+            with self.assertRaises(ValueError):
+                f.name
             self.assertRaises(ValueError, f.fileno)
+            self.assertEqual(f.mode, 'wb')
             self.assertRaises(ValueError, f.readable)
             self.assertRaises(ValueError, f.writable)
             self.assertRaises(ValueError, f.seekable)
@@ -596,13 +615,18 @@ def testOpenFileWithName(self):
         with open(self.filename, 'ab') as raw:
             with BZ2File(raw, 'ab') as f:
                 f.write(b'appendix')
+                self.assertEqual(f.name, raw.name)
                 self.assertEqual(f.fileno(), raw.fileno())
+                self.assertEqual(f.mode, 'wb')
                 self.assertIs(f.readable(), False)
                 self.assertIs(f.writable(), True)
                 self.assertIs(f.seekable(), False)
                 self.assertIs(f.closed, False)
             self.assertIs(f.closed, True)
+            with self.assertRaises(ValueError):
+                f.name
             self.assertRaises(ValueError, f.fileno)
+            self.assertEqual(f.mode, 'wb')
             self.assertRaises(ValueError, f.readable)
             self.assertRaises(ValueError, f.writable)
             self.assertRaises(ValueError, f.seekable)
@@ -610,14 +634,18 @@ def testOpenFileWithName(self):
         with open(self.filename, 'rb') as raw:
             with BZ2File(raw, 'rb') as f:
                 self.assertEqual(f.read(), b'contentappendix')
+                self.assertEqual(f.name, raw.name)
                 self.assertEqual(f.fileno(), raw.fileno())
+                self.assertEqual(f.mode, 'rb')
                 self.assertIs(f.readable(), True)
                 self.assertIs(f.writable(), False)
                 self.assertIs(f.seekable(), True)
                 self.assertIs(f.closed, False)
             self.assertIs(f.closed, True)
             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.writable)
             self.assertRaises(ValueError, f.seekable)
@@ -626,61 +654,91 @@ def testOpenFileWithoutName(self):
         bio = BytesIO()
         with BZ2File(bio, 'wb') as f:
             f.write(b'content')
+            with self.assertRaises(AttributeError):
+                f.name
             self.assertRaises(io.UnsupportedOperation, f.fileno)
+            self.assertEqual(f.mode, 'wb')
+        with self.assertRaises(ValueError):
+            f.name
         self.assertRaises(ValueError, f.fileno)
 
         with BZ2File(bio, 'ab') as f:
             f.write(b'appendix')
+            with self.assertRaises(AttributeError):
+                f.name
             self.assertRaises(io.UnsupportedOperation, f.fileno)
+            self.assertEqual(f.mode, 'wb')
+        with self.assertRaises(ValueError):
+            f.name
         self.assertRaises(ValueError, f.fileno)
 
         bio.seek(0)
         with BZ2File(bio, 'rb') as f:
             self.assertEqual(f.read(), b'contentappendix')
+            with self.assertRaises(AttributeError):
+                f.name
             self.assertRaises(io.UnsupportedOperation, f.fileno)
+            self.assertEqual(f.mode, 'rb')
         with self.assertRaises(ValueError):
-            f.fileno()
+            f.name
+        self.assertRaises(ValueError, f.fileno)
 
     def testOpenFileWithIntName(self):
         fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
         with open(fd, 'wb') as raw:
             with BZ2File(raw, 'wb') as f:
                 f.write(b'content')
+                self.assertEqual(f.name, raw.name)
                 self.assertEqual(f.fileno(), raw.fileno())
+                self.assertEqual(f.mode, 'wb')
+            with self.assertRaises(ValueError):
+                f.name
             self.assertRaises(ValueError, f.fileno)
 
         fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND)
         with open(fd, 'ab') as raw:
             with BZ2File(raw, 'ab') as f:
                 f.write(b'appendix')
+                self.assertEqual(f.name, raw.name)
                 self.assertEqual(f.fileno(), raw.fileno())
+                self.assertEqual(f.mode, 'wb')
+            with self.assertRaises(ValueError):
+                f.name
             self.assertRaises(ValueError, f.fileno)
 
         fd = os.open(self.filename, os.O_RDONLY)
         with open(fd, 'rb') as raw:
             with BZ2File(raw, 'rb') as f:
                 self.assertEqual(f.read(), b'contentappendix')
+                self.assertEqual(f.name, raw.name)
                 self.assertEqual(f.fileno(), raw.fileno())
+                self.assertEqual(f.mode, 'rb')
             with self.assertRaises(ValueError):
-                f.fileno()
+                f.name
+            self.assertRaises(ValueError, f.fileno)
 
     def testOpenBytesFilename(self):
         str_filename = self.filename
         bytes_filename = os.fsencode(str_filename)
         with BZ2File(bytes_filename, "wb") as f:
             f.write(self.DATA)
+            self.assertEqual(f.name, bytes_filename)
         with BZ2File(bytes_filename, "rb") as f:
             self.assertEqual(f.read(), self.DATA)
+            self.assertEqual(f.name, bytes_filename)
         # Sanity check that we are actually operating on the right file.
         with BZ2File(str_filename, "rb") as f:
             self.assertEqual(f.read(), self.DATA)
+            self.assertEqual(f.name, str_filename)
 
     def testOpenPathLikeFilename(self):
         filename = FakePath(self.filename)
         with BZ2File(filename, "wb") as f:
             f.write(self.DATA)
+            self.assertEqual(f.name, self.filename)
         with BZ2File(filename, "rb") as f:
             self.assertEqual(f.read(), self.DATA)
+            self.assertEqual(f.name, self.filename)
 
     def testDecompressLimited(self):
         """Decompressed data buffering should be limited"""
@@ -701,6 +759,9 @@ def testReadBytesIO(self):
             with BZ2File(bio) as bz2f:
                 self.assertRaises(TypeError, bz2f.read, float())
                 self.assertEqual(bz2f.read(), self.TEXT)
+                with self.assertRaises(AttributeError):
+                    bz2.name
+                self.assertEqual(bz2f.mode, 'rb')
             self.assertFalse(bio.closed)
 
     def testPeekBytesIO(self):
@@ -716,6 +777,9 @@ def testWriteBytesIO(self):
             with BZ2File(bio, "w") as bz2f:
                 self.assertRaises(TypeError, bz2f.write)
                 bz2f.write(self.TEXT)
+                with self.assertRaises(AttributeError):
+                    bz2.name
+                self.assertEqual(bz2f.mode, 'wb')
             self.assertEqual(ext_decompress(bio.getvalue()), self.TEXT)
             self.assertFalse(bio.closed)
 
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index d220c7d06e50c9..cf801278da9e9b 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -587,6 +587,8 @@ def test_fileobj_from_fdopen(self):
             self.assertRaises(AttributeError, f.fileno)
 
     def test_fileobj_mode(self):
+        self.assertEqual(gzip.READ, 'rb')
+        self.assertEqual(gzip.WRITE, 'wb')
         gzip.GzipFile(self.filename, "wb").close()
         with open(self.filename, "r+b") as f:
             with gzip.GzipFile(fileobj=f, mode='r') as g:
diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py
index db290e139327e0..22478c14fb4a65 100644
--- a/Lib/test/test_lzma.py
+++ b/Lib/test/test_lzma.py
@@ -551,19 +551,25 @@ def test_init_with_PathLike_filename(self):
         with TempFile(filename, COMPRESSED_XZ):
             with LZMAFile(filename) as f:
                 self.assertEqual(f.read(), INPUT)
+                self.assertEqual(f.name, TESTFN)
             with LZMAFile(filename, "a") as f:
                 f.write(INPUT)
+                self.assertEqual(f.name, TESTFN)
             with LZMAFile(filename) as f:
                 self.assertEqual(f.read(), INPUT * 2)
+                self.assertEqual(f.name, TESTFN)
 
     def test_init_with_filename(self):
         with TempFile(TESTFN, COMPRESSED_XZ):
             with LZMAFile(TESTFN) as f:
-                pass
+                self.assertEqual(f.name, TESTFN)
+                self.assertEqual(f.mode, 'rb')
             with LZMAFile(TESTFN, "w") as f:
-                pass
+                self.assertEqual(f.name, TESTFN)
+                self.assertEqual(f.mode, 'wb')
             with LZMAFile(TESTFN, "a") as f:
-                pass
+                self.assertEqual(f.name, TESTFN)
+                self.assertEqual(f.mode, 'wb')
 
     def test_init_mode(self):
         with TempFile(TESTFN):
@@ -586,6 +592,7 @@ def test_init_with_x_mode(self):
             unlink(TESTFN)
             with LZMAFile(TESTFN, mode) as f:
                 pass
+            self.assertEqual(f.mode, 'wb')
             with self.assertRaises(FileExistsError):
                 LZMAFile(TESTFN, mode)
 
@@ -865,13 +872,18 @@ def test_read_from_file(self):
             with LZMAFile(TESTFN) as f:
                 self.assertEqual(f.read(), INPUT)
                 self.assertEqual(f.read(), b"")
+                self.assertEqual(f.name, TESTFN)
                 self.assertIsInstance(f.fileno(), int)
+                self.assertEqual(f.mode, 'rb')
                 self.assertIs(f.readable(), True)
                 self.assertIs(f.writable(), False)
                 self.assertIs(f.seekable(), True)
                 self.assertIs(f.closed, False)
             self.assertIs(f.closed, True)
+            with self.assertRaises(ValueError):
+                f.name
             self.assertRaises(ValueError, f.fileno)
+            self.assertEqual(f.mode, 'rb')
             self.assertRaises(ValueError, f.readable)
             self.assertRaises(ValueError, f.writable)
             self.assertRaises(ValueError, f.seekable)
@@ -882,6 +894,7 @@ def test_read_from_file_with_bytes_filename(self):
             with LZMAFile(bytes_filename) as f:
                 self.assertEqual(f.read(), INPUT)
                 self.assertEqual(f.read(), b"")
+                self.assertEqual(f.name, bytes_filename)
 
     def test_read_from_fileobj(self):
         with TempFile(TESTFN, COMPRESSED_XZ):
@@ -889,13 +902,18 @@ def test_read_from_fileobj(self):
                 with LZMAFile(raw) as f:
                     self.assertEqual(f.read(), INPUT)
                     self.assertEqual(f.read(), b"")
+                    self.assertEqual(f.name, raw.name)
                     self.assertEqual(f.fileno(), raw.fileno())
+                    self.assertEqual(f.mode, 'rb')
                     self.assertIs(f.readable(), True)
                     self.assertIs(f.writable(), False)
                     self.assertIs(f.seekable(), True)
                     self.assertIs(f.closed, False)
                 self.assertIs(f.closed, True)
+                with self.assertRaises(ValueError):
+                    f.name
                 self.assertRaises(ValueError, f.fileno)
+                self.assertEqual(f.mode, 'rb')
                 self.assertRaises(ValueError, f.readable)
                 self.assertRaises(ValueError, f.writable)
                 self.assertRaises(ValueError, f.seekable)
@@ -907,13 +925,18 @@ def test_read_from_fileobj_with_int_name(self):
                 with LZMAFile(raw) as f:
                     self.assertEqual(f.read(), INPUT)
                     self.assertEqual(f.read(), b"")
+                    self.assertEqual(f.name, raw.name)
                     self.assertEqual(f.fileno(), raw.fileno())
+                    self.assertEqual(f.mode, 'rb')
                     self.assertIs(f.readable(), True)
                     self.assertIs(f.writable(), False)
                     self.assertIs(f.seekable(), True)
                     self.assertIs(f.closed, False)
                 self.assertIs(f.closed, True)
+                with self.assertRaises(ValueError):
+                    f.name
                 self.assertRaises(ValueError, f.fileno)
+                self.assertEqual(f.mode, 'rb')
                 self.assertRaises(ValueError, f.readable)
                 self.assertRaises(ValueError, f.writable)
                 self.assertRaises(ValueError, f.seekable)
@@ -1045,6 +1068,8 @@ def test_write(self):
         with BytesIO() as dst:
             with LZMAFile(dst, "w") as f:
                 f.write(INPUT)
+                with self.assertRaises(AttributeError):
+                    f.name
             expected = lzma.compress(INPUT)
             self.assertEqual(dst.getvalue(), expected)
         with BytesIO() as dst:
@@ -1081,23 +1106,31 @@ def test_write_append(self):
         with BytesIO() as dst:
             with LZMAFile(dst, "w") as f:
                 f.write(part1)
+            self.assertEqual(f.mode, 'wb')
             with LZMAFile(dst, "a") as f:
                 f.write(part2)
+            self.assertEqual(f.mode, 'wb')
             with LZMAFile(dst, "a") as f:
                 f.write(part3)
+            self.assertEqual(f.mode, 'wb')
             self.assertEqual(dst.getvalue(), expected)
 
     def test_write_to_file(self):
         try:
             with LZMAFile(TESTFN, "w") as f:
                 f.write(INPUT)
+                self.assertEqual(f.name, TESTFN)
                 self.assertIsInstance(f.fileno(), int)
+                self.assertEqual(f.mode, 'wb')
                 self.assertIs(f.readable(), False)
                 self.assertIs(f.writable(), True)
                 self.assertIs(f.seekable(), False)
                 self.assertIs(f.closed, False)
             self.assertIs(f.closed, True)
+            with self.assertRaises(ValueError):
+                f.name
             self.assertRaises(ValueError, f.fileno)
+            self.assertEqual(f.mode, 'wb')
             self.assertRaises(ValueError, f.readable)
             self.assertRaises(ValueError, f.writable)
             self.assertRaises(ValueError, f.seekable)
@@ -1113,6 +1146,7 @@ def test_write_to_file_with_bytes_filename(self):
         try:
             with LZMAFile(bytes_filename, "w") as f:
                 f.write(INPUT)
+                self.assertEqual(f.name, bytes_filename)
             expected = lzma.compress(INPUT)
             with open(TESTFN, "rb") as f:
                 self.assertEqual(f.read(), expected)
@@ -1124,13 +1158,18 @@ def test_write_to_fileobj(self):
             with open(TESTFN, "wb") as raw:
                 with LZMAFile(raw, "w") as f:
                     f.write(INPUT)
+                    self.assertEqual(f.name, raw.name)
                     self.assertEqual(f.fileno(), raw.fileno())
+                    self.assertEqual(f.mode, 'wb')
                     self.assertIs(f.readable(), False)
                     self.assertIs(f.writable(), True)
                     self.assertIs(f.seekable(), False)
                     self.assertIs(f.closed, False)
                 self.assertIs(f.closed, True)
+                with self.assertRaises(ValueError):
+                    f.name
                 self.assertRaises(ValueError, f.fileno)
+                self.assertEqual(f.mode, 'wb')
                 self.assertRaises(ValueError, f.readable)
                 self.assertRaises(ValueError, f.writable)
                 self.assertRaises(ValueError, f.seekable)
@@ -1147,13 +1186,18 @@ def test_write_to_fileobj_with_int_name(self):
             with open(fd, 'wb') as raw:
                 with LZMAFile(raw, "w") as f:
                     f.write(INPUT)
+                    self.assertEqual(f.name, raw.name)
                     self.assertEqual(f.fileno(), raw.fileno())
+                    self.assertEqual(f.mode, 'wb')
                     self.assertIs(f.readable(), False)
                     self.assertIs(f.writable(), True)
                     self.assertIs(f.seekable(), False)
                     self.assertIs(f.closed, False)
                 self.assertIs(f.closed, True)
+                with self.assertRaises(ValueError):
+                    f.name
                 self.assertRaises(ValueError, f.fileno)
+                self.assertEqual(f.mode, 'wb')
                 self.assertRaises(ValueError, f.readable)
                 self.assertRaises(ValueError, f.writable)
                 self.assertRaises(ValueError, f.seekable)
@@ -1172,10 +1216,13 @@ def test_write_append_to_file(self):
         try:
             with LZMAFile(TESTFN, "w") as f:
                 f.write(part1)
+            self.assertEqual(f.mode, 'wb')
             with LZMAFile(TESTFN, "a") as f:
                 f.write(part2)
+            self.assertEqual(f.mode, 'wb')
             with LZMAFile(TESTFN, "a") as f:
                 f.write(part3)
+            self.assertEqual(f.mode, 'wb')
             with open(TESTFN, "rb") as f:
                 self.assertEqual(f.read(), expected)
         finally:
@@ -1373,11 +1420,13 @@ def test_with_pathlike_filename(self):
         with TempFile(filename):
             with lzma.open(filename, "wb") as f:
                 f.write(INPUT)
+                self.assertEqual(f.name, TESTFN)
             with open(filename, "rb") as f:
                 file_data = lzma.decompress(f.read())
                 self.assertEqual(file_data, INPUT)
             with lzma.open(filename, "rb") as f:
                 self.assertEqual(f.read(), INPUT)
+                self.assertEqual(f.name, TESTFN)
 
     def test_bad_params(self):
         # Test invalid parameter combinations.
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index c2abc7ef9698fb..c9c1097963a885 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -513,6 +513,7 @@ def test_extractfile_attrs(self):
         with self.tar.extractfile(file) as fobj:
             self.assertEqual(fobj.name, 'ustar/regtype')
             self.assertRaises(AttributeError, fobj.fileno)
+            self.assertEqual(fobj.mode, 'rb')
             self.assertIs(fobj.readable(), True)
             self.assertIs(fobj.writable(), False)
             if self.is_stream:
@@ -523,6 +524,7 @@ def test_extractfile_attrs(self):
         self.assertIs(fobj.closed, True)
         self.assertEqual(fobj.name, 'ustar/regtype')
         self.assertRaises(AttributeError, fobj.fileno)
+        self.assertEqual(fobj.mode, 'rb')
         self.assertIs(fobj.readable(), True)
         self.assertIs(fobj.writable(), False)
         if self.is_stream:
@@ -533,11 +535,8 @@ def test_extractfile_attrs(self):
 
 class MiscReadTestBase(CommonReadTest):
     is_stream = False
-    def requires_name_attribute(self):
-        pass
 
     def test_no_name_argument(self):
-        self.requires_name_attribute()
         with open(self.tarname, "rb") as fobj:
             self.assertIsInstance(fobj.name, str)
             with tarfile.open(fileobj=fobj, mode=self.mode) as tar:
@@ -570,7 +569,6 @@ def test_int_name_attribute(self):
                 self.assertIsNone(tar.name)
 
     def test_bytes_name_attribute(self):
-        self.requires_name_attribute()
         tarname = os.fsencode(self.tarname)
         with open(tarname, 'rb') as fobj:
             self.assertIsInstance(fobj.name, bytes)
@@ -839,12 +837,10 @@ class GzipMiscReadTest(GzipTest, MiscReadTestBase, 
unittest.TestCase):
     pass
 
 class Bz2MiscReadTest(Bz2Test, MiscReadTestBase, unittest.TestCase):
-    def requires_name_attribute(self):
-        self.skipTest("BZ2File have no name attribute")
+    pass
 
 class LzmaMiscReadTest(LzmaTest, MiscReadTestBase, unittest.TestCase):
-    def requires_name_attribute(self):
-        self.skipTest("LZMAFile have no name attribute")
+    pass
 
 
 class StreamReadTest(CommonReadTest, unittest.TestCase):
diff --git a/Lib/test/test_zipfile/test_core.py 
b/Lib/test/test_zipfile/test_core.py
index a605aa1f14fe3f..423974aada4ac1 100644
--- a/Lib/test/test_zipfile/test_core.py
+++ b/Lib/test/test_zipfile/test_core.py
@@ -389,7 +389,6 @@ def test_repr(self):
                 with zipfp.open(fname) as zipopen:
                     r = repr(zipopen)
                     self.assertIn('name=%r' % fname, r)
-                    self.assertIn("mode='r'", r)
                     if self.compression != zipfile.ZIP_STORED:
                         self.assertIn('compress_type=', r)
                 self.assertIn('[closed]', repr(zipopen))
@@ -455,14 +454,14 @@ def test_zipextfile_attrs(self):
             with zipfp.open(fname) as fid:
                 self.assertEqual(fid.name, fname)
                 self.assertRaises(io.UnsupportedOperation, fid.fileno)
-                self.assertEqual(fid.mode, 'r')
+                self.assertEqual(fid.mode, 'rb')
                 self.assertIs(fid.readable(), True)
                 self.assertIs(fid.writable(), False)
                 self.assertIs(fid.seekable(), True)
                 self.assertIs(fid.closed, False)
             self.assertIs(fid.closed, True)
             self.assertEqual(fid.name, fname)
-            self.assertEqual(fid.mode, 'r')
+            self.assertEqual(fid.mode, 'rb')
             self.assertRaises(io.UnsupportedOperation, fid.fileno)
             self.assertRaises(ValueError, fid.readable)
             self.assertIs(fid.writable(), False)
@@ -1308,12 +1307,16 @@ def test_zipwritefile_attrs(self):
         fname = "somefile.txt"
         with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) 
as zipfp:
             with zipfp.open(fname, 'w') as fid:
+                self.assertEqual(fid.name, fname)
                 self.assertRaises(io.UnsupportedOperation, fid.fileno)
+                self.assertEqual(fid.mode, 'wb')
                 self.assertIs(fid.readable(), False)
                 self.assertIs(fid.writable(), True)
                 self.assertIs(fid.seekable(), False)
                 self.assertIs(fid.closed, False)
             self.assertIs(fid.closed, True)
+            self.assertEqual(fid.name, fname)
+            self.assertEqual(fid.mode, 'wb')
             self.assertRaises(io.UnsupportedOperation, fid.fileno)
             self.assertIs(fid.readable(), False)
             self.assertIs(fid.writable(), True)
diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py
index e4603b559f5962..31ef9bb1ad925e 100644
--- a/Lib/zipfile/__init__.py
+++ b/Lib/zipfile/__init__.py
@@ -940,7 +940,7 @@ def __repr__(self):
         result = ['<%s.%s' % (self.__class__.__module__,
                               self.__class__.__qualname__)]
         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:
                 result.append(' compress_type=%s' %
                               compressor_names.get(self._compress_type,
@@ -1217,6 +1217,14 @@ def __init__(self, zf, zinfo, zip64):
     def _fileobj(self):
         return self._zipfile.fp
 
+    @property
+    def name(self):
+        return self._zinfo.filename
+
+    @property
+    def mode(self):
+        return 'wb'
+
     def writable(self):
         return True
 
@@ -1687,7 +1695,7 @@ def open(self, name, mode="r", pwd=None, *, 
force_zip64=False):
             else:
                 pwd = None
 
-            return ZipExtFile(zef_file, mode, zinfo, pwd, True)
+            return ZipExtFile(zef_file, mode + 'b', zinfo, pwd, True)
         except:
             zef_file.close()
             raise
diff --git 
a/Misc/NEWS.d/next/Library/2024-02-28-10-41-24.gh-issue-115961.P-_DU0.rst 
b/Misc/NEWS.d/next/Library/2024-02-28-10-41-24.gh-issue-115961.P-_DU0.rst
new file mode 100644
index 00000000000000..eef7eb8687b44f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-02-28-10-41-24.gh-issue-115961.P-_DU0.rst
@@ -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'``.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to