From 1d9213ccb371d6d8d149b1291d06d46e5b56c05f Mon Sep 17 00:00:00 2001 From: Felipe Bidu Date: Tue, 16 Jun 2026 10:05:34 +0100 Subject: [PATCH] gh-86228: Initialize HTTPResponse.url in __init__ (bpo-42062) HTTPResponse.__init__ accepted a ``url`` argument but never stored it, so geturl() raised AttributeError on responses created directly via http.client rather than through urllib. Store it on self.url and add a regression test. --- Lib/http/client.py | 4 ++++ Lib/test/test_httplib.py | 16 ++++++++++++++++ ...2026-06-16-10-04-05.gh-issue-86228.ECtxfP.rst | 5 +++++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-16-10-04-05.gh-issue-86228.ECtxfP.rst diff --git a/Lib/http/client.py b/Lib/http/client.py index 1e1a535c4c4eb18..0bb2ce07fc20674 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -294,6 +294,10 @@ def __init__(self, sock, debuglevel=0, method=None, url=None): self.length = _UNKNOWN # number of bytes left in response self.will_close = _UNKNOWN # conn will close at end of response + # URL of the resource, set by urllib but otherwise passed in here so + # that geturl()/url work on responses created directly via http.client. + self.url = url + def _read_status(self): line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1") if len(line) > _MAXLINE: diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index f771fc48dada368..5c0b21a628a2ab7 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1043,6 +1043,22 @@ def test_response_headers(self): cookies = r.getheader("Set-Cookie") self.assertEqual(cookies, hdr) + def test_url_set_at_init(self): + # bpo-42062: HTTPResponse.url (returned by geturl()) is initialized in + # __init__, so it is available on responses created directly via + # http.client and not only on those returned by urllib. Accessing it + # previously raised AttributeError. + body = 'HTTP/1.1 200 OK\r\n\r\nText' + url = 'http://www.python.org/' + resp = client.HTTPResponse(FakeSocket(body), url=url) + self.assertEqual(resp.url, url) + self.assertEqual(resp.geturl(), url) + + # When no URL is supplied, geturl() returns None instead of raising. + resp = client.HTTPResponse(FakeSocket(body)) + self.assertIsNone(resp.url) + self.assertIsNone(resp.geturl()) + def test_read_head(self): # Test that the library doesn't attempt to read any data # from a HEAD request. (Tickles SF bug #622042.) diff --git a/Misc/NEWS.d/next/Library/2026-06-16-10-04-05.gh-issue-86228.ECtxfP.rst b/Misc/NEWS.d/next/Library/2026-06-16-10-04-05.gh-issue-86228.ECtxfP.rst new file mode 100644 index 000000000000000..69cc6028511cd2e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-16-10-04-05.gh-issue-86228.ECtxfP.rst @@ -0,0 +1,5 @@ +The :attr:`~http.client.HTTPResponse.url` attribute is now always set when an +:class:`~http.client.HTTPResponse` is created. Previously it was left unset on +responses created directly via :mod:`http.client`, so accessing ``url`` (or the +deprecated ``geturl()``) raised :exc:`AttributeError`. Patch by Felipe +Rodrigues.