Issue 5178: Add tempfile.TemporaryDirectory (original patch by Neil Schemenauer)

This commit is contained in:
Nick Coghlan 2010-10-24 11:23:25 +00:00
parent d4519c14ca
commit 543af75961
5 changed files with 229 additions and 3 deletions

View File

@ -25,7 +25,7 @@ no longer necessary to use the global *tempdir* and *template* variables.
To maintain backward compatibility, the argument order is somewhat odd; it
is recommended to use keyword arguments for clarity.
The module defines the following user-callable functions:
The module defines the following user-callable items:
.. function:: TemporaryFile(mode='w+b', buffering=None, encoding=None, newline=None, suffix='', prefix='tmp', dir=None)
@ -83,6 +83,24 @@ The module defines the following user-callable functions:
used in a :keyword:`with` statement, just like a normal file.
.. function:: TemporaryDirectory(suffix='', prefix='tmp', dir=None)
This function creates a temporary directory using :func:`mkdtemp`
(the supplied arguments are passed directly to the underlying function).
The resulting object can be used as a context manager (see
:ref:`context-managers`). 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.
The directory name can be retrieved from the :attr:`name` member
of the returned object.
The directory can be explicitly cleaned up by calling the
:func:`cleanup` method.
.. versionadded:: 3.2
.. function:: mkstemp(suffix='', prefix='tmp', dir=None, text=False)
Creates a temporary file in the most secure manner possible. There are
@ -210,3 +228,36 @@ the appropriate function arguments, instead.
Return the filename prefix used to create temporary files. This does not
contain the directory component.
Examples
--------
Here are some examples of typical usage of the :mod:`tempfile` module::
>>> import tempfile
# create a temporary file and write some data to it
>>> fp = tempfile.TemporaryFile()
>>> fp.write('Hello world!')
# read data from file
>>> fp.seek(0)
>>> fp.read()
'Hello world!'
# close the file, it will be removed
>>> fp.close()
# create a temporary file using a context manager
>>> with tempfile.TemporaryFile() as fp:
... fp.write('Hello world!')
... fp.seek(0)
... fp.read()
'Hello world!'
>>>
# file is now closed and removed
# create a temporary directory using the context manager
>>> with tempfile.TemporaryDirectory() as tmpdirname:
... print 'created temporary directory', tmpdirname
>>>
# directory and contents have been removed

View File

@ -496,6 +496,13 @@ New, Improved, and Deprecated Modules
(Contributed by Giampaolo Rodolà; :issue:`6706`.)
* The :mod:`tempfile` module has a new context manager,
:class:`~tempfile.TemporaryDirectory` which provides easy deterministic
cleanup of temporary directories.
(Contributed by Neil Schemenauer and Nick Coghlan; :issue:`5178`.)
Multi-threading
===============

View File

@ -19,7 +19,7 @@ This module also provides some data items to the user:
__all__ = [
"NamedTemporaryFile", "TemporaryFile", # high level safe interfaces
"SpooledTemporaryFile",
"SpooledTemporaryFile", "TemporaryDirectory",
"mkstemp", "mkdtemp", # low level safe interfaces
"mktemp", # deprecated unsafe interface
"TMP_MAX", "gettempprefix", # constants
@ -613,3 +613,66 @@ class SpooledTemporaryFile:
def xreadlines(self, *args):
return self._file.xreadlines(*args)
class TemporaryDirectory(object):
"""Create and return a temporary directory. This has the same
behavior as mkdtemp but can be used as a context manager. For
example:
with TemporaryDirectory() as tmpdir:
...
Upon exiting the context, the directory and everthing contained
in it are removed.
"""
def __init__(self, suffix="", prefix=template, dir=None):
self.name = mkdtemp(suffix, prefix, dir)
self._closed = False
def __enter__(self):
return self.name
def cleanup(self):
if not self._closed:
self._rmtree(self.name)
self._closed = True
def __exit__(self, exc, value, tb):
self.cleanup()
__del__ = cleanup
# XXX (ncoghlan): The following code attempts to make
# this class tolerant of the module nulling out process
# that happens during CPython interpreter shutdown
# Alas, it doesn't actually manage it. See issue #10188
_listdir = staticmethod(_os.listdir)
_path_join = staticmethod(_os.path.join)
_isdir = staticmethod(_os.path.isdir)
_remove = staticmethod(_os.remove)
_rmdir = staticmethod(_os.rmdir)
_os_error = _os.error
def _rmtree(self, path):
# Essentially a stripped down version of shutil.rmtree. We can't
# use globals because they may be None'ed out at shutdown.
for name in self._listdir(path):
fullname = self._path_join(path, name)
try:
isdir = self._isdir(fullname)
except self._os_error:
isdir = False
if isdir:
self._rmtree(fullname)
else:
try:
self._remove(fullname)
except self._os_error:
pass
try:
self._rmdir(path)
except self._os_error:
pass

View File

@ -85,7 +85,8 @@ class test_exports(TC):
"gettempdir" : 1,
"tempdir" : 1,
"template" : 1,
"SpooledTemporaryFile" : 1
"SpooledTemporaryFile" : 1,
"TemporaryDirectory" : 1,
}
unexp = []
@ -889,6 +890,107 @@ class test_TemporaryFile(TC):
if tempfile.NamedTemporaryFile is not tempfile.TemporaryFile:
test_classes.append(test_TemporaryFile)
# Helper for test_del_on_shutdown
class NulledModules:
def __init__(self, *modules):
self.refs = [mod.__dict__ for mod in modules]
self.contents = [ref.copy() for ref in self.refs]
def __enter__(self):
for d in self.refs:
for key in d:
d[key] = None
def __exit__(self, *exc_info):
for d, c in zip(self.refs, self.contents):
d.clear()
d.update(c)
class test_TemporaryDirectory(TC):
"""Test TemporaryDirectory()."""
def do_create(self, dir=None, pre="", suf="", recurse=1):
if dir is None:
dir = tempfile.gettempdir()
try:
tmp = tempfile.TemporaryDirectory(dir=dir, prefix=pre, suffix=suf)
except:
self.failOnException("TemporaryDirectory")
self.nameCheck(tmp.name, dir, pre, suf)
# Create a subdirectory and some files
if recurse:
self.do_create(tmp.name, pre, suf, recurse-1)
with open(os.path.join(tmp.name, "test.txt"), "wb") as f:
f.write(b"Hello world!")
return tmp
def test_explicit_cleanup(self):
# A TemporaryDirectory is deleted when cleaned up
dir = tempfile.mkdtemp()
try:
d = self.do_create(dir=dir)
self.assertTrue(os.path.exists(d.name),
"TemporaryDirectory %s does not exist" % d.name)
d.cleanup()
self.assertFalse(os.path.exists(d.name),
"TemporaryDirectory %s exists after cleanup" % d.name)
finally:
os.rmdir(dir)
@support.cpython_only
def test_del_on_collection(self):
# A TemporaryDirectory is deleted when garbage collected
dir = tempfile.mkdtemp()
try:
d = self.do_create(dir=dir)
name = d.name
del d # Rely on refcounting to invoke __del__
self.assertFalse(os.path.exists(name),
"TemporaryDirectory %s exists after __del__" % name)
finally:
os.rmdir(dir)
@unittest.expectedFailure # See issue #10188
def test_del_on_shutdown(self):
# A TemporaryDirectory may be cleaned up during shutdown
# Make sure it works with the relevant modules nulled out
dir = tempfile.mkdtemp()
try:
d = self.do_create(dir=dir)
# Mimic the nulling out of modules that
# occurs during system shutdown
modules = [os, os.path]
if has_stat:
modules.append(stat)
with NulledModules(*modules):
d.cleanup()
self.assertFalse(os.path.exists(d.name),
"TemporaryDirectory %s exists after cleanup" % d.name)
finally:
os.rmdir(dir)
def test_multiple_close(self):
# Can be cleaned-up many times without error
d = self.do_create()
d.cleanup()
try:
d.cleanup()
d.cleanup()
except:
self.failOnException("cleanup")
def test_context_manager(self):
# Can be used as a context manager
d = self.do_create()
with d as name:
self.assertTrue(os.path.exists(name))
self.assertEqual(name, d.name)
self.assertFalse(os.path.exists(name))
test_classes.append(test_TemporaryDirectory)
def test_main():
support.run_unittest(*test_classes)

View File

@ -51,6 +51,9 @@ Core and Builtins
Library
-------
- Issue #5178: Added tempfile.TemporaryDirectory class that can be used
as a context manager.
- Issue #1349106: Generator (and BytesGenerator) flatten method and Header
encode method now support a 'linesep' argument.