Skip to content

Commit aa533dc

Browse files
Aniketsysobolevnblurb-it[bot]
authored
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 <mail@sobolevn.me> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
1 parent 374920a commit aa533dc

8 files changed

Lines changed: 86 additions & 18 deletions

File tree

Doc/deprecations/pending-removal-in-3.21.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ Pending removal in Python 3.21
2323
* Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now
2424
deprecated. These codes will be removed in Python 3.21. Use instead
2525
two-letter forms ``'Zf'`` and ``'Zd'``.
26+
27+
* :mod:`tempfile`:
28+
29+
* ``tempfile._TemporaryFileWrapper`` will be removed in Python 3.21. Use the
30+
public :class:`tempfile.TemporaryFileWrapper` instead.

Doc/library/tempfile.rst

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414

1515
This module creates temporary files and directories. It works on all
1616
supported platforms. :class:`TemporaryFile`, :class:`NamedTemporaryFile`,
17-
:class:`TemporaryDirectory`, and :class:`SpooledTemporaryFile` are high-level
18-
interfaces which provide automatic cleanup and can be used as
19-
:term:`context managers <context manager>`. :func:`mkstemp` and
20-
:func:`mkdtemp` are lower-level functions which require manual cleanup.
17+
:class:`TemporaryFileWrapper`, :class:`TemporaryDirectory`, and
18+
:class:`SpooledTemporaryFile` are high-level interfaces which provide
19+
automatic cleanup and can be used as :term:`context managers
20+
<context manager>`. :func:`mkstemp` and :func:`mkdtemp` are lower-level
21+
functions which require manual cleanup.
2122

2223
All the user-callable functions and constructors take additional arguments which
2324
allow direct control over the location and name of temporary files and
@@ -84,13 +85,13 @@ The module defines the following user-callable items:
8485
:func:`TemporaryFile` with *delete* and *delete_on_close* parameters that
8586
determine whether and how the named file should be automatically deleted.
8687

87-
The returned object is always a :term:`file-like object` whose :attr:`!file`
88-
attribute is the underlying true file object. This file-like object
89-
can be used in a :keyword:`with` statement, just like a normal file. The
90-
name of the temporary file can be retrieved from the :attr:`!name` attribute
91-
of the returned file-like object. On Unix, unlike with the
92-
:func:`TemporaryFile`, the directory entry does not get unlinked immediately
93-
after the file creation.
88+
The returned object is always a :class:`TemporaryFileWrapper` instance
89+
(a :term:`file-like object`) whose :attr:`~TemporaryFileWrapper.file` attribute is the underlying
90+
true file object. This file-like object can be used in a :keyword:`with`
91+
statement, just like a normal file. The name of the temporary file can be
92+
retrieved from the :attr:`!name` attribute of the returned file-like object.
93+
On Unix, unlike with the :func:`TemporaryFile`, the directory entry does not
94+
get unlinked immediately after the file creation.
9495

9596
If *delete* is true (the default) and *delete_on_close* is true (the
9697
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:
140141
.. versionchanged:: 3.12
141142
Added *delete_on_close* parameter.
142143

144+
.. class:: TemporaryFileWrapper(file, name, delete=True, delete_on_close=True)
145+
146+
A mutable wrapper returned by :func:`NamedTemporaryFile`. It wraps the
147+
underlying file object, delegating attribute access to it, and ensures
148+
the temporary file is deleted when appropriate.
149+
150+
.. attribute:: file
151+
152+
The underlying :term:`file-like object`.
153+
154+
.. attribute:: name
155+
156+
The file name of the temporary file.
157+
158+
.. method:: close()
159+
160+
Close the temporary file, possibly deleting it depending on the
161+
*delete* and *delete_on_close* arguments passed to
162+
:func:`NamedTemporaryFile`.
163+
164+
.. note::
165+
166+
``tempfile._TemporaryFileWrapper`` is kept as a backwards compatible
167+
deprecated alias for this class.
168+
It will be removed in Python 3.21
169+
170+
.. versionadded:: next
171+
143172

144173
.. class:: SpooledTemporaryFile(max_size=0, mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None)
145174

Doc/whatsnew/3.16.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,13 @@ New deprecations
588588
two-letter forms ``'Zf'`` and ``'Zd'``.
589589
(Contributed by Sergey B Kirpichev in :gh:`121249`.)
590590

591+
* :mod:`tempfile`
592+
593+
* The private ``tempfile._TemporaryFileWrapper`` name is deprecated
594+
and is slated for removal in Python 3.21.
595+
Use the new public :class:`tempfile.TemporaryFileWrapper` instead,
596+
which is the return type of :func:`tempfile.NamedTemporaryFile`.
597+
591598
.. Add deprecations above alphabetically, not here at the end.
592599
593600
.. include:: ../deprecations/pending-removal-in-3.17.rst

Lib/tempfile.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"TMP_MAX", "gettempprefix", # constants
3232
"tempdir", "gettempdir",
3333
"gettempprefixb", "gettempdirb",
34+
"TemporaryFileWrapper",
3435
]
3536

3637

@@ -484,7 +485,7 @@ def __del__(self):
484485
_warnings.warn(self.warn_message, ResourceWarning)
485486

486487

487-
class _TemporaryFileWrapper:
488+
class TemporaryFileWrapper:
488489
"""Temporary file wrapper
489490
490491
This class provides a wrapper around files opened for
@@ -555,6 +556,19 @@ def __iter__(self):
555556
for line in self.file:
556557
yield line
557558

559+
def __getattr__(name):
560+
if name == "_TemporaryFileWrapper":
561+
_warnings._deprecated(
562+
"tempfile._TemporaryFileWrapper",
563+
message=(
564+
"{name!r} is deprecated and slated for removal in Python {remove}. "
565+
"Use tempfile.TemporaryFileWrapper instead."
566+
),
567+
remove=(3, 21),
568+
)
569+
return TemporaryFileWrapper
570+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
571+
558572
def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
559573
newline=None, suffix=None, prefix=None,
560574
dir=None, delete=True, *, errors=None,
@@ -607,7 +621,7 @@ def opener(*args):
607621
raw = getattr(file, 'buffer', file)
608622
raw = getattr(raw, 'raw', raw)
609623
raw.name = name
610-
return _TemporaryFileWrapper(file, name, delete, delete_on_close)
624+
return TemporaryFileWrapper(file, name, delete, delete_on_close)
611625
except:
612626
file.close()
613627
raise

Lib/test/test_tempfile.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ def test_exports(self):
148148
"template" : 1,
149149
"SpooledTemporaryFile" : 1,
150150
"TemporaryDirectory" : 1,
151+
"TemporaryFileWrapper" : 1,
151152
}
152153

153154
unexp = []
@@ -980,12 +981,23 @@ def do_create(self, dir=None, pre="", suf="", delete=True):
980981

981982
def test_basic(self):
982983
# NamedTemporaryFile can create files
983-
self.do_create()
984+
f = self.do_create()
985+
self.assertIsInstance(f, tempfile.TemporaryFileWrapper)
984986
self.do_create(pre="a")
985987
self.do_create(suf="b")
986988
self.do_create(pre="a", suf="b")
987989
self.do_create(pre="aa", suf=".txt")
988990

991+
def test_in_all(self):
992+
self.assertIn("TemporaryFileWrapper", tempfile.__all__)
993+
994+
def test_deprecated_TemporaryFileWrapper_alias(self):
995+
# gh-152586: _TemporaryFileWrapper is a deprecated alias
996+
# for the public TemporaryFileWrapper class.
997+
with self.assertWarns(DeprecationWarning):
998+
obj = tempfile._TemporaryFileWrapper
999+
self.assertIs(obj, tempfile.TemporaryFileWrapper)
1000+
9891001
def test_method_lookup(self):
9901002
# Issue #18879: Looking up a temporary file method should keep it
9911003
# alive long enough.
@@ -1141,7 +1153,7 @@ def my_func(dir):
11411153
try:
11421154
with self.assertWarnsRegex(
11431155
expected_warning=ResourceWarning,
1144-
expected_regex=r"Implicitly cleaning up <_TemporaryFileWrapper file=.*>",
1156+
expected_regex=r"Implicitly cleaning up <TemporaryFileWrapper file=.*>",
11451157
):
11461158
tmp_name = my_func(dir)
11471159
support.gc_collect()
@@ -1185,7 +1197,7 @@ def test_bad_encoding(self):
11851197
def test_unexpected_error(self):
11861198
dir = tempfile.mkdtemp()
11871199
self.addCleanup(os_helper.rmtree, dir)
1188-
with mock.patch('tempfile._TemporaryFileWrapper') as mock_ntf, \
1200+
with mock.patch('tempfile.TemporaryFileWrapper') as mock_ntf, \
11891201
mock.patch('io.open', mock.mock_open()) as mock_open:
11901202
mock_ntf.side_effect = KeyboardInterrupt()
11911203
with self.assertRaises(KeyboardInterrupt):

Lib/test/test_urllib_response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def setUp(self):
2121
def test_with(self):
2222
addbase = urllib.response.addbase(self.fp)
2323

24-
self.assertIsInstance(addbase, tempfile._TemporaryFileWrapper)
24+
self.assertIsInstance(addbase, tempfile.TemporaryFileWrapper)
2525

2626
def f():
2727
with addbase as spam:

Lib/urllib/response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
__all__ = ['addbase', 'addclosehook', 'addinfo', 'addinfourl']
1212

1313

14-
class addbase(tempfile._TemporaryFileWrapper):
14+
class addbase(tempfile.TemporaryFileWrapper):
1515
"""Base class for addinfo and addclosehook. Is a good idea for garbage collection."""
1616

1717
# XXX Add a method to expose the timeout on the underlying socket?
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)