Opened 5 years ago

Closed 5 years ago

#76 closed defect (invalid)

Error message related to OS

Reported by: ftputiluser Owned by: schwa
Priority: major Milestone: 3.1
Component: Library Version: 3.0
Keywords: error message, original exception, socket Cc:

Description (last modified by schwa)

 python test.py 

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    with ftputil.FTPHost('195.0.0.1:595', 'User1', 'test1') as host:
  File "/home/nick/Desktop/ftp_testing_python/ftputil/host.py", line 69, in __init__
    self._session = self._make_session()
  File "/home/nick/Desktop/ftp_testing_python/ftputil/host.py", line 129, in _make_session
    return factory(*args, **kwargs)
  File "/home/nick/Desktop/ftp_testing_python/ftputil/error.py", line 133, in __exit__
    raise FTPOSError(*exc_value.args)
ftputil.error.FTPOSError: -2
Debugging info: ftputil 3.0, Python 2.7.3 (linux2)

The following code worked on a different version of ubuntu

import ftputil 

# Download some files from the login directory.
with ftputil.FTPHost('195.0.0.1:595', 'User1', 'test1') as host:
    names = host.listdir(host.curdir)
    for name in names:
        print(name)

Change History (9)

comment:1 Changed 5 years ago by schwa

  • Description modified (diff)
  • Status changed from new to assigned
  • Version set to 3.0

Added code markup to ticket description.

comment:2 Changed 5 years ago by schwa

Hi,

Thanks for the report.

Is "the following code" the one that gives the traceback shown at the start of the description (i. e. test.py)?

One thing I notice in the code is the use of "195.0.0.1:595" for host and port. ftputil passes this string as the host to ftplib.FTP. It will then depend on the ftplib module in the Python standard library whether it will strip off the part after the colon and use that for the port when connecting. From the Python documentation on `ftplib` it doesn't look like the form "host:port" is allowed. I can imagine that the code that was working on another Ubuntu version actually used only a host without port and hence implicitly the default port.

When I do something similar in the interpreter like in your code (note: no ftputil involved here), I also get an error code -2:

>>> import ftplib
>>> f = ftplib.FTP("ftp.gnome.org:21", "anonymous", "")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.7/ftplib.py", line 117, in __init__
    self.connect(host)
  File "/usr/lib64/python2.7/ftplib.py", line 132, in connect
    self.sock = socket.create_connection((self.host, self.port), self.timeout)
  File "/usr/lib64/python2.7/socket.py", line 553, in create_connection
    for res in getaddrinfo(host, port, 0, SOCK_STREAM):
socket.gaierror: [Errno -2] Name or service not known

If you actually want to connect to a non-default port, I recommend you read this section in the ftputil documentation.

comment:3 Changed 5 years ago by schwa

  • Summary changed from error message related to OS to Error message related to OS

comment:4 follow-up: Changed 5 years ago by schwa

Things to find out and probably use to improve ftputil:

Why does ftputil convert the socket.gaierror to an FTPOSError?

This code in ftputil.error.FtplibErrorToFTPOSError.__exit__ is responsible for the conversion:

        elif isinstance(exc_value, ftplib.all_errors):
            raise FTPOSError(*exc_value.args)

In the interpreter:

In [9]: try:
   ...:     f = ftplib.FTP("ftp.gnome.org:21", "", "")
   ...: except Exception, exc:
   ...:     socket_exc = exc
   ...:     

In [11]: isinstance(socket_exc, ftplib.all_errors)
Out[11]: True

In [13]: ftplib.all_errors
Out[13]: (ftplib.Error, IOError, EOFError, ssl.SSLError)

In [14]: for exc in ftplib.all_errors:
   ....:     if isinstance(socket_exc, exc):
   ....:         print exc
   ....:         
<type 'exceptions.IOError'>

In [17]: socket_exc.__class__.mro()
Out[17]: 
[socket.gaierror,
 socket.error,
 IOError,
 EnvironmentError,
 StandardError,
 Exception,
 BaseException,
 object]

So socket.gaierror has IOError in its baseclasses and IOError is also in the ftplib.all_errors tuple.

Why is the error message from ftputil only "-2"?

In [18]: socket_exc.args
Out[18]: (-2, 'Name or service not known')

ftputil tries to be helpful and shows the error message from the original exception. However, ftputil uses only args[0] as source of the error message because, as far as I know, there's no guarantee or even a common idiom that args[1:] contains anything that should be part of an error message.

I still may use heuristics like:

  • If the exception inherits from socket.error, assume args[1] is the actual message and combine args[:2]. (The error code might be useful in some cases. Don't set the ftputil exception's errno to args[0] though because this might violate callers' expectations.)
  • If args[0] is an integer, assume that args[1] is the actual message and combine them.

comment:5 in reply to: ↑ 4 Changed 5 years ago by schwa

Replying to schwa:

Why is the error message from ftputil only "-2"?

In [18]: socket_exc.args
Out[18]: (-2, 'Name or service not known')

ftputil tries to be helpful and shows the error message from the original exception. However, ftputil uses only args[0] as source of the error message because, as far as I know, there's no guarantee or even a common idiom that args[1:] contains anything that should be part of an error message.

Why does the message from FTPOSError only contain the "-2" part even though the exception is constructed with FTPOSError(*exc_value.args) (see above)?

This is the responsible code from ftputil.error:

class FTPError(Exception):
    """General ftputil error class."""

    def __init__(self, *args):
        super(FTPError, self).__init__(*args)
        # Don't use `args[0]` directly because `args` may be empty.
        if args:
            self.strerror = self.args[0]
        else:
            self.strerror = ""
        try:
            self.errno = int(self.strerror[:3])
        except (TypeError, IndexError, ValueError):
            self.errno = None
        self.file_name = None

    def __str__(self):
        return "{0}\nDebugging info: {1}".format(self.strerror,
                                                 ftputil.version.version_info)

So by default, ftputil uses only args[0] for the FTPOSError.strerror attribute.

comment:6 Changed 5 years ago by schwa

I checked in [6e82349a3a14] to include the full string representation of a previously raised exception. So if you use the "host:port" form now, you get a slightly more informative error message:

>>> host = ftputil.FTPHost("ftp.gnome.org:21", "", "")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "ftputil/host.py", line 69, in __init__
    self._session = self._make_session()
  File "ftputil/host.py", line 129, in _make_session
    return factory(*args, **kwargs)
  File "ftputil/error.py", line 139, in __exit__
    raise FTPOSError(*exc_value.args, original_exception=exc_value)
ftputil.error.FTPOSError: [Errno -2] Name or service not known
Debugging info: ftputil 3.0, Python 2.7.5 (linux2)

comment:7 Changed 5 years ago by schwa

  • Keywords error message original exception socket added
  • Milestone set to 3.1
  • Resolution set to fixed
  • Status changed from assigned to closed

comment:8 Changed 5 years ago by schwa

  • Description modified (diff)
  • Resolution fixed deleted
  • Status changed from closed to reopened

comment:9 Changed 5 years ago by schwa

  • Resolution set to invalid
  • Status changed from reopened to closed

I change the resolution to "invalid" since in my opinion the "lacking" support for the "host:port" syntax is no defect.

The "fixed" referred to the improvement in the error message.

Last edited 3 years ago by schwa (previous) (diff)
Note: See TracTickets for help on using tickets.