diff -r f03c074b6242 Doc/library/tempfile.rst --- a/Doc/library/tempfile.rst Fri Sep 18 15:21:22 2015 -0700 +++ b/Doc/library/tempfile.rst Tue Oct 06 01:30:02 2015 -0300 @@ -106,20 +106,21 @@ the truncate method now accepts a ``size`` argument. -.. function:: TemporaryDirectory(suffix='', prefix='tmp', dir=None) +.. function:: TemporaryDirectory(suffix='', prefix='tmp', dir=None, delete=True) This function securely creates a temporary directory using the same rules as :func:`mkdtemp`. The resulting object can be used as a context manager (see :ref:`tempfile-examples`). On completion of the context or destruction of the temporary directory object the newly created temporary directory - and all its contents are removed from the filesystem. + and all its contents are removed from the filesystem unless delete parameter + is set to False. The directory name can be retrieved from the :attr:`name` attribute of the returned object. When the returned object is used as a context manager, the :attr:`name` will be assigned to the target of the :keyword:`as` clause in the :keyword:`with` statement, if there is one. - The directory can be explicitly cleaned up by calling the + The directory can be explicitly cleaned up, even when delete=False, by calling the :func:`cleanup` method. .. versionadded:: 3.2 diff -r f03c074b6242 Lib/tempfile.py --- a/Lib/tempfile.py Fri Sep 18 15:21:22 2015 -0700 +++ b/Lib/tempfile.py Tue Oct 06 01:30:02 2015 -0300 @@ -776,17 +776,19 @@ in it are removed. """ - def __init__(self, suffix=None, prefix=None, dir=None): + def __init__(self, suffix=None, prefix=None, dir=None, delete=True): self.name = mkdtemp(suffix, prefix, dir) - self._finalizer = _weakref.finalize( - self, self._cleanup, self.name, - warn_message="Implicitly cleaning up {!r}".format(self)) + self._delete_directory = delete + self._warn_message = "Implicitly cleaning up {!r}".format(self) + self._finalizer = _weakref.finalize(self, self._cleanup, self.name, + warn_message=self._warn_message, + delete_directory=self._delete_directory) @classmethod - def _cleanup(cls, name, warn_message): - _shutil.rmtree(name) - _warnings.warn(warn_message, ResourceWarning) - + def _cleanup(cls, name, warn_message, delete_directory=True): + if delete_directory: + _shutil.rmtree(name) + _warnings.warn(warn_message, ResourceWarning) def __repr__(self): return "<{} {!r}>".format(self.__class__.__name__, self.name) @@ -795,7 +797,8 @@ return self.name def __exit__(self, exc, value, tb): - self.cleanup() + if self._delete_directory: + self.cleanup() def cleanup(self): if self._finalizer.detach(): diff -r f03c074b6242 Lib/test/test_tempfile.py --- a/Lib/test/test_tempfile.py Fri Sep 18 15:21:22 2015 -0700 +++ b/Lib/test/test_tempfile.py Tue Oct 06 01:30:02 2015 -0300 @@ -1285,14 +1285,15 @@ class TestTemporaryDirectory(BaseTestCase): """Test TemporaryDirectory().""" - def do_create(self, dir=None, pre="", suf="", recurse=1): + def do_create(self, dir=None, pre="", suf="", recurse=1, delete=True): if dir is None: dir = tempfile.gettempdir() - tmp = tempfile.TemporaryDirectory(dir=dir, prefix=pre, suffix=suf) + tmp = tempfile.TemporaryDirectory(dir=dir, prefix=pre, + suffix=suf, delete=delete) self.nameCheck(tmp.name, dir, pre, suf) # Create a subdirectory and some files if recurse: - d1 = self.do_create(tmp.name, pre, suf, recurse-1) + d1 = self.do_create(tmp.name, pre, suf, recurse-1, delete=delete) d1.name = None with open(os.path.join(tmp.name, "test.txt"), "wb") as f: f.write(b"Hello world!") @@ -1308,11 +1309,11 @@ tempfile.TemporaryDirectory(dir=nonexistent) self.assertEqual(cm.exception.errno, errno.ENOENT) - def test_explicit_cleanup(self): + def _explicit_cleanup(self, delete=True): # A TemporaryDirectory is deleted when cleaned up dir = tempfile.mkdtemp() try: - d = self.do_create(dir=dir) + d = self.do_create(dir=dir, delete=delete) self.assertTrue(os.path.exists(d.name), "TemporaryDirectory %s does not exist" % d.name) d.cleanup() @@ -1321,6 +1322,15 @@ finally: os.rmdir(dir) + def test_explicit_cleanup(self): + self._explicit_cleanup() + + def test_explicit_cleanup_when_delete_false(self): + # Assure that directory is deleted even if delete is set to False + # during resource creation when explicit cleanup is called. + self._explicit_cleanup(delete=False) + + @support.skip_unless_symlink def test_cleanup_with_symlink_to_a_directory(self): # cleanup() should not follow symlinks to directories (issue #12464) @@ -1355,37 +1365,66 @@ finally: os.rmdir(dir) + def test_keep_directory_when_delete_false_on_collection(self): + try: + d = self.do_create(delete=False) + dir_name = d.name + del d + self.assertTrue(os.path.exists(dir_name), + "TemporaryDirectory %s does not exist" % dir_name) + finally: + support.rmtree(dir_name) + + def _del_on_shutdown(self, delete=True): + # A TemporaryDirectory may be cleaned up during shutdown unless delete + # is set to False + dir_name = "" + try: + with self.do_create(delete=delete) as dir: + dir_name = dir + for mod in ('builtins', 'os', 'shutil', 'sys', 'tempfile', 'warnings'): + code = """if True: + import builtins + import os + import shutil + import sys + import tempfile + import warnings + + tmp = tempfile.TemporaryDirectory(dir={dir!r}, delete={delete!r}) + sys.stdout.buffer.write(tmp.name.encode()) + + tmp2 = os.path.join(tmp.name, 'test_dir') + os.mkdir(tmp2) + with open(os.path.join(tmp2, "test.txt"), "w") as f: + f.write("Hello world!") + + {mod}.tmp = tmp + + warnings.filterwarnings("always", category=ResourceWarning) + """.format(dir=dir, delete=delete, mod=mod) + rc, out, err = script_helper.assert_python_ok("-c", code) + tmp_name = out.decode().strip() + if delete: + self.assertFalse(os.path.exists(tmp_name), + "TemporaryDirectory %s exists after cleanup" % tmp_name) + err = err.decode('utf-8', 'backslashreplace') + self.assertNotIn("Exception ", err) + self.assertIn("ResourceWarning: Implicitly cleaning up", err) + + # if delete is set to False then the directory should be + # kept after a shutdown. + else: + self.assertTrue(os.path.exists(tmp_name), + "TemporaryDirectory %s exists after cleanup" % tmp_name) + finally: + support.rmtree(dir_name) + def test_del_on_shutdown(self): - # A TemporaryDirectory may be cleaned up during shutdown - with self.do_create() as dir: - for mod in ('builtins', 'os', 'shutil', 'sys', 'tempfile', 'warnings'): - code = """if True: - import builtins - import os - import shutil - import sys - import tempfile - import warnings + self._del_on_shutdown() - tmp = tempfile.TemporaryDirectory(dir={dir!r}) - sys.stdout.buffer.write(tmp.name.encode()) - - tmp2 = os.path.join(tmp.name, 'test_dir') - os.mkdir(tmp2) - with open(os.path.join(tmp2, "test.txt"), "w") as f: - f.write("Hello world!") - - {mod}.tmp = tmp - - warnings.filterwarnings("always", category=ResourceWarning) - """.format(dir=dir, mod=mod) - rc, out, err = script_helper.assert_python_ok("-c", code) - tmp_name = out.decode().strip() - self.assertFalse(os.path.exists(tmp_name), - "TemporaryDirectory %s exists after cleanup" % tmp_name) - err = err.decode('utf-8', 'backslashreplace') - self.assertNotIn("Exception ", err) - self.assertIn("ResourceWarning: Implicitly cleaning up", err) + def test_keep_directory_on_shutdown_when_delete_false(self): + self._del_on_shutdown(delete=False) def test_exit_on_shutdown(self): # Issue #22427 @@ -1432,14 +1471,24 @@ d.cleanup() d.cleanup() - def test_context_manager(self): + def _use_context_manager(self, delete=True): # Can be used as a context manager - d = self.do_create() + d = self.do_create(delete=delete) with d as name: self.assertTrue(os.path.exists(name)) self.assertEqual(name, d.name) + return d.name + + def test_context_manager(self): + name = self._use_context_manager() self.assertFalse(os.path.exists(name)) + def test_context_manager_keep_directory_when_delete_false(self): + try: + name = self._use_context_manager(delete=False) + self.assertTrue(os.path.exists(name)) + finally: + support.rmtree(name) if __name__ == "__main__": unittest.main()