From 435551d0c4ee18e0a046506fe9bf925718bd94ba Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 7 Aug 2026 22:44:25 +0200 Subject: [PATCH] gh-155358: Use named attributes with urllib.parse module urlparse(), replace: * parts[0] => parts.scheme * parts[1] => parts.netloc * parts[2] => parts.path * parts[3] => parts.query * parts[4] => parts.fragment urlsplit(), replace: * parts[0] => parts.scheme * parts[1] => parts.netloc * parts[2] => parts.path * parts[3] => parts.query * parts[4] => parts.fragment --- Lib/http/cookiejar.py | 2 +- Lib/http/server.py | 4 ++-- Lib/test/ssl_servers.py | 2 +- Lib/test/support/__init__.py | 2 +- Lib/urllib/request.py | 16 +++++++++------- Lib/urllib/robotparser.py | 4 +++- Lib/xmlrpc/client.py | 2 +- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py index 13e5b104a81ea2..eea60c4285d362 100644 --- a/Lib/http/cookiejar.py +++ b/Lib/http/cookiejar.py @@ -626,7 +626,7 @@ def request_host(request): """ url = request.get_full_url() - host = urllib.parse.urlparse(url)[1] + host = urllib.parse.urlparse(url).netloc if host == "": host = request.get_header("Host", "") diff --git a/Lib/http/server.py b/Lib/http/server.py index 095b5744bd12fc..506cf63934b1c3 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -796,8 +796,8 @@ def send_head(self): if not parts.path.endswith(('/', '%2f', '%2F')): # redirect browser - doing basically what apache does self.send_response(HTTPStatus.MOVED_PERMANENTLY) - new_parts = (parts[0], parts[1], parts[2] + '/', - parts[3], parts[4]) + new_parts = (parts.scheme, parts.netloc, parts.path + '/', + parts.query, parts.fragment) new_url = urllib.parse.urlunsplit(new_parts) self.send_header("Location", new_url) self.send_header("Content-Length", "0") diff --git a/Lib/test/ssl_servers.py b/Lib/test/ssl_servers.py index 15b071e04dda1f..e3416a822f6525 100644 --- a/Lib/test/ssl_servers.py +++ b/Lib/test/ssl_servers.py @@ -61,7 +61,7 @@ def translate_path(self, path): """ # abandon query parameters - path = urllib.parse.urlparse(path)[2] + path = urllib.parse.urlparse(path).path path = os.path.normpath(urllib.parse.unquote(path)) words = path.split('/') words = filter(None, words) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 74d3794289bf69..a0b57779afbea7 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -868,7 +868,7 @@ def open_urlresource(url, *args, **kw): check = kw.pop('check', None) - filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL! + filename = urllib.parse.urlparse(url).path.split('/')[-1] # '/': it's URL! fn = os.path.join(TEST_DATA_DIR, filename) diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index 660301fef61258..a58923c3087517 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -274,7 +274,7 @@ def request_host(request): """ url = request.full_url - host = urlparse(url)[1] + host = urlparse(url).netloc if host == "": host = request.get_header("Host", "") @@ -832,11 +832,11 @@ def reduce_uri(self, uri, default_port=True): """Accept authority or URI and extract only the authority and path.""" # note HTTP URLs do not have a userinfo component parts = urlsplit(uri) - if parts[1]: + if parts.netloc: # URI - scheme = parts[0] - authority = parts[1] - path = parts[2] or '/' + scheme = parts.scheme + authority = parts.netloc + path = parts.path or '/' else: # host or host:port scheme = None @@ -1209,7 +1209,7 @@ class HTTPDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler): handler_order = 490 # before Basic auth def http_error_401(self, req, fp, code, msg, headers): - host = urlparse(req.full_url)[1] + host = urlparse(req.full_url).netloc retry = self.http_error_auth_reqed('www-authenticate', host, req, headers) self.reset_retry_count() @@ -1668,7 +1668,9 @@ def url2pathname(url, *, require_scheme=False, resolve_host=False): """ if not require_scheme: url = 'file:' + url - scheme, authority, url = urlsplit(url)[:3] # Discard query and fragment. + parts = urlsplit(url) + # Discard query and fragment. + scheme, authority, url = parts.scheme, parts.netloc, parts.path if scheme != 'file': raise URLError("URL is missing a 'file:' scheme") if os.name == 'nt': diff --git a/Lib/urllib/robotparser.py b/Lib/urllib/robotparser.py index 8d0311d96f5e0b..985333c7100438 100644 --- a/Lib/urllib/robotparser.py +++ b/Lib/urllib/robotparser.py @@ -62,7 +62,9 @@ def set_url(self, url): if isinstance(url, urllib.request.Request): url = url.full_url - self.host, self.path = urllib.parse.urlsplit(url)[1:3] + parts = urllib.parse.urlsplit(url) + self.host = parts.netloc + self.path = parts.path def read(self): """Reads the robots.txt URL and feeds it to the parser.""" diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py index 84e4e4d11a7319..418ffbfecb82f5 100644 --- a/Lib/xmlrpc/client.py +++ b/Lib/xmlrpc/client.py @@ -1402,7 +1402,7 @@ def __init__(self, uri, transport=None, encoding=None, verbose=False, if p.scheme not in ("http", "https"): raise OSError("unsupported XML-RPC protocol") self.__host = p.netloc - self.__handler = urllib.parse.urlunsplit(["", "", *p[2:]]) + self.__handler = urllib.parse.urlunsplit(["", "", p.path, p.query, p.fragment]) if not self.__handler: self.__handler = "/RPC2"