Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Lib/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def _infer_return_type(*args):
for arg in args:
if arg is None:
continue
elif isinstance(arg, _os.PathLike):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this explicit test is needed? Aren't high-level function fail in any case when pass path-like object as prefix or suffix?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allow path-like objects for prefix and suffix? it will failed when path-like objects concat with string:
file = _os.path.join(dir, pre + name + suf)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lulouie No, path-like objects are only useful for things that represent paths. Both prefix and suffix are not paths themselves.

raise TypeError("Arguments only allow str or bytes.")
if isinstance(arg, bytes):
if return_type is str:
raise TypeError("Can't mix bytes and non-bytes in "
Expand All @@ -117,6 +119,7 @@ def _infer_return_type(*args):

def _sanitize_params(prefix, suffix, dir):
"""Common parameter processing for most APIs in this module."""
dir = _os.fspath(dir) if isinstance(dir, _os.PathLike) else dir
output_type = _infer_return_type(prefix, suffix, dir)
if suffix is None:
suffix = output_type()
Expand Down
101 changes: 92 additions & 9 deletions Lib/test/test_tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
else:
TEST_FILES = 100


class _PathLikeObj(object):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use test.support.FakePath.

def __init__(self, path):
self.path = path

def __fspath__(self):
return self.path

# This is organized as one test for each chunk of code in tempfile.py,
# in order of their appearance in the file. Testing which requires
# threads is not done here.
Expand All @@ -42,25 +50,67 @@ def test_infer_return_type_singles(self):
self.assertIs(bytes, tempfile._infer_return_type(b''))
self.assertIs(str, tempfile._infer_return_type(None))

with self.assertRaises(TypeError):
tempfile._infer_return_type(_PathLikeObj(''))
with self.assertRaises(TypeError):
tempfile._infer_return_type(_PathLikeObj(b''))

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'', '')
with self.assertRaises(TypeError):
# path-like objects isn't bytes or str, should raise
# TypeError when using in _infer_return_type
tempfile._infer_return_type(b'', _PathLikeObj(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, '')

def sanitize_check(self, types, sanitize):
pre, suf, dir, output_type = sanitize
self.assertIs(type(pre), types)
self.assertIs(type(suf), types)
self.assertIs(type(dir), types)
self.assertIs(output_type, types)

def test_sanitize_params(self):
sp = tempfile._sanitize_params
self.sanitize_check(str, sp(prefix='', suffix='', dir=''))
self.sanitize_check(str, sp(prefix='a', suffix='b', dir='c'))
self.sanitize_check(str, sp(prefix='foo', suffix='bar', dir=_PathLikeObj('.')))
self.sanitize_check(bytes, sp(prefix=b'', suffix=b'', dir=b''))
self.sanitize_check(bytes, sp(prefix=b'a', suffix=b'b', dir=b'c'))
self.sanitize_check(bytes, sp(prefix=b'foo', suffix=b'bar', dir=_PathLikeObj(b'.')))

with self.assertRaises(TypeError):
sp(prefix='', suffix=b'', dir='')
with self.assertRaises(TypeError):
sp(prefix=b'', suffix='', dir=b'')
with self.assertRaises(TypeError):
sp(prefix=b'', suffix='', dir=b'')
with self.assertRaises(TypeError):
sp(prefix=b'', suffix='', dir=_PathLikeObj(b''))
with self.assertRaises(TypeError):
sp(prefix='', suffix=b'', dir=_PathLikeObj(''))
with self.assertRaises(TypeError):
sp(prefix=b'', suffix=b'', dir=_PathLikeObj(''))
with self.assertRaises(TypeError):
sp(prefix='', suffix='', dir=_PathLikeObj(b''))


# Common functionality.

Expand All @@ -78,12 +128,14 @@ def setUp(self):
def tearDown(self):
self._warnings_manager.__exit__(None, None, None)


def nameCheck(self, name, dir, pre, suf):
# Assume dir is str or bytes, not path-like objects
(ndir, nbase) = os.path.split(name)
npre = nbase[:len(pre)]
nsuf = nbase[len(nbase)-len(suf):]
npre = nbase[:len(pre)]
nsuf = nbase[len(nbase) - len(suf):]

if isinstance(dir, os.PathLike):
dir = os.fspath(dir)
if dir is not None:
self.assertIs(type(name), str if type(dir) is str else bytes,
"unexpected return type")
Expand Down Expand Up @@ -362,7 +414,8 @@ def __init__(self, dir, pre, suf, bin):
if bin: flags = self._bflags
else: flags = self._tflags

output_type = tempfile._infer_return_type(dir, pre, suf)
fdir = os.fspath(dir) if isinstance(dir, os.PathLike) else dir
output_type = tempfile._infer_return_type(pre, suf, fdir)
(self.fd, self.name) = tempfile._mkstemp_inner(dir, pre, suf, flags, output_type)

def write(self, str):
Expand All @@ -373,7 +426,8 @@ def __del__(self):
self._unlink(self.name)

def do_create(self, dir=None, pre=None, suf=None, bin=1):
output_type = tempfile._infer_return_type(dir, pre, suf)
fdir = os.fspath(dir) if isinstance(dir, os.PathLike) else dir
output_type = tempfile._infer_return_type(fdir, pre, suf)
if dir is None:
if output_type is str:
dir = tempfile.gettempdir()
Expand All @@ -384,7 +438,6 @@ def do_create(self, dir=None, pre=None, suf=None, bin=1):
if suf is None:
suf = output_type()
file = self.mkstemped(dir, pre, suf, bin)

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

Expand All @@ -405,6 +458,7 @@ def test_basic_with_bytes_names(self):
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")
Expand All @@ -413,6 +467,10 @@ def test_basic_with_bytes_names(self):
with self.assertRaises(TypeError):
self.do_create(dir=dir_b, pre=b"", suf="").write(b"blat")

# Can't accept path-like objects
self.do_create(dir=_PathLikeObj(b""), suf=b"").write(b"blat")


def test_basic_many(self):
# _mkstemp_inner can create many files (stochastic)
extant = list(range(TEST_FILES))
Expand All @@ -427,6 +485,12 @@ def test_choose_directory(self):
finally:
os.rmdir(dir)

def test_choose_pathlike_directory(self):
# _mkstemp_inner can create files in a user-selected pathlike directory
dir = _PathLikeObj(tempfile.mkdtemp())
self.addCleanup(os.rmdir, dir)
self.do_create(dir=dir).write(b'blat')

@unittest.skipUnless(has_stat, 'os.stat not available')
def test_file_mode(self):
# _mkstemp_inner creates files with the proper mode
Expand Down Expand Up @@ -606,7 +670,8 @@ class TestMkstemp(BaseTestCase):
"""Test mkstemp()."""

def do_create(self, dir=None, pre=None, suf=None):
output_type = tempfile._infer_return_type(dir, pre, suf)
fdir = os.fspath(dir) if isinstance(dir, os.PathLike) else dir
output_type = tempfile._infer_return_type(fdir, pre, suf)
if dir is None:
if output_type is str:
dir = tempfile.gettempdir()
Expand Down Expand Up @@ -636,6 +701,9 @@ def test_basic(self):
self.do_create(pre="a", suf="b")
self.do_create(pre="aa", suf=".txt")
self.do_create(dir=".")
self.do_create(dir=_PathLikeObj("."))
self.do_create(dir=_PathLikeObj(b"."), pre=b"a", suf=b"b")
self.do_create(dir=_PathLikeObj(b"."), pre=b"aa", suf=b".txt")

def test_basic_with_bytes_names(self):
# mkstemp can create files when given name parts all
Expand All @@ -647,14 +715,15 @@ def test_basic_with_bytes_names(self):
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".")
self.do_create(dir=_PathLikeObj(b"."), pre=b"aa", suf=b".txt")

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()
Expand All @@ -663,6 +732,12 @@ def test_choose_directory(self):
finally:
os.rmdir(dir)

def test_choose_pathlike_directory(self):
# mkstemp can create directories in a user-selected pathlike directory
dir = _PathLikeObj(tempfile.mkdtemp())
self.addCleanup(os.rmdir, dir)
self.do_create(dir=dir)


class TestMkdtemp(TestBadTempdir, BaseTestCase):
"""Test mkdtemp()."""
Expand All @@ -671,7 +746,8 @@ def make_temp(self):
return tempfile.mkdtemp()

def do_create(self, dir=None, pre=None, suf=None):
output_type = tempfile._infer_return_type(dir, pre, suf)
fdir = os.fspath(dir) if isinstance(dir, os.PathLike) else dir
output_type = tempfile._infer_return_type(fdir, pre, suf)
if dir is None:
if output_type is str:
dir = tempfile.gettempdir()
Expand Down Expand Up @@ -712,6 +788,7 @@ def test_basic_with_bytes_names(self):
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"))
os.rmdir(self.do_create(dir=_PathLikeObj(b""), pre=b"aa", suf=b".txt"))

def test_basic_many(self):
# mkdtemp can create many directories (stochastic)
Expand All @@ -732,6 +809,12 @@ def test_choose_directory(self):
finally:
os.rmdir(dir)

def test_choose_pathlike_directory(self):
# mkdtemp can create directories in a user-selected pathlike directory
dir = _PathLikeObj(tempfile.mkdtemp())
self.addCleanup(os.rmdir, dir)
os.rmdir(self.do_create(dir=dir))

@unittest.skipUnless(has_stat, 'os.stat not available')
def test_mode(self):
# mkdtemp creates directories with the proper mode
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ Jason Lowe
Tony Lownds
Ray Loyzaga
Kang-Hao (Kenny) Lu
Louie Lu
Lukas Lueg
Loren Luke
Fredrik Lundh
Expand Down