Skip to content

gh-87646: Make tempfile.NamedTemporaryFile and TemporaryDirectory path-like #114765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions Doc/library/tempfile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ The module defines the following user-callable items:
.. versionchanged:: 3.12
Added *delete_on_close* parameter.

.. versionchanged:: next
Added support for the :term:`path-like object` protocol.


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

Expand Down Expand Up @@ -217,6 +220,9 @@ The module defines the following user-callable items:
.. versionchanged:: 3.12
Added the *delete* parameter.

.. versionchanged:: next
Added support for the :term:`path-like object` protocol.


.. function:: mkstemp(suffix=None, prefix=None, dir=None, text=False)

Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,14 @@ sysconfig
(Contributed by Xuehai Pan in :gh:`131799`.)


tempfile
--------

* :func:`tempfile.NamedTemporaryFile` and :func:`~tempfile.TemporaryDirectory`
now return a :term:`path-like object`.
(Contributed by Barney Gale in :gh:`87646`.)


threading
---------

Expand Down
8 changes: 8 additions & 0 deletions Lib/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,10 @@ def func_wrapper(*args, **kwargs):
setattr(self, name, a)
return a

def __fspath__(self):
"""Return the filesystem path of the temporary file."""
return self.name

# The underlying __enter__ method returns the wrong object
# (self.file) so override it to return the wrapper
def __enter__(self):
Expand Down Expand Up @@ -962,6 +966,10 @@ def _cleanup(cls, name, warn_message, ignore_errors=False, delete=True):
def __repr__(self):
return "<{} {!r}>".format(self.__class__.__name__, self.name)

def __fspath__(self):
"""Return the filesystem path of the temporary directory."""
return self.name

def __enter__(self):
return self.name

Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,8 +950,12 @@ def do_create(self, dir=None, pre="", suf="", delete=True):
delete=delete)

self.nameCheck(file.name, dir, pre, suf)
self.nameCheck(os.fspath(file), dir, pre, suf)
return file

def test_pathlike(self):
tmp = self.do_create()
self.assertIsInstance(tmp, os.PathLike)

def test_basic(self):
# NamedTemporaryFile can create files
Expand Down Expand Up @@ -1623,9 +1627,14 @@ def do_create(self, dir=None, pre="", suf="", recurse=1, dirs=1, files=1,
dir=dir, prefix=pre, suffix=suf,
ignore_cleanup_errors=ignore_cleanup_errors)
self.nameCheck(tmp.name, dir, pre, suf)
self.nameCheck(os.fspath(tmp), dir, pre, suf)
self.do_create2(tmp.name, recurse, dirs, files)
return tmp

def test_pathlike(self):
tmp = self.do_create()
self.assertIsInstance(tmp, os.PathLike)

def do_create2(self, path, recurse=1, dirs=1, files=1):
# Create subdirectories and some files
if recurse:
Expand Down
17 changes: 9 additions & 8 deletions Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1377,12 +1377,17 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True,
"metadata_encoding is only supported for reading files")

# Check if we were passed a file-like object
if isinstance(file, os.PathLike):
file = os.fspath(file)
if isinstance(file, str):
if hasattr(file, 'read') or hasattr(file, 'write'):
self._filePassed = 1
self.fp = file
if isinstance(file, os.PathLike):
self.filename = os.fspath(file)
else:
self.filename = getattr(file, 'name', None)
else:
# No, it's a filename
self._filePassed = 0
self.filename = file
self.filename = os.fspath(file)
modeDict = {'r' : 'rb', 'w': 'w+b', 'x': 'x+b', 'a' : 'r+b',
'r+b': 'w+b', 'w+b': 'wb', 'x+b': 'xb'}
filemode = modeDict[mode]
Expand All @@ -1395,10 +1400,6 @@ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True,
continue
raise
break
else:
self._filePassed = 1
self.fp = file
self.filename = getattr(file, 'name', None)
self._fileRefCnt = 1
self._lock = threading.RLock()
self._seekable = True
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Return a :term:`path-like object` from :func:`tempfile.NamedTemporaryFile`
and :func:`~tempfile.TemporaryDirectory`. (Contributed by Barney Gale in
:gh:`87646`.)
Loading