mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +00:00
Patch #1630118: add a SpooledTemporaryFile class to tempfile.
This commit is contained in:
parent
d9dbe72056
commit
a8785cc26a
5 changed files with 237 additions and 1 deletions
|
@ -67,6 +67,23 @@ it is closed.
|
||||||
\versionadded[The \var{delete} parameter]{2.6}
|
\versionadded[The \var{delete} parameter]{2.6}
|
||||||
\end{funcdesc}
|
\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{,
|
\begin{funcdesc}{mkstemp}{\optional{suffix\optional{,
|
||||||
prefix\optional{, dir\optional{, text}}}}}
|
prefix\optional{, dir\optional{, text}}}}}
|
||||||
Creates a temporary file in the most secure manner possible. There
|
Creates a temporary file in the most secure manner possible. There
|
||||||
|
|
114
Lib/tempfile.py
114
Lib/tempfile.py
|
@ -19,6 +19,7 @@ This module also provides some data items to the user:
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"NamedTemporaryFile", "TemporaryFile", # high level safe interfaces
|
"NamedTemporaryFile", "TemporaryFile", # high level safe interfaces
|
||||||
|
"SpooledTemporaryFile",
|
||||||
"mkstemp", "mkdtemp", # low level safe interfaces
|
"mkstemp", "mkdtemp", # low level safe interfaces
|
||||||
"mktemp", # deprecated unsafe interface
|
"mktemp", # deprecated unsafe interface
|
||||||
"TMP_MAX", "gettempprefix", # constants
|
"TMP_MAX", "gettempprefix", # constants
|
||||||
|
@ -36,6 +37,11 @@ if _os.name == 'mac':
|
||||||
import Carbon.Folder as _Folder
|
import Carbon.Folder as _Folder
|
||||||
import Carbon.Folders as _Folders
|
import Carbon.Folders as _Folders
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO as _StringIO
|
||||||
|
except:
|
||||||
|
from StringIO import StringIO as _StringIO
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import fcntl as _fcntl
|
import fcntl as _fcntl
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -473,3 +479,111 @@ else:
|
||||||
except:
|
except:
|
||||||
_os.close(fd)
|
_os.close(fd)
|
||||||
raise
|
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)
|
||||||
|
|
|
@ -81,7 +81,8 @@ class test_exports(TC):
|
||||||
"gettempprefix" : 1,
|
"gettempprefix" : 1,
|
||||||
"gettempdir" : 1,
|
"gettempdir" : 1,
|
||||||
"tempdir" : 1,
|
"tempdir" : 1,
|
||||||
"template" : 1
|
"template" : 1,
|
||||||
|
"SpooledTemporaryFile" : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
unexp = []
|
unexp = []
|
||||||
|
@ -632,6 +633,107 @@ class test_NamedTemporaryFile(TC):
|
||||||
|
|
||||||
test_classes.append(test_NamedTemporaryFile)
|
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):
|
class test_TemporaryFile(TC):
|
||||||
"""Test TemporaryFile()."""
|
"""Test TemporaryFile()."""
|
||||||
|
|
|
@ -442,6 +442,7 @@ Chad Miller
|
||||||
Damien Miller
|
Damien Miller
|
||||||
Roman Milner
|
Roman Milner
|
||||||
Dom Mitchell
|
Dom Mitchell
|
||||||
|
Dustin J. Mitchell
|
||||||
Doug Moen
|
Doug Moen
|
||||||
Paul Moore
|
Paul Moore
|
||||||
The Dragon De Monsyne
|
The Dragon De Monsyne
|
||||||
|
|
|
@ -187,6 +187,8 @@ Core and builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Patch #1630118: add a SpooledTemporaryFile class to tempfile.py.
|
||||||
|
|
||||||
- Patch #1273829: os.walk() now has a "followlinks" parameter. If set to
|
- Patch #1273829: os.walk() now has a "followlinks" parameter. If set to
|
||||||
True (which is not the default), it visits symlinks pointing to
|
True (which is not the default), it visits symlinks pointing to
|
||||||
directories.
|
directories.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue