[Python-checkins] cpython: Issue 24230: The tempfile module now accepts bytes for prefix, suffix and dir

gregory.p.smith python-checkins at python.org
Sat May 23 01:18:30 CEST 2015


https://hg.python.org/cpython/rev/870899ce71f4
changeset:   96218:870899ce71f4
parent:      96215:620a247b4960
user:        Gregory P. Smith <greg at krypto.org>
date:        Fri May 22 16:18:14 2015 -0700
summary:
  Issue 24230: The tempfile module now accepts bytes for prefix, suffix and dir
parameters and returns bytes in such situations (matching the os module APIs).

files:
  Doc/library/tempfile.rst  |   37 +++++-
  Doc/whatsnew/3.5.rst      |    7 +-
  Lib/tempfile.py           |  115 +++++++++++++----
  Lib/test/test_tempfile.py |  169 ++++++++++++++++++++++---
  Misc/NEWS                 |    3 +
  5 files changed, 281 insertions(+), 50 deletions(-)


diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst
--- a/Doc/library/tempfile.rst
+++ b/Doc/library/tempfile.rst
@@ -119,7 +119,7 @@
    .. versionadded:: 3.2
 
 
-.. function:: mkstemp(suffix='', prefix='tmp', dir=None, text=False)
+.. function:: mkstemp(suffix=None, prefix=None, dir=None, text=False)
 
    Creates a temporary file in the most secure manner possible.  There are
    no race conditions in the file's creation, assuming that the platform
@@ -148,6 +148,16 @@
    filename will have any nice properties, such as not requiring quoting
    when passed to external commands via ``os.popen()``.
 
+   *suffix*, *prefix*, and *dir* must all contain the same type, if specified.
+   If they are bytes, the returned name will be bytes instead of str.
+   If you want to force a bytes return value with otherwise default behavior,
+   pass ``suffix=b''``.
+
+   A *prefix* value of ``None`` means use the return value of
+   :func:`gettempprefix` or :func:`gettempprefixb` as appropriate.
+
+   A *suffix* value of ``None`` means use an appropriate empty value.
+
    If *text* is specified, it indicates whether to open the file in binary
    mode (the default) or text mode.  On some platforms, this makes no
    difference.
@@ -156,8 +166,14 @@
    file (as would be returned by :func:`os.open`) and the absolute pathname
    of that file, in that order.
 
+   .. versionchanged:: 3.5
+      *suffix*, *prefix*, and *dir* may now be supplied in bytes in order to
+      obtain a bytes return value.  Prior to this, only str was allowed.
+      *suffix* and *prefix* now accept and default to ``None`` to cause
+      an appropriate default value to be used.
 
-.. function:: mkdtemp(suffix='', prefix='tmp', dir=None)
+
+.. function:: mkdtemp(suffix=None, prefix=None, dir=None)
 
    Creates a temporary directory in the most secure manner possible. There
    are no race conditions in the directory's creation.  The directory is
@@ -171,6 +187,12 @@
 
    :func:`mkdtemp` returns the absolute pathname of the new directory.
 
+   .. versionchanged:: 3.5
+      *suffix*, *prefix*, and *dir* may now be supplied in bytes in order to
+      obtain a bytes return value.  Prior to this, only str was allowed.
+      *suffix* and *prefix* now accept and default to ``None`` to cause
+      an appropriate default value to be used.
+
 
 .. function:: mktemp(suffix='', prefix='tmp', dir=None)
 
@@ -239,12 +261,23 @@
    :data:`tempdir` is not ``None``, this simply returns its contents; otherwise,
    the search described above is performed, and the result returned.
 
+.. function:: gettempdirb()
+
+   Same as :func:`gettempdir` but the return value is in bytes.
+
+   .. versionadded:: 3.5
 
 .. function:: gettempprefix()
 
    Return the filename prefix used to create temporary files.  This does not
    contain the directory component.
 
+.. function:: gettempprefixb()
+
+   Same as :func:`gettempprefixb` but the return value is in bytes.
+
+   .. versionadded:: 3.5
+
 
 Examples
 --------
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -96,7 +96,12 @@
 
 Significantly Improved Library Modules:
 
-* None yet.
+* You may now pass bytes to the :mod:`tempfile` module's APIs and it will
+  return the temporary pathname as bytes instead of str.  It also accepts
+  a value of ``None`` on parameters where only str was accepted in the past to
+  do the right thing based on the types of the other inputs.  Two functions,
+  :func:`gettempdirb` and :func:`gettempprefixb`, have been added to go along
+  with this.  This behavior matches that of the :mod:`os` APIs.
 
 Security improvements:
 
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -6,6 +6,14 @@
 except for 'mktemp'.  'mktemp' is subject to race conditions and
 should not be used; it is provided for backward compatibility only.
 
+The default path names are returned as str.  If you supply bytes as
+input, all return values will be in bytes.  Ex:
+
+    >>> tempfile.mkstemp()
+    (4, '/tmp/tmptpu9nin8')
+    >>> tempfile.mkdtemp(suffix=b'')
+    b'/tmp/tmppbi8f0hy'
+
 This module also provides some data items to the user:
 
   TMP_MAX  - maximum number of names that will be tried before
@@ -21,7 +29,8 @@
     "mkstemp", "mkdtemp",                  # low level safe interfaces
     "mktemp",                              # deprecated unsafe interface
     "TMP_MAX", "gettempprefix",            # constants
-    "tempdir", "gettempdir"
+    "tempdir", "gettempdir",
+    "gettempprefixb", "gettempdirb",
    ]
 
 
@@ -55,8 +64,10 @@
 else:
     TMP_MAX = 10000
 
-# Although it does not have an underscore for historical reasons, this
-# variable is an internal implementation detail (see issue 10354).
+# This variable _was_ unused for legacy reasons, see issue 10354.
+# But as of 3.5 we actually use it at runtime so changing it would
+# have a possibly desirable side effect...  But we do not want to support
+# that as an API.  It is undocumented on purpose.  Do not depend on this.
 template = "tmp"
 
 # Internal routines.
@@ -82,6 +93,46 @@
     else:
         return True
 
+
+def _infer_return_type(*args):
+    """Look at the type of all args and divine their implied return type."""
+    return_type = None
+    for arg in args:
+        if arg is None:
+            continue
+        if isinstance(arg, bytes):
+            if return_type is str:
+                raise TypeError("Can't mix bytes and non-bytes in "
+                                "path components.")
+            return_type = bytes
+        else:
+            if return_type is bytes:
+                raise TypeError("Can't mix bytes and non-bytes in "
+                                "path components.")
+            return_type = str
+    if return_type is None:
+        return str  # tempfile APIs return a str by default.
+    return return_type
+
+
+def _sanitize_params(prefix, suffix, dir):
+    """Common parameter processing for most APIs in this module."""
+    output_type = _infer_return_type(prefix, suffix, dir)
+    if suffix is None:
+        suffix = output_type()
+    if prefix is None:
+        if output_type is str:
+            prefix = template
+        else:
+            prefix = _os.fsencode(template)
+    if dir is None:
+        if output_type is str:
+            dir = gettempdir()
+        else:
+            dir = gettempdirb()
+    return prefix, suffix, dir, output_type
+
+
 class _RandomNameSequence:
     """An instance of _RandomNameSequence generates an endless
     sequence of unpredictable strings which can safely be incorporated
@@ -195,17 +246,18 @@
     return _name_sequence
 
 
-def _mkstemp_inner(dir, pre, suf, flags):
+def _mkstemp_inner(dir, pre, suf, flags, output_type):
     """Code common to mkstemp, TemporaryFile, and NamedTemporaryFile."""
 
     names = _get_candidate_names()
+    if output_type is bytes:
+        names = map(_os.fsencode, names)
 
     for seq in range(TMP_MAX):
         name = next(names)
         file = _os.path.join(dir, pre + name + suf)
         try:
             fd = _os.open(file, flags, 0o600)
-            return (fd, _os.path.abspath(file))
         except FileExistsError:
             continue    # try again
         except PermissionError:
@@ -216,6 +268,7 @@
                 continue
             else:
                 raise
+        return (fd, _os.path.abspath(file))
 
     raise FileExistsError(_errno.EEXIST,
                           "No usable temporary file name found")
@@ -224,9 +277,13 @@
 # User visible interfaces.
 
 def gettempprefix():
-    """Accessor for tempdir.template."""
+    """The default prefix for temporary directories."""
     return template
 
+def gettempprefixb():
+    """The default prefix for temporary directories as bytes."""
+    return _os.fsencode(gettempprefix())
+
 tempdir = None
 
 def gettempdir():
@@ -241,7 +298,11 @@
             _once_lock.release()
     return tempdir
 
-def mkstemp(suffix="", prefix=template, dir=None, text=False):
+def gettempdirb():
+    """A bytes version of tempfile.gettempdir()."""
+    return _os.fsencode(gettempdir())
+
+def mkstemp(suffix=None, prefix=None, dir=None, text=False):
     """User-callable function to create and return a unique temporary
     file.  The return value is a pair (fd, name) where fd is the
     file descriptor returned by os.open, and name is the filename.
@@ -259,6 +320,10 @@
     mode.  Else (the default) the file is opened in binary mode.  On
     some operating systems, this makes no difference.
 
+    suffix, prefix and dir must all contain the same type if specified.
+    If they are bytes, the returned name will be bytes; str otherwise.
+    A value of None will cause an appropriate default to be used.
+
     The file is readable and writable only by the creating user ID.
     If the operating system uses permission bits to indicate whether a
     file is executable, the file is executable by no one. The file
@@ -267,18 +332,17 @@
     Caller is responsible for deleting the file when done with it.
     """
 
-    if dir is None:
-        dir = gettempdir()
+    prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
 
     if text:
         flags = _text_openflags
     else:
         flags = _bin_openflags
 
-    return _mkstemp_inner(dir, prefix, suffix, flags)
+    return _mkstemp_inner(dir, prefix, suffix, flags, output_type)
 
 
-def mkdtemp(suffix="", prefix=template, dir=None):
+def mkdtemp(suffix=None, prefix=None, dir=None):
     """User-callable function to create and return a unique temporary
     directory.  The return value is the pathname of the directory.
 
@@ -291,17 +355,17 @@
     Caller is responsible for deleting the directory when done with it.
     """
 
-    if dir is None:
-        dir = gettempdir()
+    prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
 
     names = _get_candidate_names()
+    if output_type is bytes:
+        names = map(_os.fsencode, names)
 
     for seq in range(TMP_MAX):
         name = next(names)
         file = _os.path.join(dir, prefix + name + suffix)
         try:
             _os.mkdir(file, 0o700)
-            return file
         except FileExistsError:
             continue    # try again
         except PermissionError:
@@ -312,6 +376,7 @@
                 continue
             else:
                 raise
+        return file
 
     raise FileExistsError(_errno.EEXIST,
                           "No usable temporary directory name found")
@@ -323,8 +388,8 @@
     Arguments are as for mkstemp, except that the 'text' argument is
     not accepted.
 
-    This function is unsafe and should not be used.  The file name
-    refers to a file that did not exist at some point, but by the time
+    THIS FUNCTION IS UNSAFE AND SHOULD NOT BE USED.  The file name may
+    refer to a file that did not exist at some point, but by the time
     you get around to creating it, someone else may have beaten you to
     the punch.
     """
@@ -454,7 +519,7 @@
 
 
 def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
-                       newline=None, suffix="", prefix=template,
+                       newline=None, suffix=None, prefix=None,
                        dir=None, delete=True):
     """Create and return a temporary file.
     Arguments:
@@ -471,8 +536,7 @@
     when it is closed unless the 'delete' argument is set to False.
     """
 
-    if dir is None:
-        dir = gettempdir()
+    prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
 
     flags = _bin_openflags
 
@@ -481,7 +545,7 @@
     if _os.name == 'nt' and delete:
         flags |= _os.O_TEMPORARY
 
-    (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags)
+    (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags, output_type)
     try:
         file = _io.open(fd, mode, buffering=buffering,
                         newline=newline, encoding=encoding)
@@ -503,7 +567,7 @@
     _O_TMPFILE_WORKS = hasattr(_os, 'O_TMPFILE')
 
     def TemporaryFile(mode='w+b', buffering=-1, encoding=None,
-                      newline=None, suffix="", prefix=template,
+                      newline=None, suffix=None, prefix=None,
                       dir=None):
         """Create and return a temporary file.
         Arguments:
@@ -519,8 +583,7 @@
         """
         global _O_TMPFILE_WORKS
 
-        if dir is None:
-            dir = gettempdir()
+        prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
 
         flags = _bin_openflags
         if _O_TMPFILE_WORKS:
@@ -544,7 +607,7 @@
                     raise
             # Fallback to _mkstemp_inner().
 
-        (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags)
+        (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags, output_type)
         try:
             _os.unlink(name)
             return _io.open(fd, mode, buffering=buffering,
@@ -562,7 +625,7 @@
 
     def __init__(self, max_size=0, mode='w+b', buffering=-1,
                  encoding=None, newline=None,
-                 suffix="", prefix=template, dir=None):
+                 suffix=None, prefix=None, dir=None):
         if 'b' in mode:
             self._file = _io.BytesIO()
         else:
@@ -713,7 +776,7 @@
     in it are removed.
     """
 
-    def __init__(self, suffix="", prefix=template, dir=None):
+    def __init__(self, suffix=None, prefix=None, dir=None):
         self.name = mkdtemp(suffix, prefix, dir)
         self._finalizer = _weakref.finalize(
             self, self._cleanup, self.name,
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -36,10 +36,38 @@
 # in order of their appearance in the file.  Testing which requires
 # threads is not done here.
 
+class TestLowLevelInternals(unittest.TestCase):
+    def test_infer_return_type_singles(self):
+        self.assertIs(str, tempfile._infer_return_type(''))
+        self.assertIs(bytes, tempfile._infer_return_type(b''))
+        self.assertIs(str, tempfile._infer_return_type(None))
+
+    def test_infer_return_type_multiples(self):
+        self.assertIs(str, tempfile._infer_return_type('', ''))
+        self.assertIs(bytes, tempfile._infer_return_type(b'', b''))
+        with self.assertRaises(TypeError):
+            tempfile._infer_return_type('', b'')
+        with self.assertRaises(TypeError):
+            tempfile._infer_return_type(b'', '')
+
+    def test_infer_return_type_multiples_and_none(self):
+        self.assertIs(str, tempfile._infer_return_type(None, ''))
+        self.assertIs(str, tempfile._infer_return_type('', None))
+        self.assertIs(str, tempfile._infer_return_type(None, None))
+        self.assertIs(bytes, tempfile._infer_return_type(b'', None))
+        self.assertIs(bytes, tempfile._infer_return_type(None, b''))
+        with self.assertRaises(TypeError):
+            tempfile._infer_return_type('', None, b'')
+        with self.assertRaises(TypeError):
+            tempfile._infer_return_type(b'', None, '')
+
+
 # Common functionality.
+
 class BaseTestCase(unittest.TestCase):
 
     str_check = re.compile(r"^[a-z0-9_-]{8}$")
+    b_check = re.compile(br"^[a-z0-9_-]{8}$")
 
     def setUp(self):
         self._warnings_manager = support.check_warnings()
@@ -56,18 +84,31 @@
         npre  = nbase[:len(pre)]
         nsuf  = nbase[len(nbase)-len(suf):]
 
+        if dir is not None:
+            self.assertIs(type(name), str if type(dir) is str else bytes,
+                          "unexpected return type")
+        if pre is not None:
+            self.assertIs(type(name), str if type(pre) is str else bytes,
+                          "unexpected return type")
+        if suf is not None:
+            self.assertIs(type(name), str if type(suf) is str else bytes,
+                          "unexpected return type")
+        if (dir, pre, suf) == (None, None, None):
+            self.assertIs(type(name), str, "default return type must be str")
+
         # check for equality of the absolute paths!
         self.assertEqual(os.path.abspath(ndir), os.path.abspath(dir),
-                         "file '%s' not in directory '%s'" % (name, dir))
+                         "file %r not in directory %r" % (name, dir))
         self.assertEqual(npre, pre,
-                         "file '%s' does not begin with '%s'" % (nbase, pre))
+                         "file %r does not begin with %r" % (nbase, pre))
         self.assertEqual(nsuf, suf,
-                         "file '%s' does not end with '%s'" % (nbase, suf))
+                         "file %r does not end with %r" % (nbase, suf))
 
         nbase = nbase[len(pre):len(nbase)-len(suf)]
-        self.assertTrue(self.str_check.match(nbase),
-                     "random string '%s' does not match ^[a-z0-9_-]{8}$"
-                     % nbase)
+        check = self.str_check if isinstance(nbase, str) else self.b_check
+        self.assertTrue(check.match(nbase),
+                        "random characters %r do not match %r"
+                        % (nbase, check.pattern))
 
 
 class TestExports(BaseTestCase):
@@ -83,7 +124,9 @@
             "mktemp" : 1,
             "TMP_MAX" : 1,
             "gettempprefix" : 1,
+            "gettempprefixb" : 1,
             "gettempdir" : 1,
+            "gettempdirb" : 1,
             "tempdir" : 1,
             "template" : 1,
             "SpooledTemporaryFile" : 1,
@@ -320,7 +363,8 @@
             if bin: flags = self._bflags
             else:   flags = self._tflags
 
-            (self.fd, self.name) = tempfile._mkstemp_inner(dir, pre, suf, flags)
+            output_type = tempfile._infer_return_type(dir, pre, suf)
+            (self.fd, self.name) = tempfile._mkstemp_inner(dir, pre, suf, flags, output_type)
 
         def write(self, str):
             os.write(self.fd, str)
@@ -329,9 +373,17 @@
             self._close(self.fd)
             self._unlink(self.name)
 
-    def do_create(self, dir=None, pre="", suf="", bin=1):
+    def do_create(self, dir=None, pre=None, suf=None, bin=1):
+        output_type = tempfile._infer_return_type(dir, pre, suf)
         if dir is None:
-            dir = tempfile.gettempdir()
+            if output_type is str:
+                dir = tempfile.gettempdir()
+            else:
+                dir = tempfile.gettempdirb()
+        if pre is None:
+            pre = output_type()
+        if suf is None:
+            suf = output_type()
         file = self.mkstemped(dir, pre, suf, bin)
 
         self.nameCheck(file.name, dir, pre, suf)
@@ -345,6 +397,23 @@
         self.do_create(pre="a", suf="b").write(b"blat")
         self.do_create(pre="aa", suf=".txt").write(b"blat")
 
+    def test_basic_with_bytes_names(self):
+        # _mkstemp_inner can create files when given name parts all
+        # specified as bytes.
+        dir_b = tempfile.gettempdirb()
+        self.do_create(dir=dir_b, suf=b"").write(b"blat")
+        self.do_create(dir=dir_b, pre=b"a").write(b"blat")
+        self.do_create(dir=dir_b, suf=b"b").write(b"blat")
+        self.do_create(dir=dir_b, pre=b"a", suf=b"b").write(b"blat")
+        self.do_create(dir=dir_b, pre=b"aa", suf=b".txt").write(b"blat")
+        # Can't mix str & binary types in the args.
+        with self.assertRaises(TypeError):
+            self.do_create(dir="", suf=b"").write(b"blat")
+        with self.assertRaises(TypeError):
+            self.do_create(dir=dir_b, pre="").write(b"blat")
+        with self.assertRaises(TypeError):
+            self.do_create(dir=dir_b, pre=b"", suf="").write(b"blat")
+
     def test_basic_many(self):
         # _mkstemp_inner can create many files (stochastic)
         extant = list(range(TEST_FILES))
@@ -424,9 +493,10 @@
 
     def make_temp(self):
         return tempfile._mkstemp_inner(tempfile.gettempdir(),
-                                       tempfile.template,
+                                       tempfile.gettempprefix(),
                                        '',
-                                       tempfile._bin_openflags)
+                                       tempfile._bin_openflags,
+                                       str)
 
     def test_collision_with_existing_file(self):
         # _mkstemp_inner tries another name when a file with
@@ -462,7 +532,12 @@
         p = tempfile.gettempprefix()
 
         self.assertIsInstance(p, str)
-        self.assertTrue(len(p) > 0)
+        self.assertGreater(len(p), 0)
+
+        pb = tempfile.gettempprefixb()
+
+        self.assertIsInstance(pb, bytes)
+        self.assertGreater(len(pb), 0)
 
     def test_usable_template(self):
         # gettempprefix returns a usable prefix string
@@ -487,11 +562,11 @@
     def test_directory_exists(self):
         # gettempdir returns a directory which exists
 
-        dir = tempfile.gettempdir()
-        self.assertTrue(os.path.isabs(dir) or dir == os.curdir,
-                     "%s is not an absolute path" % dir)
-        self.assertTrue(os.path.isdir(dir),
-                     "%s is not a directory" % dir)
+        for d in (tempfile.gettempdir(), tempfile.gettempdirb()):
+            self.assertTrue(os.path.isabs(d) or d == os.curdir,
+                            "%r is not an absolute path" % d)
+            self.assertTrue(os.path.isdir(d),
+                            "%r is not a directory" % d)
 
     def test_directory_writable(self):
         # gettempdir returns a directory writable by the user
@@ -507,8 +582,11 @@
         # gettempdir always returns the same object
         a = tempfile.gettempdir()
         b = tempfile.gettempdir()
+        c = tempfile.gettempdirb()
 
         self.assertTrue(a is b)
+        self.assertNotEqual(type(a), type(c))
+        self.assertEqual(a, os.fsdecode(c))
 
     def test_case_sensitive(self):
         # gettempdir should not flatten its case
@@ -528,9 +606,17 @@
 class TestMkstemp(BaseTestCase):
     """Test mkstemp()."""
 
-    def do_create(self, dir=None, pre="", suf=""):
+    def do_create(self, dir=None, pre=None, suf=None):
+        output_type = tempfile._infer_return_type(dir, pre, suf)
         if dir is None:
-            dir = tempfile.gettempdir()
+            if output_type is str:
+                dir = tempfile.gettempdir()
+            else:
+                dir = tempfile.gettempdirb()
+        if pre is None:
+            pre = output_type()
+        if suf is None:
+            suf = output_type()
         (fd, name) = tempfile.mkstemp(dir=dir, prefix=pre, suffix=suf)
         (ndir, nbase) = os.path.split(name)
         adir = os.path.abspath(dir)
@@ -552,6 +638,24 @@
         self.do_create(pre="aa", suf=".txt")
         self.do_create(dir=".")
 
+    def test_basic_with_bytes_names(self):
+        # mkstemp can create files when given name parts all
+        # specified as bytes.
+        d = tempfile.gettempdirb()
+        self.do_create(dir=d, suf=b"")
+        self.do_create(dir=d, pre=b"a")
+        self.do_create(dir=d, suf=b"b")
+        self.do_create(dir=d, pre=b"a", suf=b"b")
+        self.do_create(dir=d, pre=b"aa", suf=b".txt")
+        self.do_create(dir=b".")
+        with self.assertRaises(TypeError):
+            self.do_create(dir=".", pre=b"aa", suf=b".txt")
+        with self.assertRaises(TypeError):
+            self.do_create(dir=b".", pre="aa", suf=b".txt")
+        with self.assertRaises(TypeError):
+            self.do_create(dir=b".", pre=b"aa", suf=".txt")
+
+
     def test_choose_directory(self):
         # mkstemp can create directories in a user-selected directory
         dir = tempfile.mkdtemp()
@@ -567,9 +671,17 @@
     def make_temp(self):
         return tempfile.mkdtemp()
 
-    def do_create(self, dir=None, pre="", suf=""):
+    def do_create(self, dir=None, pre=None, suf=None):
+        output_type = tempfile._infer_return_type(dir, pre, suf)
         if dir is None:
-            dir = tempfile.gettempdir()
+            if output_type is str:
+                dir = tempfile.gettempdir()
+            else:
+                dir = tempfile.gettempdirb()
+        if pre is None:
+            pre = output_type()
+        if suf is None:
+            suf = output_type()
         name = tempfile.mkdtemp(dir=dir, prefix=pre, suffix=suf)
 
         try:
@@ -587,6 +699,21 @@
         os.rmdir(self.do_create(pre="a", suf="b"))
         os.rmdir(self.do_create(pre="aa", suf=".txt"))
 
+    def test_basic_with_bytes_names(self):
+        # mkdtemp can create directories when given all binary parts
+        d = tempfile.gettempdirb()
+        os.rmdir(self.do_create(dir=d))
+        os.rmdir(self.do_create(dir=d, pre=b"a"))
+        os.rmdir(self.do_create(dir=d, suf=b"b"))
+        os.rmdir(self.do_create(dir=d, pre=b"a", suf=b"b"))
+        os.rmdir(self.do_create(dir=d, pre=b"aa", suf=b".txt"))
+        with self.assertRaises(TypeError):
+            os.rmdir(self.do_create(dir=d, pre="aa", suf=b".txt"))
+        with self.assertRaises(TypeError):
+            os.rmdir(self.do_create(dir=d, pre=b"aa", suf=".txt"))
+        with self.assertRaises(TypeError):
+            os.rmdir(self.do_create(dir="", pre=b"aa", suf=b".txt"))
+
     def test_basic_many(self):
         # mkdtemp can create many directories (stochastic)
         extant = list(range(TEST_FILES))
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -61,6 +61,9 @@
 Library
 -------
 
+- Issue 24230: The tempfile module now accepts bytes for prefix, suffix and dir
+  parameters and returns bytes in such situations (matching the os module APIs).
+
 - Issue 24244: Prevents termination when an invalid format string is
   encountered on Windows in strftime.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list