diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index b361e194d75..9f39631dde2 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -863,6 +863,7 @@ def make_candidate_evaluator( hashes=hashes, ) + @lru_cache(maxsize=None) def find_best_candidate( self, project_name, # type: str diff --git a/src/pip/_internal/utils/hashes.py b/src/pip/_internal/utils/hashes.py index b306dafe7d7..4d90f5bfda4 100644 --- a/src/pip/_internal/utils/hashes.py +++ b/src/pip/_internal/utils/hashes.py @@ -39,7 +39,12 @@ def __init__(self, hashes=None): :param hashes: A dict of algorithm names pointing to lists of allowed hex digests """ - self._allowed = {} if hashes is None else hashes + allowed = {} + if hashes is not None: + for alg, keys in hashes.items(): + # Make sure values are always sorted (to ease equality checks) + allowed[alg] = sorted(keys) + self._allowed = allowed def __and__(self, other): # type: (Hashes) -> Hashes @@ -128,6 +133,22 @@ def __bool__(self): # type: () -> bool return self.__nonzero__() + def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, Hashes): + return NotImplemented + return self._allowed == other._allowed + + def __hash__(self): + # type: () -> int + return hash( + ",".join(sorted( + ":".join((alg, digest)) + for alg, digest_list in self._allowed.items() + for digest in digest_list + )) + ) + class MissingHashes(Hashes): """A workalike for Hashes used when we're missing a hash for a requirement diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 14b4d74820f..1996b35cb37 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -541,6 +541,16 @@ def test_non_zero(self): assert not Hashes() assert not Hashes({}) + def test_equality(self): + assert Hashes() == Hashes() + assert Hashes({'sha256': ['abcd']}) == Hashes({'sha256': ['abcd']}) + assert Hashes({'sha256': ['ab', 'cd']}) == Hashes({'sha256': ['cd', 'ab']}) + + def test_hash(self): + cache = {} + cache[Hashes({'sha256': ['ab', 'cd']})] = 42 + assert cache[Hashes({'sha256': ['ab', 'cd']})] == 42 + class TestEncoding(object): """Tests for pip._internal.utils.encoding"""