[Python-checkins] r54439 - in python/trunk: Doc/lib/libtempfile.tex Lib/tempfile.py Lib/test/test_tempfile.py Misc/ACKS Misc/NEWS

collin.winter python-checkins at python.org
Mon Mar 19 19:52:14 CET 2007


Author: collin.winter
Date: Mon Mar 19 19:52:08 2007
New Revision: 54439

Modified:
   python/trunk/Doc/lib/libtempfile.tex
   python/trunk/Lib/tempfile.py
   python/trunk/Lib/test/test_tempfile.py
   python/trunk/Misc/ACKS
   python/trunk/Misc/NEWS
Log:
Patch #1630118: add a SpooledTemporaryFile class to tempfile.

Modified: python/trunk/Doc/lib/libtempfile.tex
==============================================================================
--- python/trunk/Doc/lib/libtempfile.tex	(original)
+++ python/trunk/Doc/lib/libtempfile.tex	Mon Mar 19 19:52:08 2007
@@ -67,6 +67,23 @@
 \versionadded[The \var{delete} parameter]{2.6}
 \end{funcdesc}
 
+\begin{funcdesc}{SpooledTemporaryFile}{\optional{max\_size=\code{0},
+                                \optional{mode=\code{'w+b'}\optional{,
+                                bufsize=\code{-1}\optional{,
+                                suffix\optional{, prefix\optional{,
+                                dir}}}}}}}
+This function operates exactly as \function{TemporaryFile()} does,
+except that data is spooled in memory until the file size exceeds
+\var{max_size}, or until the file's \function{fileno()} method is
+called, at which point the contents are written to disk and operation
+proceeds as with \function{TemporaryFile()}.
+
+The resulting file has one additional method, \function{rollover()},
+which causes the file to roll over to an on-disk file regardless of
+its size.
+\versionadded{2.6}
+\end{funcdesc}
+
 \begin{funcdesc}{mkstemp}{\optional{suffix\optional{,
                           prefix\optional{, dir\optional{, text}}}}}
 Creates a temporary file in the most secure manner possible.  There

Modified: python/trunk/Lib/tempfile.py
==============================================================================
--- python/trunk/Lib/tempfile.py	(original)
+++ python/trunk/Lib/tempfile.py	Mon Mar 19 19:52:08 2007
@@ -19,6 +19,7 @@
 
 __all__ = [
     "NamedTemporaryFile", "TemporaryFile", # high level safe interfaces
+    "SpooledTemporaryFile",
     "mkstemp", "mkdtemp",                  # low level safe interfaces
     "mktemp",                              # deprecated unsafe interface
     "TMP_MAX", "gettempprefix",            # constants
@@ -37,6 +38,11 @@
     import Carbon.Folders as _Folders
 
 try:
+  from cStringIO import StringIO as _StringIO
+except:
+  from StringIO import StringIO as _StringIO
+
+try:
     import fcntl as _fcntl
 except ImportError:
     def _set_cloexec(fd):
@@ -473,3 +479,111 @@
         except:
             _os.close(fd)
             raise
+
+class SpooledTemporaryFile:
+    """Temporary file wrapper, specialized to switch from
+    StringIO to a real file when it exceeds a certain size or
+    when a fileno is needed.
+    """
+    _rolled = False
+
+    def __init__(self, max_size=0, mode='w+b', bufsize=-1,
+                 suffix="", prefix=template, dir=None):
+        self._file = _StringIO()
+        self._max_size = max_size
+        self._rolled = False
+        self._TemporaryFileArgs = (mode, bufsize, suffix, prefix, dir)
+
+    def _check(self, file):
+        if self._rolled: return
+        max_size = self._max_size
+        if max_size and file.tell() > max_size:
+            self.rollover()
+
+    def rollover(self):
+        if self._rolled: return
+        file = self._file
+        newfile = self._file = TemporaryFile(*self._TemporaryFileArgs)
+        del self._TemporaryFileArgs
+
+        newfile.write(file.getvalue())
+        newfile.seek(file.tell(), 0)
+
+        self._rolled = True
+        
+    # file protocol
+    def __iter__(self):
+        return self._file.__iter__()
+
+    def close(self):
+        self._file.close()
+
+    @property
+    def closed(self):
+        return self._file.closed
+
+    @property
+    def encoding(self):
+        return self._file.encoding
+
+    def fileno(self):
+        self.rollover()
+        return self._file.fileno()
+
+    def flush(self):
+        self._file.flush()
+
+    def isatty(self):
+        return self._file.isatty()
+
+    @property
+    def mode(self):
+        return self._file.mode
+
+    @property
+    def name(self):
+        return self._file.name
+
+    @property
+    def newlines(self):
+        return self._file.newlines
+
+    def next(self):
+        return self._file.next
+
+    def read(self, *args):
+        return self._file.read(*args)
+
+    def readline(self, *args):
+        return self._file.readline(*args)
+
+    def readlines(self, *args):
+        return self._file.readlines(*args)
+
+    def seek(self, *args):
+        self._file.seek(*args)
+
+    @property
+    def softspace(self):
+        return self._file.softspace
+
+    def tell(self):
+        return self._file.tell()
+
+    def truncate(self):
+        self._file.truncate()
+
+    def write(self, s):
+        file = self._file
+        rv = file.write(s)
+        self._check(file)
+        return rv
+
+    def writelines(self, iterable):
+        file = self._file
+        rv = file.writelines(iterable)
+        self._check(file)
+        return rv
+
+    def xreadlines(self, *args):
+        return self._file.xreadlines(*args)

Modified: python/trunk/Lib/test/test_tempfile.py
==============================================================================
--- python/trunk/Lib/test/test_tempfile.py	(original)
+++ python/trunk/Lib/test/test_tempfile.py	Mon Mar 19 19:52:08 2007
@@ -81,7 +81,8 @@
             "gettempprefix" : 1,
             "gettempdir" : 1,
             "tempdir" : 1,
-            "template" : 1
+            "template" : 1,
+            "SpooledTemporaryFile" : 1
         }
 
         unexp = []
@@ -632,6 +633,107 @@
 
 test_classes.append(test_NamedTemporaryFile)
 
+class test_SpooledTemporaryFile(TC):
+    """Test SpooledTemporaryFile()."""
+
+    def do_create(self, max_size=0, dir=None, pre="", suf=""):
+        if dir is None:
+            dir = tempfile.gettempdir()
+        try:
+            file = tempfile.SpooledTemporaryFile(max_size=max_size, dir=dir, prefix=pre, suffix=suf)
+        except:
+            self.failOnException("SpooledTemporaryFile")
+
+        return file
+
+
+    def test_basic(self):
+        # SpooledTemporaryFile can create files
+        f = self.do_create()
+        self.failIf(f._rolled)
+        f = self.do_create(max_size=100, pre="a", suf=".txt")
+        self.failIf(f._rolled)
+
+    def test_del_on_close(self):
+        # A SpooledTemporaryFile is deleted when closed
+        dir = tempfile.mkdtemp()
+        try:
+            f = tempfile.SpooledTemporaryFile(max_size=10, dir=dir)
+            self.failIf(f._rolled)
+            f.write('blat ' * 5)
+            self.failUnless(f._rolled)
+            filename = f.name
+            f.close()
+            self.failIf(os.path.exists(filename),
+                        "SpooledTemporaryFile %s exists after close" % filename)
+        finally:
+            os.rmdir(dir)
+
+    def test_rewrite_small(self):
+        # A SpooledTemporaryFile can be written to multiple within the max_size
+        f = self.do_create(max_size=30)
+        self.failIf(f._rolled)
+        for i in range(5):
+            f.seek(0, 0)
+            f.write('x' * 20)
+        self.failIf(f._rolled)
+
+    def test_write_sequential(self):
+        # A SpooledTemporaryFile should hold exactly max_size bytes, and roll
+        # over afterward
+        f = self.do_create(max_size=30)
+        self.failIf(f._rolled)
+        f.write('x' * 20)
+        self.failIf(f._rolled)
+        f.write('x' * 10)
+        self.failIf(f._rolled)
+        f.write('x')
+        self.failUnless(f._rolled)
+
+    def test_sparse(self):
+        # A SpooledTemporaryFile that is written late in the file will extend
+        # when that occurs
+        f = self.do_create(max_size=30)
+        self.failIf(f._rolled)
+        f.seek(100, 0)
+        self.failIf(f._rolled)
+        f.write('x')
+        self.failUnless(f._rolled)
+
+    def test_fileno(self):
+        # A SpooledTemporaryFile should roll over to a real file on fileno()
+        f = self.do_create(max_size=30)
+        self.failIf(f._rolled)
+        self.failUnless(f.fileno() > 0)
+        self.failUnless(f._rolled)
+
+    def test_multiple_close(self):
+        # A SpooledTemporaryFile can be closed many times without error
+        f = tempfile.SpooledTemporaryFile()
+        f.write('abc\n')
+        f.close()
+        try:
+            f.close()
+            f.close()
+        except:
+            self.failOnException("close")
+
+    def test_bound_methods(self):
+        # It should be OK to steal a bound method from a SpooledTemporaryFile
+        # and use it independently; when the file rolls over, those bound
+        # methods should continue to function
+        f = self.do_create(max_size=30)
+        read = f.read
+        write = f.write
+        seek = f.seek
+
+        write("a" * 35)
+        write("b" * 35)
+        seek(0, 0)
+        self.failUnless(read(70) == 'a'*35 + 'b'*35)
+
+test_classes.append(test_SpooledTemporaryFile)
+
 
 class test_TemporaryFile(TC):
     """Test TemporaryFile()."""

Modified: python/trunk/Misc/ACKS
==============================================================================
--- python/trunk/Misc/ACKS	(original)
+++ python/trunk/Misc/ACKS	Mon Mar 19 19:52:08 2007
@@ -442,6 +442,7 @@
 Damien Miller
 Roman Milner
 Dom Mitchell
+Dustin J. Mitchell
 Doug Moen
 Paul Moore
 The Dragon De Monsyne

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Mon Mar 19 19:52:08 2007
@@ -187,6 +187,8 @@
 Library
 -------
 
+- Patch #1630118: add a SpooledTemporaryFile class to tempfile.py.
+
 - Patch #1273829: os.walk() now has a "followlinks" parameter. If set to
   True (which is not the default), it visits symlinks pointing to
   directories.


More information about the Python-checkins mailing list