From 62407c95c2e81ac38c0e130afe423b3bc4bde907 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Tue, 30 Jun 2026 03:44:01 +0000 Subject: [PATCH 1/5] Make tempfile.TemporaryFileWrapper public --- Doc/library/tempfile.rst | 36 ++++++++++++++++++++++++++++++++---- Lib/tempfile.py | 7 +++++-- Lib/test/test_tempfile.py | 16 +++++++++++++++- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst index bf9198e175a0e11..47a00bfd4b35ceb 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 `. :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 +`. :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 @@ -140,6 +141,33 @@ 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 + alias for this class. + + .. versionadded:: 3.16 + .. 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/Lib/tempfile.py b/Lib/tempfile.py index 6dac9ab3c417171..a938afae96c3e78 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,8 @@ def __iter__(self): for line in self.file: yield line +_TemporaryFileWrapper = TemporaryFileWrapper + def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, delete=True, *, errors=None, @@ -607,7 +610,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..7fe33bdaf3ba269 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 = [] @@ -1141,7 +1142,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 ", ): tmp_name = my_func(dir) support.gc_collect() @@ -1195,6 +1196,19 @@ def test_unexpected_error(self): # How to test the mode and bufsize parameters? +class TestTemporaryFileWrapper(BaseTestCase): + """Test TemporaryFileWrapper.""" + + def test_public_name(self): + self.assertIs(tempfile.TemporaryFileWrapper, tempfile._TemporaryFileWrapper) + + def test_in_all(self): + self.assertIn("TemporaryFileWrapper", tempfile.__all__) + + def test_is_return_type_of_named_temporary_file(self): + with tempfile.NamedTemporaryFile() as f: + self.assertIsInstance(f, tempfile.TemporaryFileWrapper) + class TestSpooledTemporaryFile(BaseTestCase): """Test SpooledTemporaryFile().""" From 4fcba6710fdc946c48ccaea50014108e376e8575 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:27:16 +0530 Subject: [PATCH 2/5] Update Doc/library/tempfile.rst Co-authored-by: sobolevn --- Doc/library/tempfile.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst index 47a00bfd4b35ceb..a17e1ec5219ebfc 100644 --- a/Doc/library/tempfile.rst +++ b/Doc/library/tempfile.rst @@ -166,7 +166,7 @@ The module defines the following user-callable items: ``tempfile._TemporaryFileWrapper`` is kept as a backwards compatible alias for this class. - .. versionadded:: 3.16 + .. versionadded:: next .. class:: SpooledTemporaryFile(max_size=0, mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None) From 38db57fdc807811e8b028a869c6ba87d9edce28e Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Wed, 1 Jul 2026 22:50:33 +0000 Subject: [PATCH 3/5] add depreciation and update tests --- Lib/tempfile.py | 12 +++++++++++- Lib/test/test_tempfile.py | 27 +++++++++++++-------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Lib/tempfile.py b/Lib/tempfile.py index a938afae96c3e78..bb064611cb987a0 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -556,7 +556,17 @@ def __iter__(self): for line in self.file: yield line -_TemporaryFileWrapper = TemporaryFileWrapper +# _TemporaryFileWrapper is deprecated in favor of the public +def __getattr__(name): + if name == "_TemporaryFileWrapper": + _warnings._deprecated( + "tempfile._TemporaryFileWrapper", + message="{name} is deprecated and will be removed in a future " + "version. Use tempfile.TemporaryFileWrapper instead.", + remove=(3, 16), + ) + 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, diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 7fe33bdaf3ba269..c5f5bf000a31770 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -981,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. @@ -1186,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): @@ -1196,18 +1207,6 @@ def test_unexpected_error(self): # How to test the mode and bufsize parameters? -class TestTemporaryFileWrapper(BaseTestCase): - """Test TemporaryFileWrapper.""" - - def test_public_name(self): - self.assertIs(tempfile.TemporaryFileWrapper, tempfile._TemporaryFileWrapper) - - def test_in_all(self): - self.assertIn("TemporaryFileWrapper", tempfile.__all__) - - def test_is_return_type_of_named_temporary_file(self): - with tempfile.NamedTemporaryFile() as f: - self.assertIsInstance(f, tempfile.TemporaryFileWrapper) class TestSpooledTemporaryFile(BaseTestCase): """Test SpooledTemporaryFile().""" From a790753b0894e9c94bd6d2dece6d3b7c69f6ff24 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:59:28 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-01-22-59-25.gh-issue-152586.WuEae6.rst 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..94e0c3ccf56a2a4 --- /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 a future version. From c30d7a238dc2b5883912ca2193922c7a2f787f1a Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Wed, 1 Jul 2026 23:04:09 +0000 Subject: [PATCH 5/5] Make tempfile._TemporaryFileWrapper public as TemporaryFileWrapper --- Lib/test/test_urllib_response.py | 2 +- Lib/urllib/response.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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?