diff options
Diffstat (limited to 'Lib/tempfile.py')
| -rw-r--r-- | Lib/tempfile.py | 103 | 
1 files changed, 88 insertions, 15 deletions
| diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 049cdaa2c2..b28d91f87e 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -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 @@ -29,6 +29,8 @@ __all__ = [  # Imports. +import warnings as _warnings +import sys as _sys  import io as _io  import os as _os  import errno as _errno @@ -108,30 +110,19 @@ class _RandomNameSequence:      _RandomNameSequence is an iterator.""" -    characters = ("abcdefghijklmnopqrstuvwxyz" + -                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + -                  "0123456789_") +    characters = "abcdefghijklmnopqrstuvwxyz0123456789_"      def __init__(self): -        self.mutex = _allocate_lock()          self.rng = _Random() -        self.normcase = _os.path.normcase      def __iter__(self):          return self      def __next__(self): -        m = self.mutex          c = self.characters          choose = self.rng.choice - -        m.acquire() -        try: -            letters = [choose(c) for dummy in "123456"] -        finally: -            m.release() - -        return self.normcase(''.join(letters)) +        letters = [choose(c) for dummy in "123456"] +        return ''.join(letters)  def _candidate_tempdir_list():      """Generate a list of candidate temporary directories which @@ -613,3 +604,85 @@ 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._closed = False +        self.name = None # Handle mkdtemp throwing an exception +        self.name = mkdtemp(suffix, prefix, dir) + +    def __repr__(self): +        return "<{} {!r}>".format(self.__class__.__name__, self.name) + +    def __enter__(self): +        return self.name + +    def cleanup(self, _warn=False): +        if self.name and not self._closed: +            try: +                self._rmtree(self.name) +            except (TypeError, AttributeError) as ex: +                # Issue #10188: Emit a warning on stderr +                # if the directory could not be cleaned +                # up due to missing globals +                if "None" not in str(ex): +                    raise +                print("ERROR: {!r} while cleaning up {!r}".format(ex, self,), +                      file=_sys.stderr) +                return +            self._closed = True +            if _warn: +                self._warn("Implicitly cleaning up {!r}".format(self), +                           ResourceWarning) + +    def __exit__(self, exc, value, tb): +        self.cleanup() + +    def __del__(self): +        # Issue a ResourceWarning if implicit cleanup needed +        self.cleanup(_warn=True) + +    # 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 +    _warn = _warnings.warn + +    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 | 
