Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/library/urllib.error.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ The following exceptions are raised by :mod:`!urllib.error` as appropriate:
:exc:`URLError` used to be a subtype of :exc:`IOError`, which is now an
alias of :exc:`OSError`.

.. versionchanged:: 3.16
The :attr:`~OSError.errno` and :attr:`~OSError.strerror` attributes are
now set from :attr:`reason` when it is an :exc:`OSError`.


.. exception:: HTTPError(url, code, msg, hdrs, fp)

Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_urllib2.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ def test_parse_http_list(self):
def test_URLError_reasonstr(self):
err = urllib.error.URLError('reason')
self.assertIn(err.reason, str(err))
self.assertIsNone(err.errno)
self.assertIsNone(err.strerror)

def test_URLError_OSError_attributes(self):
# gh-50720: errno and strerror are copied from a wrapped OSError.
socket_error = socket.gaierror(-2, 'Name or service not known')

class FailingHTTPConnection(MockHTTPClass):
def request(self, *args, **kwargs):
raise socket_error

h = urllib.request.AbstractHTTPHandler()
req = Request("http://example.com/")
req.timeout = None

with self.assertRaises(urllib.error.URLError) as cm:
h.do_open(FailingHTTPConnection(), req)
err = cm.exception
self.assertIs(err.reason, socket_error)
self.assertEqual(err.errno, -2)
self.assertEqual(err.strerror, 'Name or service not known')


class RequestHdrsTests(unittest.TestCase):
Expand Down
5 changes: 5 additions & 0 deletions Lib/urllib/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def __init__(self, reason, filename=None):
self.reason = reason
if filename is not None:
self.filename = filename
if isinstance(reason, OSError):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only question is winerror Should we handle that?
We don't have a great on windows and if winerror will be different than errno for the scenarios that URLError will capture.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this just as thought. Not a blocker.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, winerror stays None on Windows while the wrapped error carries the code. For socket errors it equals errno, but it differs for non-socket errors wrapped by FileHandler.

Should I open a separate improvement issue for it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a windows system, could you verify it with a some tests?
Exposing winerror gated by system check will be good.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have macOS and Linux.

The CI runs the test suite on Windows, so a Windows-only test would be verified there. I can open a follow-up PR that copies winerror behind a platform check, with the test skipped elsewhere.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sounds good to me.

# OSError.__init__() is not called, so copy the attributes
# it would have set from the wrapped error (gh-50720).
self.errno = reason.errno
self.strerror = reason.strerror

def __str__(self):
return '<urlopen error %s>' % self.reason
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:exc:`urllib.error.URLError` now sets the :attr:`~OSError.errno` and
:attr:`~OSError.strerror` attributes from
:attr:`~urllib.error.URLError.reason` when it is an :exc:`OSError`.
Loading