Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache calculated hashes for requirements and _InstallRequirementBackedCandidate #12657

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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