Index: Include/pyerrors.h =================================================================== --- Include/pyerrors.h (revision 56165) +++ Include/pyerrors.h (working copy) @@ -130,6 +130,7 @@ PyAPI_DATA(PyObject *) PyExc_EnvironmentError; PyAPI_DATA(PyObject *) PyExc_IOError; PyAPI_DATA(PyObject *) PyExc_OSError; +PyAPI_DATA(PyObject *) PyExc_NetworkIOError; PyAPI_DATA(PyObject *) PyExc_ImportError; PyAPI_DATA(PyObject *) PyExc_IndexError; PyAPI_DATA(PyObject *) PyExc_KeyError; Index: setup.py =================================================================== --- setup.py (revision 56165) +++ setup.py (working copy) @@ -199,6 +199,7 @@ if missing: print print "Failed to find the necessary bits to build these modules:" + print "(or they were not necessary on this platform)" print_three_column(missing) if self.failed: Index: Objects/exceptions.c =================================================================== --- Objects/exceptions.c (revision 56165) +++ Objects/exceptions.c (working copy) @@ -760,6 +760,12 @@ /* + * NetworkIOError extends IOError + */ +MiddlingExtendsException(PyExc_IOError, NetworkIOError, + EnvironmentError, "Network I/O operation failed."); + +/* * WindowsError extends OSError */ #ifdef MS_WINDOWS @@ -1966,6 +1972,7 @@ PRE_INIT(EnvironmentError) PRE_INIT(IOError) PRE_INIT(OSError) + PRE_INIT(NetworkIOError) #ifdef MS_WINDOWS PRE_INIT(WindowsError) #endif @@ -2032,6 +2039,7 @@ POST_INIT(EnvironmentError) POST_INIT(IOError) POST_INIT(OSError) + POST_INIT(NetworkIOError) #ifdef MS_WINDOWS POST_INIT(WindowsError) #endif Index: Doc/lib/liburllib2.tex =================================================================== --- Doc/lib/liburllib2.tex (revision 56165) +++ Doc/lib/liburllib2.tex (working copy) @@ -83,7 +83,7 @@ \begin{excdesc}{URLError} The handlers raise this exception (or derived exceptions) when they -run into a problem. It is a subclass of \exception{IOError}. +run into a problem. It is a subclass of \exception{NetworkIOError}. \end{excdesc} \begin{excdesc}{HTTPError} Index: Doc/lib/libexcs.tex =================================================================== --- Doc/lib/libexcs.tex (revision 56165) +++ Doc/lib/libexcs.tex (working copy) @@ -213,6 +213,19 @@ includes the name that could not be found. \end{excdesc} +\begin{excdesc}{NetworkIOError} + Raised when a Network I/O error occurs. The + \refmodule{socket}, \refmodule{urllib} and \refmodule{urllib2} + module errors use this as their base class. + This is a subclass of \exception{IOError}. See its + parent \exception{EnvironmentError} for attribute information. + \versionadded{2.6} + + In previous versions python code needed to know to catch one or more + of socket.error or IOError and possibly even OSError for networking + related exceptions. +\end{excdesc} + \begin{excdesc}{NotImplementedError} This exception is derived from \exception{RuntimeError}. In user defined base classes, abstract methods should raise this exception Index: Doc/lib/liburllib.tex =================================================================== --- Doc/lib/liburllib.tex (revision 56165) +++ Doc/lib/liburllib.tex (working copy) @@ -24,7 +24,7 @@ identifier, this opens a local file (without universal newlines); otherwise it opens a socket to a server somewhere on the network. If the connection cannot be made -the \exception{IOError} exception is raised. If all went well, a +the \exception{NetworkIOError} exception is raised. If all went well, a file-like object is returned. This supports the following methods: \method{read()}, \method{readline()}, \method{readlines()}, \method{fileno()}, \method{close()}, \method{info()} and \method{geturl()}. It also has @@ -275,7 +275,7 @@ \var{key_file} and \var{cert_file} are supported to provide an SSL key and certificate; both are needed to support client authentication. -\class{URLopener} objects will raise an \exception{IOError} exception +\class{URLopener} objects will raise an \exception{NetworkIOError} exception if the server returns an error code. \end{classdesc} Index: Doc/api/exceptions.tex =================================================================== --- Doc/api/exceptions.tex (revision 56165) +++ Doc/api/exceptions.tex (working copy) @@ -399,6 +399,7 @@ \lineiii{PyExc_KeyboardInterrupt\ttindex{PyExc_KeyboardInterrupt}}{\exception{KeyboardInterrupt}}{} \lineiii{PyExc_MemoryError\ttindex{PyExc_MemoryError}}{\exception{MemoryError}}{} \lineiii{PyExc_NameError\ttindex{PyExc_NameError}}{\exception{NameError}}{} + \lineiii{PyExc_NetworkIOError\ttindex{PyExc_NetworkIOError}}{\exception{NetworkIOError}}{(5)} \lineiii{PyExc_NotImplementedError\ttindex{PyExc_NotImplementedError}}{\exception{NotImplementedError}}{} \lineiii{PyExc_OSError\ttindex{PyExc_OSError}}{\exception{OSError}}{} \lineiii{PyExc_OverflowError\ttindex{PyExc_OverflowError}}{\exception{OverflowError}}{} @@ -428,6 +429,9 @@ \item[(4)] \versionadded{2.5} + +\item[(5)] + \versionadded{2.6} \end{description} Index: Lib/urllib.py =================================================================== --- Lib/urllib.py (revision 56165) +++ Lib/urllib.py (working copy) @@ -92,9 +92,9 @@ _urlopener.cleanup() # exception raised when downloaded size does not match content-length -class ContentTooShortError(IOError): +class ContentTooShortError(NetworkIOError): def __init__(self, message, content): - IOError.__init__(self, message) + NetworkIOError.__init__(self, message) self.content = content ftpcache = {} @@ -191,17 +191,17 @@ else: return getattr(self, name)(url, data) except socket.error, msg: - raise IOError, ('socket error', msg), sys.exc_info()[2] + raise NetworkIOError, ('socket error', msg), sys.exc_info()[2] def open_unknown(self, fullurl, data=None): """Overridable interface to open unknown URL type.""" type, url = splittype(fullurl) - raise IOError, ('url error', 'unknown url type', type) + raise NetworkIOError, ('url error', 'unknown url type', type) def open_unknown_proxy(self, proxy, fullurl, data=None): """Overridable interface to open unknown URL type.""" type, url = splittype(fullurl) - raise IOError, ('url error', 'invalid proxy for %s' % type, proxy) + raise NetworkIOError, ('url error', 'invalid proxy for %s' % type, proxy) # External interface def retrieve(self, url, filename=None, reporthook=None, data=None): @@ -298,7 +298,7 @@ host = realhost #print "proxy via http:", host, selector - if not host: raise IOError, ('http error', 'no host given') + if not host: raise NetworkIOError, ('http error', 'no host given') if proxy_passwd: import base64 @@ -330,7 +330,7 @@ if errcode == -1: if fp: fp.close() # something went wrong with the HTTP status line - raise IOError, ('http protocol error', 0, + raise NetworkIOError, ('http protocol error', 0, 'got a bad status line', None) if errcode == 200: return addinfourl(fp, headers, "http:" + url) @@ -356,10 +356,10 @@ return self.http_error_default(url, fp, errcode, errmsg, headers) def http_error_default(self, url, fp, errcode, errmsg, headers): - """Default error handler: close the connection and raise IOError.""" + """Default error handler: close the connection and raise NetworkIOError.""" void = fp.read() fp.close() - raise IOError, ('http error', errcode, errmsg, headers) + raise NetworkIOError, ('http error', errcode, errmsg, headers) if hasattr(socket, "ssl"): def open_https(self, url, data=None): @@ -389,7 +389,7 @@ if user_passwd: selector = "%s://%s%s" % (urltype, realhost, rest) #print "proxy via https:", host, selector - if not host: raise IOError, ('https error', 'no host given') + if not host: raise NetworkIOError, ('https error', 'no host given') if proxy_passwd: import base64 proxy_auth = base64.b64encode(proxy_passwd).strip() @@ -422,7 +422,7 @@ if errcode == -1: if fp: fp.close() # something went wrong with the HTTP status line - raise IOError, ('http protocol error', 0, + raise NetworkIOError, ('http protocol error', 0, 'got a bad status line', None) if errcode == 200: return addinfourl(fp, headers, "https:" + url) @@ -436,7 +436,7 @@ def open_file(self, url): """Use local file or FTP depending on form of URL.""" if not isinstance(url, str): - raise IOError, ('file error', 'proxy support for file protocol currently not implemented') + raise NetworkIOError, ('file error', 'proxy support for file protocol currently not implemented') if url[:2] == '//' and url[2:3] != '/' and url[2:12].lower() != 'localhost/': return self.open_ftp(url) else: @@ -454,7 +454,7 @@ try: stats = os.stat(localname) except OSError, e: - raise IOError(e.errno, e.strerror, e.filename) + raise NetworkIOError(e.errno, e.strerror, e.filename) size = stats.st_size modified = email.utils.formatdate(stats.st_mtime, usegmt=True) mtype = mimetypes.guess_type(url)[0] @@ -475,19 +475,19 @@ urlfile = 'file://' + file return addinfourl(open(localname, 'rb'), headers, urlfile) - raise IOError, ('local file error', 'not on local host') + raise NetworkIOError, ('local file error', 'not on local host') def open_ftp(self, url): """Use FTP protocol.""" if not isinstance(url, str): - raise IOError, ('ftp error', 'proxy support for ftp protocol currently not implemented') + raise NetworkIOError, ('ftp error', 'proxy support for ftp protocol currently not implemented') import mimetypes, mimetools try: from cStringIO import StringIO except ImportError: from StringIO import StringIO host, path = splithost(url) - if not host: raise IOError, ('ftp error', 'no host given') + if not host: raise NetworkIOError, ('ftp error', 'no host given') host, port = splitport(host) user, host = splituser(host) if user: user, passwd = splitpasswd(user) @@ -537,12 +537,12 @@ headers = mimetools.Message(StringIO(headers)) return addinfourl(fp, headers, "ftp:" + url) except ftperrors(), msg: - raise IOError, ('ftp error', msg), sys.exc_info()[2] + raise NetworkIOError, ('ftp error', msg), sys.exc_info()[2] def open_data(self, url, data=None): """Use "data" URL.""" if not isinstance(url, str): - raise IOError, ('data error', 'proxy support for data protocol currently not implemented') + raise NetworkIOError, ('data error', 'proxy support for data protocol currently not implemented') # ignore POSTed data # # syntax of data URLs: @@ -558,7 +558,7 @@ try: [type, data] = url.split(',', 1) except ValueError: - raise IOError, ('data error', 'bad data URL') + raise NetworkIOError, ('data error', 'bad data URL') if not type: type = 'text/plain;charset=US-ASCII' semi = type.rfind(';') @@ -855,7 +855,7 @@ conn = self.ftp.ntransfercmd(cmd) except ftplib.error_perm, reason: if str(reason)[:3] != '550': - raise IOError, ('ftp error', reason), sys.exc_info()[2] + raise NetworkIOError, ('ftp error', reason), sys.exc_info()[2] if not conn: # Set transfer mode to ASCII! self.ftp.voidcmd('TYPE A') Index: Lib/urllib2.py =================================================================== --- Lib/urllib2.py (revision 56165) +++ Lib/urllib2.py (working copy) @@ -18,7 +18,7 @@ urllib. pass the url and optionally data to post to an HTTP URL, and get a file-like object back. One difference is that you can also pass a Request instance instead of URL. Raises a URLError (subclass of -IOError); for HTTP errors, raises an HTTPError, which can also be +NetworkIOError); for HTTP errors, raises an HTTPError, which can also be treated as a valid response. build_opener -- Function that creates a new OpenerDirector instance. @@ -39,7 +39,7 @@ BaseHandler -- exceptions: -URLError -- A subclass of IOError, individual protocols have their own +URLError -- A subclass of NetworkIOError, individual protocols have their own specific subclass. HTTPError -- Also a valid HTTP response, so you can treat an HTTP error @@ -131,14 +131,14 @@ # make sure all of the IOError stuff is overridden. we just want to be # subtypes. -class URLError(IOError): - # URLError is a sub-type of IOError, but it doesn't share any of +class URLError(NetworkIOError): + # URLError is a sub-type of NetworkIOError, but it doesn't share any of # the implementation. need to override __init__ and __str__. # It sets self.args for compatibility with other EnvironmentError # subclasses, but args doesn't have the typical format with errno in # slot 0 and strerror in slot 1. This may be better than nothing. - def __init__(self, reason): - self.args = reason, + def __init__(self, reason, *otherArgs): + self.args = otherArgs + (reason,) self.reason = reason def __str__(self): @@ -1242,7 +1242,7 @@ import mimetypes host = req.get_host() if not host: - raise IOError, ('ftp error', 'no host given') + raise URLError('ftp error', 'no host given') host, port = splitport(host) if port is None: port = ftplib.FTP_PORT @@ -1288,7 +1288,7 @@ headers = mimetools.Message(sf) return addinfourl(fp, headers, req.get_full_url()) except ftplib.all_errors, msg: - raise IOError, ('ftp error', msg), sys.exc_info()[2] + raise URLError, ('ftp error', msg), sys.exc_info()[2] def connect_ftp(self, user, passwd, host, port, dirs, timeout): fw = ftpwrapper(user, passwd, host, port, dirs, timeout) Index: Lib/test/exception_hierarchy.txt =================================================================== --- Lib/test/exception_hierarchy.txt (revision 56165) +++ Lib/test/exception_hierarchy.txt (working copy) @@ -13,6 +13,7 @@ | +-- AttributeError | +-- EnvironmentError | | +-- IOError + | | +-- NetworkIOError | | +-- OSError | | +-- WindowsError (Windows) | | +-- VMSError (VMS) Index: Lib/test/test_urllib.py =================================================================== --- Lib/test/test_urllib.py (revision 56165) +++ Lib/test/test_urllib.py (working copy) @@ -127,11 +127,11 @@ self.unfakehttp() def test_empty_socket(self): - """urlopen() raises IOError if the underlying socket does not send any + """urlopen() raises NetworkIOError if the underlying socket does not send any data. (#1680230) """ self.fakehttp('') try: - self.assertRaises(IOError, urllib.urlopen, 'http://something') + self.assertRaises(NetworkIOError, urllib.urlopen, 'http://something') finally: self.unfakehttp() Index: Lib/test/test_urllib2net.py =================================================================== --- Lib/test/test_urllib2net.py (revision 56165) +++ Lib/test/test_urllib2net.py (working copy) @@ -136,7 +136,7 @@ def test_bad_address(self): # Make sure proper exception is raised when connecting to a bogus # address. - self.assertRaises(IOError, + self.assertRaises(NetworkIOError, # SF patch 809915: In Sep 2003, VeriSign started # highjacking invalid .com and .net addresses to # boost traffic to their own site. This test @@ -181,10 +181,7 @@ f.close() urls = [ 'file:'+sanepathname2url(os.path.abspath(TESTFN)), - - # XXX bug, should raise URLError - #('file://nonsensename/etc/passwd', None, urllib2.URLError) - ('file://nonsensename/etc/passwd', None, (EnvironmentError, socket.error)) + ('file:///nonsensename/etc/passwd', None, urllib2.URLError), ] self._test_urls(urls, self._extra_handlers()) finally: @@ -244,7 +241,7 @@ debug(url) try: f = urllib2.urlopen(url, req) - except (IOError, socket.error, OSError), err: + except EnvironmentError, err: debug(err) if expected_err: msg = ("Didn't get expected error(s) %s for %s %s, got %s" % Index: Modules/socketmodule.c =================================================================== --- Modules/socketmodule.c (revision 56165) +++ Modules/socketmodule.c (working copy) @@ -4281,7 +4281,8 @@ if (m == NULL) return; - socket_error = PyErr_NewException("socket.error", NULL, NULL); + socket_error = PyErr_NewException("socket.error", PyExc_NetworkIOError, + NULL); if (socket_error == NULL) return; PySocketModuleAPI.error = socket_error;