Decouple candidate and requirement modules

This introduces a new module "factory" that contains all methods dealing
with producing candidates/requirements from an input
requirement/candidate. This allows both models to know nothing about
each other, and simply rely on the intermediate to produce the other.

I *believe* this also helps us reduce merge conflicts due to adding
arguments to those producer functions, since now we only need to modify
the factory, and exactly one of candidate/requirement.

This is only part of a big scheme--the plan is to also move
Candidate.get_dependencies() and Requirement.find_matches() into the
factory class, so they can avoid holding and passing around finder,
preparer, and make_install_req. This is also necessary to change the
return type of Candidate.get_dependencies() to Requirement without
hard-coupling.
This commit is contained in:
Tzu-ping Chung 2020-03-27 03:14:51 +08:00
parent c3d86200b0
commit 07563847b0
5 changed files with 85 additions and 91 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,62 @@
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,
preparer=self._preparer,
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,7 +3,6 @@ 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
@ -14,31 +13,7 @@ if MYPY_CHECK_RUNNING:
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,12 +41,14 @@ class SpecifierRequirement(Requirement):
self,
ireq, # type: InstallRequirement
finder, # type: PackageFinder
preparer, # type:RequirementPreparer
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
@ -91,12 +68,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)