Skip to content

Commit 92d0bf6

Browse files
committed
Make equal URL objects produce equal hashes
Assisted-by: OpenAI Codex
1 parent 978f2e6 commit 92d0bf6

3 files changed

Lines changed: 49 additions & 16 deletions

File tree

‎src/hyperlink/_url.py‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,11 +1259,10 @@ def __hash__(self):
12591259
# type: () -> int
12601260
return hash(
12611261
(
1262-
self.__class__,
12631262
self.scheme,
12641263
self.userinfo,
12651264
self.host,
1266-
self.path,
1265+
() if self.path in _ROOT_PATHS else self.path,
12671266
self.query,
12681267
self.fragment,
12691268
self.port,
@@ -2379,20 +2378,7 @@ def __ne__(self, other):
23792378

23802379
def __hash__(self):
23812380
# type: () -> int
2382-
return hash(
2383-
(
2384-
self.__class__,
2385-
self.scheme,
2386-
self.userinfo,
2387-
self.host,
2388-
self.path,
2389-
self.query,
2390-
self.fragment,
2391-
self.port,
2392-
self.rooted,
2393-
self.uses_netloc,
2394-
)
2395-
)
2381+
return hash(self.normalize().to_uri())
23962382

23972383
# # Begin Twisted Compat Code
23982384
asURI = to_uri

‎src/hyperlink/test/test_decoded_url.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,33 @@ def test_equality_and_hashability(self):
172172

173173
assert len(durl_map) == 3
174174

175+
def test_hash_inherited_equality(self):
176+
# type: () -> None
177+
class InheritedURL(DecodedURL):
178+
pass
179+
180+
url = DecodedURL.from_text("http://example.com/a")
181+
inherited = InheritedURL.from_text("http://example.com/a")
182+
assert url == inherited
183+
assert hash(url) == hash(inherited)
184+
assert {url: "value"}[inherited] == "value"
185+
186+
def test_hash_equivalent_normalized_urls(self):
187+
# type: () -> None
188+
pairs = [
189+
("http://example.com", "http://example.com/"),
190+
("http://example.com/a", "HTTP://EXAMPLE.COM/a"),
191+
("http://example.com/a", "http://example.com/b/../a"),
192+
("http://example.com/~", "http://example.com/%7E"),
193+
]
194+
for left, right in pairs:
195+
left_url = DecodedURL.from_text(left)
196+
right_url = DecodedURL.from_text(right)
197+
assert left_url == right_url
198+
assert hash(left_url) == hash(right_url)
199+
assert len({left_url, right_url}) == 1
200+
assert {left_url: "value"}[right_url] == "value"
201+
175202
def test_replace_roundtrip(self):
176203
# type: () -> None
177204
durl = DecodedURL.from_text(TOTAL_URL)

‎src/hyperlink/test/test_url.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,26 @@ def test_hash(self):
12951295

12961296
assert hash(URL()) == hash(URL()) # slightly more sanity
12971297

1298+
def test_hash_inherited_equality(self):
1299+
# type: () -> None
1300+
class InheritedURL(URL):
1301+
pass
1302+
1303+
url = URL.from_text("http://example.com/a")
1304+
inherited = InheritedURL.from_text("http://example.com/a")
1305+
assert url == inherited
1306+
assert hash(url) == hash(inherited)
1307+
assert {url: "value"}[inherited] == "value"
1308+
1309+
def test_hash_equivalent_root_paths(self):
1310+
# type: () -> None
1311+
without_slash = URL.from_text("http://example.com")
1312+
with_slash = URL.from_text("http://example.com/")
1313+
assert without_slash == with_slash
1314+
assert hash(without_slash) == hash(with_slash)
1315+
assert len({without_slash, with_slash}) == 1
1316+
assert {without_slash: "value"}[with_slash] == "value"
1317+
12981318
def test_dir(self):
12991319
# type: () -> None
13001320
url = URL()

0 commit comments

Comments
 (0)