Skip to content

Commit c4dcd44

Browse files
committed
Append new query parameters after existing parameters
Existing parameter replacements retain their first position; new names no longer use a negative insertion index. Constraint: Preserve Python 2-compatible source style and both URL types Confidence: high Scope-risk: narrow Tested: 137 tests and doctests; actual URL and DecodedURL calls including 10000 query entries Not-tested: Historical Python interpreters
1 parent 978f2e6 commit c4dcd44

2 files changed

Lines changed: 28 additions & 7 deletions

File tree

src/hyperlink/_url.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,7 +1904,7 @@ def set(self, name, value=None):
19041904
# Preserve the original position of the query key in the list
19051905
q = [(k, v) for (k, v) in self.query if k != name]
19061906
idx = next(
1907-
(i for (i, (k, v)) in enumerate(self.query) if k == name), -1
1907+
(i for (i, (k, v)) in enumerate(self.query) if k == name), len(q)
19081908
)
19091909
q[idx:idx] = [(name, value)]
19101910
return self.replace(query=q)
@@ -2315,7 +2315,7 @@ def set(self, name, value=None):
23152315
"Return a new DecodedURL with query parameter *name* set to *value*"
23162316
query = self.query
23172317
q = [(k, v) for (k, v) in query if k != name]
2318-
idx = next((i for (i, (k, v)) in enumerate(query) if k == name), -1)
2318+
idx = next((i for (i, (k, v)) in enumerate(query) if k == name), len(q))
23192319
q[idx:idx] = [(name, value)]
23202320
return self.replace(query=q)
23212321

src/hyperlink/test/test_url.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import Any, Iterable, Optional, Text, Tuple, cast
1111

1212
from .common import HyperlinkTestCase
13-
from .. import URL, URLParseError
13+
from .. import URL, DecodedURL, URLParseError
1414
from .._url import inet_pton, SCHEME_PORT_MAP
1515

1616

@@ -1317,13 +1317,34 @@ def test_twisted_compat(self):
13171317
def test_set_ordering(self):
13181318
# type: () -> None
13191319

1320-
# TODO
13211320
url = URL.from_text("http://example.com/?a=b&c")
13221321
url = url.set("x", "x")
13231322
url = url.add("x", "y")
1324-
assert url.to_text() == "http://example.com/?a=b&x=x&c&x=y"
1325-
# Would expect:
1326-
# assert url.to_text() == u'http://example.com/?a=b&c&x=x&x=y'
1323+
assert url.to_text() == "http://example.com/?a=b&c&x=x&x=y"
1324+
1325+
def test_set_new_query_parameter(self):
1326+
# type: () -> None
1327+
"""Both URL types append new parameters without moving existing ones."""
1328+
for url_type in (URL, DecodedURL):
1329+
for query in ((), (("a", "1"),), (("a", "1"), ("b", None))):
1330+
url = url_type.from_text("https://example.com/").replace(
1331+
query=query
1332+
)
1333+
result = url.set("new", "value")
1334+
self.assertEqual(result.query, query + (("new", "value"),))
1335+
self.assertEqual(url.query, query)
1336+
1337+
def test_set_existing_query_parameter(self):
1338+
# type: () -> None
1339+
"""Replacing duplicate parameters retains the first occurrence's position."""
1340+
for url_type in (URL, DecodedURL):
1341+
url = url_type.from_text(
1342+
"https://example.com/?a=1&x=old&b=2&x=other"
1343+
)
1344+
result = url.set("x", "new")
1345+
self.assertEqual(
1346+
result.query, (("a", "1"), ("x", "new"), ("b", "2"))
1347+
)
13271348

13281349
def test_schemeless_path(self):
13291350
# type: () -> None

0 commit comments

Comments
 (0)