Ignore:
Timestamp:
Aug 4, 2013, 1:33:04 PM (6 years ago)
Author:
Stefan Schwarzer <sschwarzer@…>
Branch:
default
Message:
Use a slightly dump but effective way to give a `socket.makefile` file
interface of a `BufferedReader` or `BufferedWriter` under Python 2.

The previous approach, still visible in `_FTPFile._wrapped_file`,
still didn't work properly after a lot of fiddling with `__getattr__`
and `__setattr__`.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • ftputil/file.py

    r1326 r1334  
    77"""
    88
     9from __future__ import print_function
    910from __future__ import unicode_literals
    1011
     
    1718# This module shouldn't be used by clients of the ftputil library.
    1819__all__ = []
     20
     21
     22class BufferedReaderWriter(io.BufferedIOBase):
     23    """
     24    Adapt a file object returned from `socket.makefile` to the
     25    interfaces of `io.BufferedReader` or `io.BufferedWriter`, so that
     26    the new object can be wrapped by `io.TextIOWrapper`.
     27
     28    This is only needed with Python 2, since in Python 3
     29    `socket.makefile` already returns a `BufferedReader` or
     30    `BufferedWriter` object (depending on mode).
     31    """
     32
     33    def __init__(self, fobj, is_readable=False, is_writable=False):
     34        # This is the return value of `socket.makefile` and is already
     35        # buffered.
     36        self.raw = fobj
     37        self._is_readable = is_readable
     38        self._is_writable = is_writable
     39
     40    @property
     41    def closed(self):
     42        return self.raw.closed
     43
     44    def close(self):
     45        self.raw.close()
     46
     47    def fileno(self):
     48        return self.raw.fileno()
     49
     50    def isatty(self):
     51        # It's highly unlikely that this is interactive.
     52        return False
     53
     54    def seekable(self):
     55        return False
     56
     57    #
     58    # Interface for `BufferedReader`
     59    #
     60    def readable(self):
     61        return self._is_readable
     62
     63    def read(self, *arg):
     64        return self.raw.read(*arg)
     65
     66    read1 = read
     67
     68    def readline(self, *arg):
     69        return self.raw.readline(*arg)
     70
     71    def readlines(self, *arg):
     72        return self.raw.readlines(*arg)
     73
     74    def readinto(self, bytearray_):
     75        data = self.raw.read(len(bytearray_))
     76        bytearray_[:len(data)] = data
     77        return len(data)
     78
     79    #
     80    # Interface for `BufferedWriter`
     81    #
     82    def writable(self):
     83        return self._is_writable
     84
     85    def flush(self):
     86        self.raw.flush()
     87
     88    def write(self, bytes_or_bytearray):
     89        # `BufferedWriter.write` has to return the number of written
     90        # bytes. Since we don't really know how many bytes got
     91        # actually written, return the length of the full data, but
     92        # also call `flush` to increase the chance that all bytes are
     93        # written.
     94        self.raw.write(bytes_or_bytearray)
     95        # TODO: Measure impact of flushing for many small writes.
     96        self.flush()
     97        return len(bytes_or_bytearray)
     98
     99    def writelines(self, lines):
     100        self.raw.writelines(lines)
    19101
    20102
     
    58140                bytearray_[:len(data)] = data
    59141                return len(data)
     142            def write(self, bytes_):
     143                # `BufferedWriter` expects the number of written bytes
     144                # as return value. Since we don't really know how many
     145                # bytes are written, return the length of the full
     146                # data, but also call `flush` to increase the chance
     147                # that actually all bytes are written.
     148                print("=== type(bytes):", type(bytes_))
     149                # Use slice in case we get a `memoryview` object.
     150                self._fobj.write(bytes_[:])
     151                self._fobj.flush()
     152                return len(bytes_)
    60153            def __getattr__(self, name):
    61                 return getattr(self._fobj, name)
     154                if name == "__IOBase_closed":
     155                    result = super(Wrapper, self).__getattr__(name)
     156                    return result
     157                else:
     158                    result = getattr(self._fobj, name)
     159                    return result
    62160            def __setattr__(self, name, value):
    63161                if name == "__IOBase_closed":
     
    103201        # Force to binary regardless of transfer type (see above).
    104202        makefile_mode = mode
    105         if "t" in mode:
    106             makefile_mode = makefile_mode.replace("t", "")
     203        makefile_mode = makefile_mode.replace("t", "")
    107204        if not "b" in makefile_mode:
    108205            makefile_mode += "b"
     
    110207        with ftputil.error.ftplib_error_to_ftp_io_error:
    111208            self._conn = self._session.transfercmd(command)
    112         # The actual file object.
     209        # The actual file object. Under Python 3, this will already
     210        # be wrapped by a `BufferedReader` or `BufferedWriter`.
    113211        fobj = self._conn.makefile(makefile_mode)
    114         if is_read_mode:
    115             if ftputil.compat.python_version == 2:
    116                 # See implementation of `_wrapped_file`.
    117                 fobj = self._wrapped_file(fobj, is_readable=True)
    118             fobj = io.BufferedReader(fobj)
    119         else:
    120             if ftputil.compat.python_version == 2:
    121                 # See implementation of `_wrapped_file`.
    122                 fobj = self._wrapped_file(fobj, is_writable=True)
    123             fobj = io.BufferedWriter(fobj)
     212        if ftputil.compat.python_version == 2:
     213            if is_read_mode:
     214                fobj = BufferedReaderWriter(fobj, is_readable=True)
     215            else:
     216                fobj = BufferedReaderWriter(fobj, is_writable=True)
    124217        if not is_bin_mode:
    125218            fobj = io.TextIOWrapper(fobj, encoding=encoding,
Note: See TracChangeset for help on using the changeset viewer.