Skip to content

Commit

Permalink
Merge pull request #12657 from notatallshaw/eagerly-calculate-strings…
Browse files Browse the repository at this point in the history
…-and-hashes-for-requirements-and-candidate
  • Loading branch information
uranusjr authored Apr 30, 2024
2 parents f18bebd + 1e510e3 commit 411b981
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 7 deletions.
1 change: 1 addition & 0 deletions news/12657.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Further improve resolution performance of large dependency trees, by caching hash calculations.
7 changes: 6 additions & 1 deletion src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def __init__(
self._name = name
self._version = version
self.dist = self._prepare()
self._hash: Optional[int] = None

def __str__(self) -> str:
return f"{self.name} {self.version}"
Expand All @@ -162,7 +163,11 @@ def __repr__(self) -> str:
return f"{self.__class__.__name__}({str(self._link)!r})"

def __hash__(self) -> int:
return hash((self.__class__, self._link))
if self._hash is not None:
return self._hash

self._hash = hash((self.__class__, self._link))
return self._hash

def __eq__(self, other: Any) -> bool:
if isinstance(other, self.__class__):
Expand Down
45 changes: 39 additions & 6 deletions src/pip/_internal/resolution/resolvelib/requirements.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any
from typing import Any, Optional

from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
Expand Down Expand Up @@ -51,8 +51,18 @@ class SpecifierRequirement(Requirement):
def __init__(self, ireq: InstallRequirement) -> None:
assert ireq.link is None, "This is a link, not a specifier"
self._ireq = ireq
self._equal_cache: Optional[str] = None
self._hash: Optional[int] = None
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)

@property
def _equal(self) -> str:
if self._equal_cache is not None:
return self._equal_cache

self._equal_cache = str(self._ireq)
return self._equal_cache

def __str__(self) -> str:
return str(self._ireq.req)

Expand All @@ -62,10 +72,14 @@ def __repr__(self) -> str:
def __eq__(self, other: object) -> bool:
if not isinstance(other, SpecifierRequirement):
return NotImplemented
return str(self._ireq) == str(other._ireq)
return self._equal == other._equal

def __hash__(self) -> int:
return hash(str(self._ireq))
if self._hash is not None:
return self._hash

self._hash = hash(self._equal)
return self._hash

@property
def project_name(self) -> NormalizedName:
Expand Down Expand Up @@ -114,15 +128,29 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
def __init__(self, ireq: InstallRequirement) -> None:
assert ireq.link is None, "This is a link, not a specifier"
self._ireq = install_req_drop_extras(ireq)
self._equal_cache: Optional[str] = None
self._hash: Optional[int] = None
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)

@property
def _equal(self) -> str:
if self._equal_cache is not None:
return self._equal_cache

self._equal_cache = str(self._ireq)
return self._equal_cache

def __eq__(self, other: object) -> bool:
if not isinstance(other, SpecifierWithoutExtrasRequirement):
return NotImplemented
return str(self._ireq) == str(other._ireq)
return self._equal == other._equal

def __hash__(self) -> int:
return hash(str(self._ireq))
if self._hash is not None:
return self._hash

self._hash = hash(self._equal)
return self._hash


class RequiresPythonRequirement(Requirement):
Expand All @@ -131,6 +159,7 @@ class RequiresPythonRequirement(Requirement):
def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
self.specifier = specifier
self._specifier_string = str(specifier) # for faster __eq__
self._hash: Optional[int] = None
self._candidate = match

def __str__(self) -> str:
Expand All @@ -140,7 +169,11 @@ def __repr__(self) -> str:
return f"{self.__class__.__name__}({str(self.specifier)!r})"

def __hash__(self) -> int:
return hash((self._specifier_string, self._candidate))
if self._hash is not None:
return self._hash

self._hash = hash((self._specifier_string, self._candidate))
return self._hash

def __eq__(self, other: Any) -> bool:
if not isinstance(other, RequiresPythonRequirement):
Expand Down

0 comments on commit 411b981

Please sign in to comment.