https://github.com/python/cpython/commit/aa533dce22bf20ba3c1e954aefb245ca1b63543b
commit: aa533dce22bf20ba3c1e954aefb245ca1b63543b
branch: main
author: Aniket <[email protected]>
committer: sobolevn <[email protected]>
date: 2026-07-07T21:34:16Z
summary:

gh-152586: Make `tempfile.TemporaryFileWrapper` public (#152646)

Deprecate old compatibility `tempfile._TemporaryFileWrapper` name, schedule it 
to be removed in Python 3.21

Co-authored-by: sobolevn <[email protected]>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst
M Doc/deprecations/pending-removal-in-3.21.rst
M Doc/library/tempfile.rst
M Doc/whatsnew/3.16.rst
M Lib/tempfile.py
M Lib/test/test_tempfile.py
M Lib/test/test_urllib_response.py
M Lib/urllib/response.py

diff --git a/Doc/deprecations/pending-removal-in-3.21.rst 
b/Doc/deprecations/pending-removal-in-3.21.rst
index dbd73313b4f7779..c2546b7fc451aed 100644
--- a/Doc/deprecations/pending-removal-in-3.21.rst
+++ b/Doc/deprecations/pending-removal-in-3.21.rst
@@ -23,3 +23,8 @@ Pending removal in Python 3.21
   * Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes 
are now
     deprecated.  These codes will be removed in Python 3.21.  Use instead
     two-letter forms ``'Zf'`` and ``'Zd'``.
+
+* :mod:`tempfile`:
+
+  * ``tempfile._TemporaryFileWrapper`` will be removed in Python 3.21. Use the
+    public :class:`tempfile.TemporaryFileWrapper` instead.
diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst
index bf9198e175a0e11..8c387853d0417f4 100644
--- a/Doc/library/tempfile.rst
+++ b/Doc/library/tempfile.rst
@@ -14,10 +14,11 @@
 
 This module creates temporary files and directories.  It works on all
 supported platforms. :class:`TemporaryFile`, :class:`NamedTemporaryFile`,
-:class:`TemporaryDirectory`, and :class:`SpooledTemporaryFile` are high-level
-interfaces which provide automatic cleanup and can be used as
-:term:`context managers <context manager>`. :func:`mkstemp` and
-:func:`mkdtemp` are lower-level functions which require manual cleanup.
+:class:`TemporaryFileWrapper`, :class:`TemporaryDirectory`, and
+:class:`SpooledTemporaryFile` are high-level interfaces which provide
+automatic cleanup and can be used as :term:`context managers
+<context manager>`. :func:`mkstemp` and :func:`mkdtemp` are lower-level
+functions which require manual cleanup.
 
 All the user-callable functions and constructors take additional arguments 
which
 allow direct control over the location and name of temporary files and
@@ -84,13 +85,13 @@ The module defines the following user-callable items:
      :func:`TemporaryFile` with *delete* and *delete_on_close* parameters that
      determine whether and how the named file should be automatically deleted.
 
-   The returned object is always a :term:`file-like object` whose :attr:`!file`
-   attribute is the underlying true file object. This file-like object
-   can be used in a :keyword:`with` statement, just like a normal file.  The
-   name of the temporary file can be retrieved from the :attr:`!name` attribute
-   of the returned file-like object. On Unix, unlike with the
-   :func:`TemporaryFile`, the directory entry does not get unlinked immediately
-   after the file creation.
+   The returned object is always a :class:`TemporaryFileWrapper` instance
+   (a :term:`file-like object`) whose :attr:`~TemporaryFileWrapper.file` 
attribute is the underlying
+   true file object. This file-like object can be used in a :keyword:`with`
+   statement, just like a normal file.  The name of the temporary file can be
+   retrieved from the :attr:`!name` attribute of the returned file-like object.
+   On Unix, unlike with the :func:`TemporaryFile`, the directory entry does not
+   get unlinked immediately after the file creation.
 
    If *delete* is true (the default) and *delete_on_close* is true (the
    default), the file is deleted as soon as it is closed. If *delete* is true
@@ -140,6 +141,34 @@ The module defines the following user-callable items:
    .. versionchanged:: 3.12
       Added *delete_on_close* parameter.
 
+.. class:: TemporaryFileWrapper(file, name, delete=True, delete_on_close=True)
+
+   A mutable wrapper returned by :func:`NamedTemporaryFile`. It wraps the
+   underlying file object, delegating attribute access to it, and ensures
+   the temporary file is deleted when appropriate.
+
+   .. attribute:: file
+
+      The underlying :term:`file-like object`.
+
+   .. attribute:: name
+
+      The file name of the temporary file.
+
+   .. method:: close()
+
+      Close the temporary file, possibly deleting it depending on the
+      *delete* and *delete_on_close* arguments passed to
+      :func:`NamedTemporaryFile`.
+
+   .. note::
+
+      ``tempfile._TemporaryFileWrapper`` is kept as a backwards compatible
+      deprecated alias for this class.
+      It will be removed in Python 3.21
+
+   .. versionadded:: next
+
 
 .. class:: SpooledTemporaryFile(max_size=0, mode='w+b', buffering=-1, 
encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None)
 
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index dd2c7b68d039921..43ecdb41afc3a9c 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -588,6 +588,13 @@ New deprecations
     two-letter forms ``'Zf'`` and ``'Zd'``.
     (Contributed by Sergey B Kirpichev in :gh:`121249`.)
 
+* :mod:`tempfile`
+
+  * The private ``tempfile._TemporaryFileWrapper`` name is deprecated
+    and is slated for removal in Python 3.21.
+    Use the new public :class:`tempfile.TemporaryFileWrapper` instead,
+    which is the return type of :func:`tempfile.NamedTemporaryFile`.
+
 .. Add deprecations above alphabetically, not here at the end.
 
 .. include:: ../deprecations/pending-removal-in-3.17.rst
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index 6dac9ab3c417171..485038417c65c66 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -31,6 +31,7 @@
     "TMP_MAX", "gettempprefix",            # constants
     "tempdir", "gettempdir",
     "gettempprefixb", "gettempdirb",
+    "TemporaryFileWrapper",
    ]
 
 
@@ -484,7 +485,7 @@ def __del__(self):
             _warnings.warn(self.warn_message, ResourceWarning)
 
 
-class _TemporaryFileWrapper:
+class TemporaryFileWrapper:
     """Temporary file wrapper
 
     This class provides a wrapper around files opened for
@@ -555,6 +556,19 @@ def __iter__(self):
         for line in self.file:
             yield line
 
+def __getattr__(name):
+    if name == "_TemporaryFileWrapper":
+        _warnings._deprecated(
+            "tempfile._TemporaryFileWrapper",
+            message=(
+                "{name!r} is deprecated and slated for removal in Python 
{remove}. "
+                "Use tempfile.TemporaryFileWrapper instead."
+            ),
+            remove=(3, 21),
+        )
+        return TemporaryFileWrapper
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
 def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
                        newline=None, suffix=None, prefix=None,
                        dir=None, delete=True, *, errors=None,
@@ -607,7 +621,7 @@ def opener(*args):
             raw = getattr(file, 'buffer', file)
             raw = getattr(raw, 'raw', raw)
             raw.name = name
-            return _TemporaryFileWrapper(file, name, delete, delete_on_close)
+            return TemporaryFileWrapper(file, name, delete, delete_on_close)
         except:
             file.close()
             raise
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index 3b081ecd4a3aa50..e33cc65e090e3b4 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -148,6 +148,7 @@ def test_exports(self):
             "template" : 1,
             "SpooledTemporaryFile" : 1,
             "TemporaryDirectory" : 1,
+            "TemporaryFileWrapper" : 1,
         }
 
         unexp = []
@@ -980,12 +981,23 @@ def do_create(self, dir=None, pre="", suf="", 
delete=True):
 
     def test_basic(self):
         # NamedTemporaryFile can create files
-        self.do_create()
+        f = self.do_create()
+        self.assertIsInstance(f, tempfile.TemporaryFileWrapper)
         self.do_create(pre="a")
         self.do_create(suf="b")
         self.do_create(pre="a", suf="b")
         self.do_create(pre="aa", suf=".txt")
 
+    def test_in_all(self):
+        self.assertIn("TemporaryFileWrapper", tempfile.__all__)
+
+    def test_deprecated_TemporaryFileWrapper_alias(self):
+        # gh-152586: _TemporaryFileWrapper is a deprecated alias
+        # for the public TemporaryFileWrapper class.
+        with self.assertWarns(DeprecationWarning):
+            obj = tempfile._TemporaryFileWrapper
+        self.assertIs(obj, tempfile.TemporaryFileWrapper)
+
     def test_method_lookup(self):
         # Issue #18879: Looking up a temporary file method should keep it
         # alive long enough.
@@ -1141,7 +1153,7 @@ def my_func(dir):
         try:
             with self.assertWarnsRegex(
                 expected_warning=ResourceWarning,
-                expected_regex=r"Implicitly cleaning up <_TemporaryFileWrapper 
file=.*>",
+                expected_regex=r"Implicitly cleaning up <TemporaryFileWrapper 
file=.*>",
             ):
                 tmp_name = my_func(dir)
                 support.gc_collect()
@@ -1185,7 +1197,7 @@ def test_bad_encoding(self):
     def test_unexpected_error(self):
         dir = tempfile.mkdtemp()
         self.addCleanup(os_helper.rmtree, dir)
-        with mock.patch('tempfile._TemporaryFileWrapper') as mock_ntf, \
+        with mock.patch('tempfile.TemporaryFileWrapper') as mock_ntf, \
              mock.patch('io.open', mock.mock_open()) as mock_open:
             mock_ntf.side_effect = KeyboardInterrupt()
             with self.assertRaises(KeyboardInterrupt):
diff --git a/Lib/test/test_urllib_response.py b/Lib/test/test_urllib_response.py
index d949fa38bfc42f1..8c4acf4afc845e8 100644
--- a/Lib/test/test_urllib_response.py
+++ b/Lib/test/test_urllib_response.py
@@ -21,7 +21,7 @@ def setUp(self):
     def test_with(self):
         addbase = urllib.response.addbase(self.fp)
 
-        self.assertIsInstance(addbase, tempfile._TemporaryFileWrapper)
+        self.assertIsInstance(addbase, tempfile.TemporaryFileWrapper)
 
         def f():
             with addbase as spam:
diff --git a/Lib/urllib/response.py b/Lib/urllib/response.py
index 5a2c3cc78c395d5..fc57ad82d99ac09 100644
--- a/Lib/urllib/response.py
+++ b/Lib/urllib/response.py
@@ -11,7 +11,7 @@
 __all__ = ['addbase', 'addclosehook', 'addinfo', 'addinfourl']
 
 
-class addbase(tempfile._TemporaryFileWrapper):
+class addbase(tempfile.TemporaryFileWrapper):
     """Base class for addinfo and addclosehook. Is a good idea for garbage 
collection."""
 
     # XXX Add a method to expose the timeout on the underlying socket?
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst 
b/Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst
new file mode 100644
index 000000000000000..6c800551c906f2e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst
@@ -0,0 +1 @@
+:class:`!tempfile._TemporaryFileWrapper` has been renamed to the public 
:class:`tempfile.TemporaryFileWrapper`. The old private name is kept as a 
deprecated alias and will be removed in Python 3.21.

_______________________________________________
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