source: ftputil/file.py @ 1717:827cfaff87d7

Last change on this file since 1717:827cfaff87d7 was 1717:827cfaff87d7, checked in by Stefan Schwarzer <sschwarzer@…>, 7 months ago
Remove unneeded uses of `io` In Python 3, `io.open` is the same as `open`, so we don't need to use `io.open`.
File size: 6.9 KB
Line 
1# Copyright (C) 2003-2018, Stefan Schwarzer <sschwarzer@sschwarzer.net>
2# and ftputil contributors (see `doc/contributors.txt`)
3# See the file LICENSE for licensing terms.
4
5"""
6ftputil.file - support for file-like objects on FTP servers
7"""
8
9import io
10
11import ftputil.compat
12import ftputil.error
13
14
15# This module shouldn't be used by clients of the ftputil library.
16__all__ = []
17
18
19class FTPFile(object):
20    """
21    Represents a file-like object associated with an FTP host. File
22    and socket are closed appropriately if the `close` method is
23    called.
24    """
25
26    # Set timeout in seconds when closing file connections (see ticket #51).
27    _close_timeout = 5
28
29    def __init__(self, host):
30        """Construct the file(-like) object."""
31        self._host = host
32        # pylint: disable=protected-access
33        self._session = host._session
34        # The file is still closed.
35        self.closed = True
36        self._conn = None
37        self._fobj = None
38
39    def _open(self, path, mode, buffering=None, encoding=None, errors=None,
40              newline=None, rest=None):
41        """
42        Open the remote file with given path name and mode.
43
44        Contrary to the `open` builtin, this method returns `None`,
45        instead this file object is modified in-place.
46        """
47        # We use the same arguments as in `open`.
48        # pylint: disable=too-many-arguments
49        #
50        # `buffering` argument isn't used at this time.
51        # pylint: disable=unused-argument
52        #
53        # Check mode.
54        if "a" in mode:
55            raise ftputil.error.FTPIOError("append mode not supported")
56        if mode not in ("r", "rb", "rt", "w", "wb", "wt"):
57            raise ftputil.error.FTPIOError("invalid mode '{0}'".format(mode))
58        if "b" in mode and "t" in mode:
59            # Raise a `ValueError` like Python would.
60            raise ValueError("can't have text and binary mode at once")
61        # Convenience variables
62        is_binary_mode = "b" in mode
63        is_read_mode = "r" in mode
64        # `rest` is only allowed for binary mode.
65        if (not is_binary_mode) and (rest is not None):
66            raise ftputil.error.CommandNotImplementedError(
67                    "`rest` argument can't be used for text files")
68        # Always use binary mode (see comments above).
69        transfer_type = "I"
70        command = "TYPE {0}".format(transfer_type)
71        with ftputil.error.ftplib_error_to_ftp_io_error:
72            self._session.voidcmd(command)
73        # Make transfer command.
74        command_type = "RETR" if is_read_mode else "STOR"
75        command = "{0} {1}".format(command_type, path)
76        # Force to binary regardless of transfer type (see above).
77        makefile_mode = mode
78        makefile_mode = makefile_mode.replace("t", "")
79        if not "b" in makefile_mode:
80            makefile_mode += "b"
81        # Get connection and file object.
82        with ftputil.error.ftplib_error_to_ftp_io_error:
83            self._conn = self._session.transfercmd(command, rest)
84        # The file object. Under Python 3, this will already be a
85        # `BufferedReader` or `BufferedWriter` object.
86        fobj = self._conn.makefile(makefile_mode)
87        if not is_binary_mode:
88            fobj = io.TextIOWrapper(fobj, encoding=encoding,
89                                    errors=errors, newline=newline)
90        self._fobj = fobj
91        # This comes last so that `close` won't try to close `FTPFile`
92        # objects without `_conn` and `_fobj` attributes in case of an
93        # error.
94        self.closed = False
95
96    def __iter__(self):
97        """Return a file iterator."""
98        return self
99
100    def __next__(self):
101        """
102        Return the next line or raise `StopIteration`, if there are
103        no more.
104        """
105        # Apply implicit line ending conversion for text files.
106        line = self.readline()
107        if line:
108            return line
109        else:
110            raise StopIteration
111
112    # Although Python 2.6+ has the `next` builtin function already, it
113    # still requires iterators to have a `next` method.
114    next = __next__
115
116    #
117    # Context manager methods
118    #
119    def __enter__(self):
120        # Return `self`, so it can be accessed as the variable
121        # component of the `with` statement.
122        return self
123
124    def __exit__(self, exc_type, exc_val, exc_tb):
125        # We don't need the `exc_*` arguments here
126        # pylint: disable=unused-argument
127        self.close()
128        # Be explicit
129        return False
130
131    #
132    # Other attributes
133    #
134    def __getattr__(self, attr_name):
135        """
136        Handle requests for attributes unknown to `FTPFile` objects:
137        delegate the requests to the contained file object.
138        """
139        if attr_name in ("encoding flush isatty fileno read readline "
140                         "readlines seek tell truncate name softspace "
141                         "write writelines".split()):
142            return getattr(self._fobj, attr_name)
143        raise AttributeError(
144                "'FTPFile' object has no attribute '{0}'".format(attr_name))
145
146    # TODO: Implement `__dir__`? (See
147    # http://docs.python.org/whatsnew/2.6.html#other-language-changes )
148
149    def close(self):
150        """Close the `FTPFile`."""
151        if self.closed:
152            return
153        # Timeout value to restore, see below.
154        # Statement works only before the try/finally statement,
155        # otherwise Python raises an `UnboundLocalError`.
156        old_timeout = self._session.sock.gettimeout()
157        try:
158            self._fobj.close()
159            self._fobj = None
160            with ftputil.error.ftplib_error_to_ftp_io_error:
161                self._conn.close()
162            # Set a timeout to prevent waiting until server timeout
163            # if we have a server blocking here like in ticket #51.
164            self._session.sock.settimeout(self._close_timeout)
165            try:
166                with ftputil.error.ftplib_error_to_ftp_io_error:
167                    self._session.voidresp()
168            except ftputil.error.FTPIOError as exc:
169                # Ignore some errors, see tickets #51 and #17 at
170                # http://ftputil.sschwarzer.net/trac/ticket/51 and
171                # http://ftputil.sschwarzer.net/trac/ticket/17,
172                # respectively.
173                exc = str(exc)
174                error_code = exc[:3]
175                if exc.splitlines()[0] != "timed out" and \
176                  error_code not in ("150", "426", "450", "451"):
177                    raise
178        finally:
179            # Restore timeout for socket of `FTPFile`'s `ftplib.FTP`
180            # object in case the connection is reused later.
181            self._session.sock.settimeout(old_timeout)
182            # If something went wrong before, the file is probably
183            # defunct and subsequent calls to `close` won't help
184            # either, so we consider the file closed for practical
185            # purposes.
186            self.closed = True
187
188    def __getstate__(self):
189        raise TypeError("cannot serialize FTPFile object")
Note: See TracBrowser for help on using the repository browser.