From df729b6452255e9cf82cd14cc013e1d9e68b7ea3 Mon Sep 17 00:00:00 2001 From: Vasiliy Kiryanov Date: Sat, 31 Oct 2026 19:34:56 -0400 Subject: [PATCH] gh-50720: Copy winerror to URLError on Windows --- Doc/library/urllib.error.rst | 3 ++- Lib/test/test_urllib2.py | 10 ++++++++++ Lib/urllib/error.py | 3 +++ .../2026-10-29-20-52-57.gh-issue-50720.Kd1VMR.rst | 3 ++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Doc/library/urllib.error.rst b/Doc/library/urllib.error.rst index 6a71d4a2ebb6845..0079699e06ca27d 100644 --- a/Doc/library/urllib.error.rst +++ b/Doc/library/urllib.error.rst @@ -29,7 +29,8 @@ The following exceptions are raised by :mod:`!urllib.error` as appropriate: .. versionchanged:: 3.16 The :attr:`~OSError.errno` and :attr:`~OSError.strerror` attributes are - now set from :attr:`reason` when it is an :exc:`OSError`. + now set from :attr:`reason` when it is an :exc:`OSError`. On Windows, + :attr:`~OSError.winerror` is set as well. .. exception:: HTTPError(url, code, msg, hdrs, fp) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 5bfe5f4f4f03087..387eec0fb6e906d 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -104,6 +104,16 @@ def request(self, *args, **kwargs): self.assertEqual(err.errno, -2) self.assertEqual(err.strerror, 'Name or service not known') + @unittest.skipUnless(sys.platform == "win32", "Windows-specific test") + def test_URLError_OSError_attributes_winerror(self): + # gh-50720: OSError(errno, strerror, filename, winerror): + # on Windows the constructor derives errno from winerror. + os_error = OSError(0, "The system cannot find the path specified", None, 3) + + err = urllib.error.URLError(os_error) + self.assertEqual(err.winerror, 3) + self.assertEqual(err.strerror, os_error.strerror) + class RequestHdrsTests(unittest.TestCase): diff --git a/Lib/urllib/error.py b/Lib/urllib/error.py index 5020a60f1ef9bd0..cc9a9254d760f7f 100644 --- a/Lib/urllib/error.py +++ b/Lib/urllib/error.py @@ -11,6 +11,7 @@ response. """ import io +import sys import urllib.response __all__ = ['URLError', 'HTTPError', 'ContentTooShortError'] @@ -32,6 +33,8 @@ def __init__(self, reason, filename=None): # it would have set from the wrapped error (gh-50720). self.errno = reason.errno self.strerror = reason.strerror + if sys.platform == 'win32': + self.winerror = reason.winerror def __str__(self): return '' % self.reason diff --git a/Misc/NEWS.d/next/Library/2026-10-29-20-52-57.gh-issue-50720.Kd1VMR.rst b/Misc/NEWS.d/next/Library/2026-10-29-20-52-57.gh-issue-50720.Kd1VMR.rst index ddb4377c3c25987..263b7ac89585769 100644 --- a/Misc/NEWS.d/next/Library/2026-10-29-20-52-57.gh-issue-50720.Kd1VMR.rst +++ b/Misc/NEWS.d/next/Library/2026-10-29-20-52-57.gh-issue-50720.Kd1VMR.rst @@ -1,3 +1,4 @@ :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`. +:attr:`~urllib.error.URLError.reason` when it is an :exc:`OSError`. On +Windows, :attr:`~OSError.winerror` is set as well.