fix(requirements): make Requirement.__hash__ consistent with __eq__ for trailing-zero-equivalent specifiers - #1232
Conversation
Equal Requirements with trailing-zero-equivalent specifiers (e.g. foo==1.0.0 and foo==1.0.0.0) compared equal but hashed differently, so they failed to deduplicate in sets and dicts. __hash__ hashed the non-canonical str(self.specifier) via _iter_parts, while __eq__ compares the canonical SpecifierSet. Hash the same components __eq__ uses.
1ac700f to
fa40f9d
Compare
|
Looks fine to me, also checked with Claude. @notatallshaw would you mind verifying too? 🤖 Claude review 🤖
All tests pass. Here's my review. Code Review: PR #1232 —
|
|
I believe this is one of the bugs listed in #1239 (found after this PR) |
|
Thanks for the PR. |
Requirementis a public hashable class, but__hash__violated thea == b ⇒ hash(a) == hash(b)invariant for trailing-zero-equivalent specifiers:__hash__hashedstr(self.specifier)(non-canonical) via_iter_parts, while__eq__compares the canonicalSpecifierSet. So equal requirements silently failed to deduplicate in sets/dicts used by resolvers and lockfile tooling. The existingtest_equivalent_reqs_equal_hashes_unequal_stringsdocstring already states equivalent reqs should share a hash.Fix: hash the same components
__eq__uses (canonical name, extras, theSpecifierSetobject, url, marker) instead of the rendered string.str(req)/repr(req)are unchanged (they still use_iter_parts).Regression tests added to
EQUIVALENT_DEPENDENCIES(trailing-zero specifiers, alone and combined with extras + marker); all fail onmain, pass with the fix. Full suite: 5309 passing.