Merge pull request #7907 from uranusjr/resolver-refactor-factory

Initial refactoring to decouple candidate and requirement modules
This commit is contained in:
Paul Moore 2020-03-27 14:18:41 +00:00 committed by GitHub
commit 904d1c8c0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 109 deletions

View File

@ -9,7 +9,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .base import Candidate, format_name
if MYPY_CHECK_RUNNING:
from typing import Any, Dict, Optional, Sequence, Set
from typing import Any, Optional, Sequence, Set
from pip._internal.models.link import Link
from pip._internal.operations.prepare import RequirementPreparer
@ -18,33 +18,10 @@ if MYPY_CHECK_RUNNING:
from pip._vendor.packaging.version import _BaseVersion
from pip._vendor.pkg_resources import Distribution
logger = logging.getLogger(__name__)
_CANDIDATE_CACHE = {} # type: Dict[Link, LinkCandidate]
def make_candidate(
link, # type: Link
preparer, # type: RequirementPreparer
parent, # type: InstallRequirement
make_install_req, # type: InstallRequirementProvider
extras # type: Set[str]
):
# type: (...) -> Candidate
if link not in _CANDIDATE_CACHE:
_CANDIDATE_CACHE[link] = LinkCandidate(
link,
preparer,
parent=parent,
make_install_req=make_install_req
)
base = _CANDIDATE_CACHE[link]
if extras:
return ExtrasCandidate(base, extras)
return base
def make_install_req_from_link(link, parent):
# type: (Link, InstallRequirement) -> InstallRequirement
# TODO: Do we need to support editables?

View File

@ -0,0 +1,61 @@
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .candidates import ExtrasCandidate, LinkCandidate
from .requirements import ExplicitRequirement, SpecifierRequirement
if MYPY_CHECK_RUNNING:
from typing import Dict, Set
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.link import Link
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
from pip._internal.resolution.base import InstallRequirementProvider
from .base import Candidate, Requirement
class Factory(object):
def __init__(
self,
finder, # type: PackageFinder
preparer, # type: RequirementPreparer
make_install_req, # type: InstallRequirementProvider
):
# type: (...) -> None
self._finder = finder
self._preparer = preparer
self._make_install_req = make_install_req
self._candidate_cache = {} # type: Dict[Link, LinkCandidate]
def make_candidate(
self,
link, # type: Link
extras, # type: Set[str]
parent, # type: InstallRequirement
):
# type: (...) -> Candidate
if link not in self._candidate_cache:
self._candidate_cache[link] = LinkCandidate(
link,
self._preparer,
parent=parent,
make_install_req=self._make_install_req
)
base = self._candidate_cache[link]
if extras:
return ExtrasCandidate(base, extras)
return base
def make_requirement(self, ireq):
# type: (InstallRequirement) -> Requirement
if ireq.link:
cand = self.make_candidate(ireq.link, extras=set(), parent=ireq)
return ExplicitRequirement(cand)
else:
return SpecifierRequirement(
ireq,
finder=self._finder,
factory=self,
make_install_req=self._make_install_req,
)

View File

@ -2,41 +2,24 @@ from pip._vendor.resolvelib.providers import AbstractProvider
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .requirements import make_requirement
if MYPY_CHECK_RUNNING:
from typing import Any, Optional, Sequence, Tuple, Union
from pip._internal.index.package_finder import PackageFinder
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
from pip._internal.resolution.base import InstallRequirementProvider
from .base import Requirement, Candidate
from .factory import Factory
class PipProvider(AbstractProvider):
def __init__(
self,
finder, # type: PackageFinder
preparer, # type: RequirementPreparer
factory, # type: Factory
ignore_dependencies, # type: bool
make_install_req # type: InstallRequirementProvider
):
# type: (...) -> None
self._finder = finder
self._preparer = preparer
self._factory = factory
self._ignore_dependencies = ignore_dependencies
self._make_install_req = make_install_req
def make_requirement(self, ireq):
# type: (InstallRequirement) -> Requirement
return make_requirement(
ireq,
self._finder,
self._preparer,
self._make_install_req
)
def get_install_requirement(self, c):
# type: (Candidate) -> Optional[InstallRequirement]
@ -69,11 +52,6 @@ class PipProvider(AbstractProvider):
if self._ignore_dependencies:
return []
return [
make_requirement(
r,
self._finder,
self._preparer,
self._make_install_req
)
self._factory.make_requirement(r)
for r in candidate.get_dependencies()
]

View File

@ -3,42 +3,16 @@ from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .base import Requirement, format_name
from .candidates import make_candidate
if MYPY_CHECK_RUNNING:
from typing import Sequence
from pip._internal.index.package_finder import PackageFinder
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
from pip._internal.resolution.base import InstallRequirementProvider
from .base import Candidate
def make_requirement(
ireq, # type: InstallRequirement
finder, # type: PackageFinder
preparer, # type: RequirementPreparer
make_install_req # type: InstallRequirementProvider
):
# type: (...) -> Requirement
if ireq.link:
candidate = make_candidate(
ireq.link,
preparer,
ireq,
make_install_req,
set()
)
return ExplicitRequirement(candidate)
else:
return SpecifierRequirement(
ireq,
finder,
preparer,
make_install_req
)
from .factory import Factory
class ExplicitRequirement(Requirement):
@ -66,14 +40,14 @@ class SpecifierRequirement(Requirement):
self,
ireq, # type: InstallRequirement
finder, # type: PackageFinder
preparer, # type:RequirementPreparer
factory, # type: Factory
make_install_req # type: InstallRequirementProvider
):
# type: (...) -> None
assert ireq.link is None, "This is a link, not a specifier"
self._ireq = ireq
self._factory = factory
self._finder = finder
self._preparer = preparer
self._make_install_req = make_install_req
self.extras = ireq.req.extras
@ -91,12 +65,10 @@ class SpecifierRequirement(Requirement):
hashes=self._ireq.hashes(trust_internet=False),
)
return [
make_candidate(
ican.link,
self._preparer,
self._ireq,
self._make_install_req,
self.extras
self._factory.make_candidate(
link=ican.link,
extras=self.extras,
parent=self._ireq,
)
for ican in found.iter_applicable()
]

View File

@ -9,6 +9,8 @@ from pip._internal.resolution.base import BaseResolver
from pip._internal.resolution.resolvelib.provider import PipProvider
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .factory import Factory
if MYPY_CHECK_RUNNING:
from typing import Dict, List, Optional, Tuple
@ -35,24 +37,24 @@ class Resolver(BaseResolver):
py_version_info=None, # type: Optional[Tuple[int, ...]]
):
super(Resolver, self).__init__()
self.finder = finder
self.preparer = preparer
self.factory = Factory(
finder=finder,
preparer=preparer,
make_install_req=make_install_req,
)
self.ignore_dependencies = ignore_dependencies
self.make_install_req = make_install_req
self._result = None # type: Optional[Result]
def resolve(self, root_reqs, check_supported_wheels):
# type: (List[InstallRequirement], bool) -> RequirementSet
provider = PipProvider(
finder=self.finder,
preparer=self.preparer,
factory=self.factory,
ignore_dependencies=self.ignore_dependencies,
make_install_req=self.make_install_req,
)
reporter = BaseReporter()
resolver = RLResolver(provider, reporter)
requirements = [provider.make_requirement(r) for r in root_reqs]
requirements = [self.factory.make_requirement(r) for r in root_reqs]
self._result = resolver.resolve(requirements)
req_set = RequirementSet(check_supported_wheels=check_supported_wheels)

View File

@ -12,6 +12,7 @@ from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.network.session import PipSession
from pip._internal.req.constructors import install_req_from_req_string
from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.resolution.resolvelib.factory import Factory
from pip._internal.resolution.resolvelib.provider import PipProvider
from pip._internal.utils.temp_dir import TempDirectory, global_tempdir_manager
@ -48,16 +49,23 @@ def preparer(finder):
@pytest.fixture
def provider(finder, preparer):
def factory(finder, preparer):
make_install_req = partial(
install_req_from_req_string,
isolated=False,
wheel_cache=None,
use_pep517=None,
)
yield PipProvider(
yield Factory(
finder=finder,
preparer=preparer,
ignore_dependencies=False,
make_install_req=make_install_req,
)
@pytest.fixture
def provider(factory):
yield PipProvider(
factory=factory,
ignore_dependencies=False,
)

View File

@ -3,7 +3,6 @@ from pip._vendor.resolvelib import BaseReporter, Resolver
from pip._internal.req.constructors import install_req_from_line
from pip._internal.resolution.resolvelib.base import Candidate
from pip._internal.resolution.resolvelib.requirements import make_requirement
from pip._internal.utils.urls import path_to_url
# NOTE: All tests are prefixed `test_rlr` (for "test resolvelib resolver").
@ -49,40 +48,36 @@ def test_cases(data):
yield test_cases
def req_from_line(line):
return make_requirement(install_req_from_line(line))
def test_rlr_requirement_has_name(test_cases, provider):
def test_rlr_requirement_has_name(test_cases, factory):
"""All requirements should have a name"""
for requirement, name, matches in test_cases:
ireq = install_req_from_line(requirement)
req = provider.make_requirement(ireq)
req = factory.make_requirement(ireq)
assert req.name == name
def test_rlr_correct_number_of_matches(test_cases, provider):
def test_rlr_correct_number_of_matches(test_cases, factory):
"""Requirements should return the correct number of candidates"""
for requirement, name, matches in test_cases:
ireq = install_req_from_line(requirement)
req = provider.make_requirement(ireq)
req = factory.make_requirement(ireq)
assert len(req.find_matches()) == matches
def test_rlr_candidates_match_requirement(test_cases, provider):
def test_rlr_candidates_match_requirement(test_cases, factory):
"""Candidates returned from find_matches should satisfy the requirement"""
for requirement, name, matches in test_cases:
ireq = install_req_from_line(requirement)
req = provider.make_requirement(ireq)
req = factory.make_requirement(ireq)
for c in req.find_matches():
assert isinstance(c, Candidate)
assert req.is_satisfied_by(c)
def test_rlr_full_resolve(provider):
def test_rlr_full_resolve(factory, provider):
"""A very basic full resolve"""
ireq = install_req_from_line("simplewheel")
req = provider.make_requirement(ireq)
req = factory.make_requirement(ireq)
r = Resolver(provider, BaseReporter())
result = r.resolve([req])
assert set(result.mapping.keys()) == {'simplewheel'}