Permalink
Browse files

begin adding support for _truncate() method

  • Loading branch information...
1 parent 10abb1a commit 9320b6d048f92b9874d0656c78c27cc6825e4721 @rfk committed Sep 27, 2010
View
@@ -1,4 +1,10 @@
+Version 0.4.0
+
+ * Add support for truncate() via a primitive _truncate() method
+ * Increase default buffer size to 64K (was previous a measly 1K)
+ * Remove old class names, along with their deprecation warnings
+
Version 0.3.7
* Make __del__ more robust in the case of incomplete initialisation
View
@@ -7,8 +7,8 @@ objects that provide a rich file-like interface, including reading, writing,
seeking and iteration. It also provides a number of useful classes built on
top of this functionality.
-The main class is FileLikeBase, which implements the entire file-like
-interface on top of primitive _read(), _write(), _seek() and _tell() methods.
+The main class is FileLikeBase, which implements the entire file-like interface
+on top of primitive _read(), _write(), _seek(), _tell() and _truncate() methods.
Subclasses may implement any or all of these methods to obtain the related
higher-level file behaviors.
View
@@ -1,2 +1,3 @@
+
View
@@ -26,8 +26,8 @@
seeking and iteration. It also provides a number of useful classes built on
top of this functionality.
-The main class is FileLikeBase, which implements the entire file-like
-interface on top of primitive _read(), _write(), _seek() and _tell() methods.
+The main class is FileLikeBase, which implements the entire file-like interface
+on top of primitive _read(), _write(), _seek(), _tell() and _truncate() methods.
Subclasses may implement any or all of these methods to obtain the related
higher-level file behaviors.
@@ -89,10 +89,11 @@
"""
__ver_major__ = 0
-__ver_minor__ = 3
-__ver_patch__ = 7
+__ver_minor__ = 4
+__ver_patch__ = 0
__ver_sub__ = ""
-__version__ = "%d.%d.%d%s" % (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__)
+__ver_tuple__ = (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__)
+__version__ = "%d.%d.%d%s" % __ver_tuple__
from StringIO import StringIO
@@ -107,22 +108,25 @@ class NotWritableError(IOError):
pass
class NotSeekableError(IOError):
pass
+class NotTruncatableError(IOError):
+ pass
class FileLikeBase(object):
"""Base class for implementing file-like objects.
This class takes a lot of the legwork out of writing file-like objects
with a rich interface. It implements the higher-level file-like
- methods on top of four primitive methods: _read, _write, _seek and _tell.
- See their docstrings for precise details on how these methods behave.
+ methods on top of five primitive methods: _read, _write, _seek, _tell and
+ _truncate. See their docstrings for precise details on how these methods
+ behave.
Subclasses then need only implement some subset of these methods for
rich file-like interface compatability. They may of course override
other methods as desired.
- The class is missing the following attributes, which dont really make
- sense for anything but real files:
+ The class is missing the following attributes and methods, which dont
+ really make sense for anything but real files:
* fileno()
* isatty()
@@ -131,11 +135,6 @@ class FileLikeBase(object):
* name
* newlines
- It is also missing the following methods purely because of a lack of
- code, and they may appear at some point in the future:
-
- * truncate()
-
Unlike standard file objects, all read methods share the same buffer
and so can be freely mixed (e.g. read(), readline(), next(), ...).
@@ -162,7 +161,7 @@ class FileLikeBase(object):
"""
- def __init__(self,bufsize=1024):
+ def __init__(self,bufsize=1024*64):
"""FileLikeBase Constructor.
The optional argument 'bufsize' specifies the number of bytes to
@@ -285,6 +284,21 @@ def next(self):
def __iter__(self):
return self
+ def truncate(self,size=None):
+ """Truncate the file to the given size.
+
+ If <size> is not specified or is None, the current file position is
+ used. Note that this method may fail at runtime if the underlying
+ filelike object is not truncatable.
+ """
+ if "-" in getattr(self,"mode",""):
+ raise NotTruncatableError("File is not seekable, can't truncate.")
+ if self._wbuffer:
+ self.flush()
+ if size is None:
+ size = self.tell()
+ self._truncate(size)
+
def seek(self,offset,whence=0):
"""Move the internal file pointer to the given location."""
if whence > 2 or whence < 0:
@@ -557,7 +571,7 @@ def _seek(self,offset,whence):
the start of the file, i.e. offset=0 and whence=0. If more
complex seeks are difficult to implement then it may raise
NotImplementedError to have them simulated (inefficiently) by
- the higher-level mahinery of this class.
+ the higher-level machinery of this class.
"""
raise NotSeekableError("Object not seekable")
@@ -574,6 +588,19 @@ def _tell(self):
"""
raise NotSeekableError("Object not seekable")
+ def _truncate(self,size):
+ """Truncate the file's size to <size>.
+
+ This method must be implemented by subclasses that wish to be
+ truncatable. It must truncate the file to exactly the given size
+ or fail with an IOError.
+
+ Note that <size> will never be None; if it was not specified by the
+ user then it is calculated as the file's apparent position (which may
+ be different to its actual position due to buffering).
+ """
+ raise NotTruncatableError("Object not truncatable")
+
class Opener(object):
"""Class allowing clever opening of files.
View
@@ -313,17 +313,26 @@ def test_tofilelike_string(self):
class Test_Docs(unittest.TestCase):
"""Unittests for our documentation."""
- def test_readme(self):
- """Check that README.txt is up-to-date."""
- import os
- import difflib
- readme = os.path.join(os.path.dirname(__file__),"..","README.txt")
- if os.path.exists(readme):
- diff = difflib.unified_diff(open(readme).readlines(),filelike.__doc__.splitlines(True))
- diff = "".join(diff)
- if diff:
- print diff
- raise RuntimeError
+ def test_README(self):
+ """Ensure that the README is in sync with the docstring.
+
+ This test should always pass; if the README is out of sync it just
+ updates it with the contents of filelike.__doc__.
+ """
+ dirname = os.path.dirname
+ readme = os.path.join(dirname(dirname(__file__)),"README.txt")
+ if not os.path.isfile(readme):
+ f = open(readme,"wb")
+ f.write(filelike.__doc__.encode())
+ f.close()
+ else:
+ f = open(readme,"rb")
+ if f.read() != filelike.__doc__:
+ f.close()
+ f = open(readme,"wb")
+ f.write(filelike.__doc__.encode())
+ f.close()
+
def build_test_suite():
@@ -160,8 +160,10 @@ def _seek(self,offset,whence):
def _tell(self):
return self._fileobj.tell()
-## Import the various classes from our sub-modules,
-## and mark old names as deprecated.
+ def _truncate(self,size):
+ return self._fileobj.truncate(size)
+
+## Import the various classes from our sub-modules.
from filelike.wrappers.debug import Debug
@@ -97,7 +97,7 @@ def close(self):
def _read(self,sizehint=-1):
# First return any data available from the buffer.
# Since we don't flush the buffer after every write, certain OSes
- # (guess which!) will happy read junk data from the end of it.
+ # (guess which!) will happily read junk data from the end of it.
# Instead, we explicitly read only up to self._in_pos.
if not self._in_eof:
buffered_size = self._in_pos - self._buffer.tell()
@@ -146,6 +146,13 @@ def _seek(self,offset,whence):
def _tell(self):
return self._buffer.tell()
+
+ def _truncate(self,size):
+ if self._check_mode("r") and not self._in_eof:
+ if size > self._in_pos:
+ self._read_rest()
+ self._in_eof = True
+ self._buffer.truncate(size)
def _read_rest(self):
"""Read the rest of the input stream."""
@@ -23,7 +23,7 @@
This module provides the filelike wrappers 'BZip2' and 'UnBZip2' for dealing
with files compressed in bz2 format. It also provides some base classes for
-each building other compression wrappers.
+building other compression wrappers.
"""
@@ -71,6 +71,12 @@ def _tell(self):
self._debug("TELLED",pos)
return pos
+ def _truncate(self,size):
+ self._debug("TRUNCATING",size)
+ self._fileobj.truncate(size)
+ self._debug("TRUNCATED")
+ return pos
+
def flush(self):
self._debug("FLUSHING")
self._fileobj.flush()
@@ -40,7 +40,7 @@ class FixedBlockSize(FileWrapper):
example, to write data to a cipher function without manually
chunking text to match the cipher's block size.
- No padding is added to the file is its length is not a multiple
+ No padding is added to the file if its length is not a multiple
of the blocksize. This might cause things to fail when this file
is flushed or closed, since an incorrectly-sized string could be
given in this case.
@@ -98,7 +98,7 @@ def _write(self,data,flushing=False):
self.seek(padstart - self.blocksize,1)
return ""
- # TODO: primative implementation of relative seek
+ # TODO: primitive implementation of relative seek
def _seek(self,offset,whence):
"""Absolute seek, repecting block boundaries.
@@ -148,7 +148,6 @@ def _seek(self,offset,whence):
if whence > 0:
# TODO: implementing these shouldn't be that hard...
raise NotImplementedError
- print "SEEK:", offset, whence
self._fileobj.seek(0,0)
self._pad_unread = ""
self._pad_read = ""
@@ -184,6 +183,23 @@ def _seek(self,offset,whence):
def _tell(self):
return self._fileobj.tell() + len(self._pad_read)
+ def _truncate(self,size):
+ if size % self.blocksize != 0:
+ msg = "PadToBlockSize must be truncated to a multiple of " \
+ "the blocksize"
+ raise IOError(msg)
+ pos = self._fileobj.tell()
+ if size <= pos:
+ self._pad_read = self._pad_unread = None
+ else:
+ self._fileobj.seek(0,2)
+ fsize = self._fileobj.tell()
+ self._fileobj.seek(pos,0)
+ if size > fsize:
+ msg = "PadToBlockSize can't truncate past end of file"
+ raise IOError(msg)
+ self._fileobj.truncate(size)
+
class UnPadToBlockSize(FileWrapper):
"""Class removing block-size padding from a file.
@@ -305,3 +321,7 @@ def _seek(self,offset,whence):
def _tell(self):
return self._fileobj.tell() - len(self._pad_seen)
+ def _truncate(self,size):
+ msg = "UnPadToBlockSize objects are not truncatable"
+ raise filelike.NotTruncatableError(msg)
+
@@ -149,4 +149,8 @@ def _seek(self,offset,whence):
def _tell(self):
"""Get position of file pointer."""
return self._fileobj.tell() - self.start
+
+ def _truncate(self,size):
+ msg = "File slices are not truncatable"
+ raise filelike.NotTruncatableError(msg)
@@ -158,6 +158,10 @@ def _seek(self,offset,whence):
if hasattr(self._wfunc,"reset"):
self._wfunc.reset()
+ def _truncate(self,size):
+ msg = "Translate wrapper is not truncatable"
+ raise filelike.NotTruncatableError(msg)
+
class BytewiseTranslate(FileWrapper):
"""Class implementing a bytewise translation on a file's contents.
@@ -219,6 +223,6 @@ def _write(self,data,flushing=False):
"""Write the given data to the file."""
self._fileobj.write(self._wfunc(data))
- # Since this is a bytewise translation, the default seek() and tell()
- # will do what we want - simply move the underlying file object.
+ # Since this is a bytewise translation, the default implementations of
+ # _seek(), _tell() and _truncate() will do what we want.

0 comments on commit 9320b6d

Please sign in to comment.