-
-
Notifications
You must be signed in to change notification settings - Fork 35.2k
bpo-29447: tempfile: Add pathlike object support for dir argument #1496
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
Closed
Closed
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
0c0d7df
Add pathlike object support
louisom 4ab46fe
Fix path value
louisom d2e1168
Fix doc
louisom f16b99c
Revert tempfile doc
louisom 9afde67
Fix low-level internals support path-like with bytes
louisom e9b7b2e
Add and fix test case
louisom 21871d0
Remove NEWS entry
louisom 9345faf
Add Louie Lu to ACKS
louisom e670c71
Fix low-level internal for path-like objects
louisom 949f9fd
Fix path-like test
louisom 732bbf4
Fix dir argument
louisom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,14 @@ | |
| else: | ||
| TEST_FILES = 100 | ||
|
|
||
|
|
||
| class _PathLikeObj(object): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| 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. | ||
|
|
@@ -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. | ||
|
|
||
|
|
@@ -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") | ||
|
|
@@ -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): | ||
|
|
@@ -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() | ||
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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") | ||
|
|
@@ -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)) | ||
|
|
@@ -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 | ||
|
|
@@ -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() | ||
|
|
@@ -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 | ||
|
|
@@ -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() | ||
|
|
@@ -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().""" | ||
|
|
@@ -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() | ||
|
|
@@ -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) | ||
|
|
@@ -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 | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)There was a problem hiding this comment.
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
prefixandsuffixare not paths themselves.