summaryrefslogtreecommitdiff
path: root/paramiko/file.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/file.py')
-rw-r--r--paramiko/file.py220
1 files changed, 114 insertions, 106 deletions
diff --git a/paramiko/file.py b/paramiko/file.py
index 5fd81cf..2238f0b 100644
--- a/paramiko/file.py
+++ b/paramiko/file.py
@@ -15,17 +15,14 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-
-"""
-BufferedFile.
-"""
-
-from cStringIO import StringIO
+from paramiko.common import linefeed_byte_value, crlf, cr_byte, linefeed_byte, \
+ cr_byte_value
+from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types
class BufferedFile (object):
"""
- Reusable base class to implement python-style file buffering around a
+ Reusable base class to implement Python-style file buffering around a
simpler stream.
"""
@@ -47,8 +44,8 @@ class BufferedFile (object):
self.newlines = None
self._flags = 0
self._bufsize = self._DEFAULT_BUFSIZE
- self._wbuffer = StringIO()
- self._rbuffer = ''
+ self._wbuffer = BytesIO()
+ self._rbuffer = bytes()
self._at_trailing_cr = False
self._closed = False
# pos - position within the file, according to the user
@@ -67,10 +64,7 @@ class BufferedFile (object):
file. This iterator happens to return the file itself, since a file is
its own iterator.
- @raise ValueError: if the file is closed.
-
- @return: an interator.
- @rtype: iterator
+ :raises ValueError: if the file is closed.
"""
if self._closed:
raise ValueError('I/O operation on closed file')
@@ -89,36 +83,57 @@ class BufferedFile (object):
buffering is not turned on.
"""
self._write_all(self._wbuffer.getvalue())
- self._wbuffer = StringIO()
+ self._wbuffer = BytesIO()
return
- def next(self):
- """
- Returns the next line from the input, or raises L{StopIteration} when
- EOF is hit. Unlike python file objects, it's okay to mix calls to
- C{next} and L{readline}.
+ if PY2:
+ def next(self):
+ """
+ Returns the next line from the input, or raises
+ `~exceptions.StopIteration` when EOF is hit. Unlike Python file
+ objects, it's okay to mix calls to `next` and `readline`.
- @raise StopIteration: when the end of the file is reached.
+ :raises StopIteration: when the end of the file is reached.
- @return: a line read from the file.
- @rtype: str
- """
- line = self.readline()
- if not line:
- raise StopIteration
- return line
+ :return: a line (`str`) read from the file.
+ """
+ line = self.readline()
+ if not line:
+ raise StopIteration
+ return line
+ else:
+ def __next__(self):
+ """
+ Returns the next line from the input, or raises L{StopIteration} when
+ EOF is hit. Unlike python file objects, it's okay to mix calls to
+ C{next} and L{readline}.
+
+ @raise StopIteration: when the end of the file is reached.
+
+ @return: a line read from the file.
+ @rtype: str
+ """
+ line = self.readline()
+ if not line:
+ raise StopIteration
+ return line
def read(self, size=None):
"""
- Read at most C{size} bytes from the file (less if we hit the end of the
- file first). If the C{size} argument is negative or omitted, read all
+ Read at most ``size`` bytes from the file (less if we hit the end of the
+ file first). If the ``size`` argument is negative or omitted, read all
the remaining data in the file.
- @param size: maximum number of bytes to read
- @type size: int
- @return: data read from the file, or an empty string if EOF was
+ .. note::
+ ``'b'`` mode flag is ignored (``self.FLAG_BINARY`` in
+ ``self._flags``), because SSH treats all files as binary, since we
+ have no idea what encoding the file is in, or even if the file is
+ text data.
+
+ :param int size: maximum number of bytes to read
+ :return:
+ data read from the file (as bytes), or an empty string if EOF was
encountered immediately
- @rtype: str
"""
if self._closed:
raise IOError('File is closed')
@@ -127,7 +142,7 @@ class BufferedFile (object):
if (size is None) or (size < 0):
# go for broke
result = self._rbuffer
- self._rbuffer = ''
+ self._rbuffer = bytes()
self._pos += len(result)
while True:
try:
@@ -139,12 +154,12 @@ class BufferedFile (object):
result += new_data
self._realpos += len(new_data)
self._pos += len(new_data)
- return result
+ return result
if size <= len(self._rbuffer):
result = self._rbuffer[:size]
self._rbuffer = self._rbuffer[size:]
self._pos += len(result)
- return result
+ return result
while len(self._rbuffer) < size:
read_size = size - len(self._rbuffer)
if self._flags & self.FLAG_BUFFERED:
@@ -160,7 +175,7 @@ class BufferedFile (object):
result = self._rbuffer[:size]
self._rbuffer = self._rbuffer[size:]
self._pos += len(result)
- return result
+ return result
def readline(self, size=None):
"""
@@ -171,14 +186,18 @@ class BufferedFile (object):
incomplete line may be returned. An empty string is returned only when
EOF is encountered immediately.
- @note: Unlike stdio's C{fgets()}, the returned string contains null
- characters (C{'\\0'}) if they occurred in the input.
+ .. note::
+ Unlike stdio's ``fgets``, the returned string contains null
+ characters (``'\\0'``) if they occurred in the input.
- @param size: maximum length of returned string.
- @type size: int
- @return: next line of the file, or an empty string if the end of the
+ :param int size: maximum length of returned string.
+ :return:
+ next line of the file, or an empty string if the end of the
file has been reached.
- @rtype: str
+
+ If the file was opened in binary (``'b'``) mode: bytes are returned
+ Else: the encoding of the file is assumed to be UTF-8 and character
+ strings (`str`) are returned
"""
# it's almost silly how complex this function is.
if self._closed:
@@ -190,11 +209,11 @@ class BufferedFile (object):
if self._at_trailing_cr and (self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0):
# edge case: the newline may be '\r\n' and we may have read
# only the first '\r' last time.
- if line[0] == '\n':
+ if line[0] == linefeed_byte_value:
line = line[1:]
- self._record_newline('\r\n')
+ self._record_newline(crlf)
else:
- self._record_newline('\r')
+ self._record_newline(cr_byte)
self._at_trailing_cr = False
# check size before looking for a linefeed, in case we already have
# enough.
@@ -204,84 +223,82 @@ class BufferedFile (object):
self._rbuffer = line[size:]
line = line[:size]
self._pos += len(line)
- return line
+ return line if self._flags & self.FLAG_BINARY else u(line)
n = size - len(line)
else:
n = self._bufsize
- if ('\n' in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)):
+ if (linefeed_byte in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (cr_byte in line)):
break
try:
new_data = self._read(n)
except EOFError:
new_data = None
if (new_data is None) or (len(new_data) == 0):
- self._rbuffer = ''
+ self._rbuffer = bytes()
self._pos += len(line)
- return line
+ return line if self._flags & self.FLAG_BINARY else u(line)
line += new_data
self._realpos += len(new_data)
# find the newline
- pos = line.find('\n')
+ pos = line.find(linefeed_byte)
if self._flags & self.FLAG_UNIVERSAL_NEWLINE:
- rpos = line.find('\r')
- if (rpos >= 0) and ((rpos < pos) or (pos < 0)):
+ rpos = line.find(cr_byte)
+ if (rpos >= 0) and (rpos < pos or pos < 0):
pos = rpos
xpos = pos + 1
- if (line[pos] == '\r') and (xpos < len(line)) and (line[xpos] == '\n'):
+ if (line[pos] == cr_byte_value) and (xpos < len(line)) and (line[xpos] == linefeed_byte_value):
xpos += 1
self._rbuffer = line[xpos:]
lf = line[pos:xpos]
- line = line[:pos] + '\n'
- if (len(self._rbuffer) == 0) and (lf == '\r'):
+ line = line[:pos] + linefeed_byte
+ if (len(self._rbuffer) == 0) and (lf == cr_byte):
# we could read the line up to a '\r' and there could still be a
# '\n' following that we read next time. note that and eat it.
self._at_trailing_cr = True
else:
self._record_newline(lf)
self._pos += len(line)
- return line
+ return line if self._flags & self.FLAG_BINARY else u(line)
def readlines(self, sizehint=None):
"""
- Read all remaining lines using L{readline} and return them as a list.
- If the optional C{sizehint} argument is present, instead of reading up
+ Read all remaining lines using `readline` and return them as a list.
+ If the optional ``sizehint`` argument is present, instead of reading up
to EOF, whole lines totalling approximately sizehint bytes (possibly
after rounding up to an internal buffer size) are read.
- @param sizehint: desired maximum number of bytes to read.
- @type sizehint: int
- @return: list of lines read from the file.
- @rtype: list
+ :param int sizehint: desired maximum number of bytes to read.
+ :return: `list` of lines read from the file.
"""
lines = []
- bytes = 0
+ byte_count = 0
while True:
line = self.readline()
if len(line) == 0:
break
lines.append(line)
- bytes += len(line)
- if (sizehint is not None) and (bytes >= sizehint):
+ byte_count += len(line)
+ if (sizehint is not None) and (byte_count >= sizehint):
break
return lines
def seek(self, offset, whence=0):
"""
- Set the file's current position, like stdio's C{fseek}. Not all file
+ Set the file's current position, like stdio's ``fseek``. Not all file
objects support seeking.
- @note: If a file is opened in append mode (C{'a'} or C{'a+'}), any seek
+ .. note::
+ If a file is opened in append mode (``'a'`` or ``'a+'``), any seek
operations will be undone at the next write (as the file position
will move back to the end of the file).
- @param offset: position to move to within the file, relative to
- C{whence}.
- @type offset: int
- @param whence: type of movement: 0 = absolute; 1 = relative to the
- current position; 2 = relative to the end of the file.
- @type whence: int
-
- @raise IOError: if the file doesn't support random access.
+ :param int offset:
+ position to move to within the file, relative to ``whence``.
+ :param int whence:
+ type of movement: 0 = absolute; 1 = relative to the current
+ position; 2 = relative to the end of the file.
+
+ :raises IOError: if the file doesn't support random access.
"""
raise IOError('File does not support seeking.')
@@ -291,21 +308,20 @@ class BufferedFile (object):
useful if the underlying file doesn't support random access, or was
opened in append mode.
- @return: file position (in bytes).
- @rtype: int
+ :return: file position (`number <int>` of bytes).
"""
return self._pos
def write(self, data):
"""
- Write data to the file. If write buffering is on (C{bufsize} was
+ Write data to the file. If write buffering is on (``bufsize`` was
specified and non-zero), some or all of the data may not actually be
- written yet. (Use L{flush} or L{close} to force buffered data to be
+ written yet. (Use `flush` or `close` to force buffered data to be
written out.)
- @param data: data to write.
- @type data: str
+ :param str data: data to write
"""
+ data = b(data)
if self._closed:
raise IOError('File is closed')
if not (self._flags & self.FLAG_WRITE):
@@ -316,12 +332,12 @@ class BufferedFile (object):
self._wbuffer.write(data)
if self._flags & self.FLAG_LINE_BUFFERED:
# only scan the new data for linefeed, to avoid wasting time.
- last_newline_pos = data.rfind('\n')
+ last_newline_pos = data.rfind(linefeed_byte)
if last_newline_pos >= 0:
wbuf = self._wbuffer.getvalue()
last_newline_pos += len(wbuf) - len(data)
self._write_all(wbuf[:last_newline_pos + 1])
- self._wbuffer = StringIO()
+ self._wbuffer = BytesIO()
self._wbuffer.write(wbuf[last_newline_pos + 1:])
return
# even if we're line buffering, if the buffer has grown past the
@@ -334,11 +350,10 @@ class BufferedFile (object):
"""
Write a sequence of strings to the file. The sequence can be any
iterable object producing strings, typically a list of strings. (The
- name is intended to match L{readlines}; C{writelines} does not add line
+ name is intended to match `readlines`; `writelines` does not add line
separators.)
- @param sequence: an iterable sequence of strings.
- @type sequence: sequence
+ :param iterable sequence: an iterable sequence of strings.
"""
for line in sequence:
self.write(line)
@@ -346,11 +361,8 @@ class BufferedFile (object):
def xreadlines(self):
"""
- Identical to C{iter(f)}. This is a deprecated file interface that
- predates python iterator support.
-
- @return: an iterator.
- @rtype: iterator
+ Identical to ``iter(f)``. This is a deprecated file interface that
+ predates Python iterator support.
"""
return self
@@ -358,40 +370,36 @@ class BufferedFile (object):
def closed(self):
return self._closed
-
### overrides...
-
def _read(self, size):
"""
- I{(subclass override)}
- Read data from the stream. Return C{None} or raise C{EOFError} to
+ (subclass override)
+ Read data from the stream. Return ``None`` or raise ``EOFError`` to
indicate EOF.
"""
raise EOFError()
def _write(self, data):
"""
- I{(subclass override)}
+ (subclass override)
Write data into the stream.
"""
raise IOError('write not implemented')
def _get_size(self):
"""
- I{(subclass override)}
- Return the size of the file. This is called from within L{_set_mode}
+ (subclass override)
+ Return the size of the file. This is called from within `_set_mode`
if the file is opened in append mode, so the file position can be
- tracked and L{seek} and L{tell} will work correctly. If the file is
+ tracked and `seek` and `tell` will work correctly. If the file is
a stream that can't be randomly accessed, you don't need to override
this method,
"""
return 0
-
### internals...
-
def _set_mode(self, mode='r', bufsize=-1):
"""
Subclasses call this method to initialize the BufferedFile.
@@ -419,13 +427,13 @@ class BufferedFile (object):
self._flags |= self.FLAG_READ
if ('w' in mode) or ('+' in mode):
self._flags |= self.FLAG_WRITE
- if ('a' in mode):
+ if 'a' in mode:
self._flags |= self.FLAG_WRITE | self.FLAG_APPEND
self._size = self._get_size()
self._pos = self._realpos = self._size
- if ('b' in mode):
+ if 'b' in mode:
self._flags |= self.FLAG_BINARY
- if ('U' in mode):
+ if 'U' in mode:
self._flags |= self.FLAG_UNIVERSAL_NEWLINE
# built-in file objects have this attribute to store which kinds of
# line terminations they've seen:
@@ -454,7 +462,7 @@ class BufferedFile (object):
return
if self.newlines is None:
self.newlines = newline
- elif (type(self.newlines) is str) and (self.newlines != newline):
+ elif self.newlines != newline and isinstance(self.newlines, bytes_types):
self.newlines = (self.newlines, newline)
elif newline not in self.newlines:
self.newlines += (newline,)