Merge pull request #7979 from uranusjr/editable

Implement editable candidate
This commit is contained in:
Paul Moore 2020-04-04 23:22:55 +01:00 committed by GitHub
commit d9b3a7cc32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 9 deletions

View File

@ -5,7 +5,10 @@ from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import Version
from pip._internal.req.constructors import install_req_from_line
from pip._internal.req.constructors import (
install_req_from_editable,
install_req_from_line,
)
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.misc import normalize_version_info
from pip._internal.utils.packaging import get_requires_python
@ -25,13 +28,19 @@ if MYPY_CHECK_RUNNING:
from .base import Requirement
from .factory import Factory
BaseCandidate = Union[
"AlreadyInstalledCandidate",
"EditableCandidate",
"LinkCandidate",
]
logger = logging.getLogger(__name__)
def make_install_req_from_link(link, parent):
# type: (Link, InstallRequirement) -> InstallRequirement
# TODO: Do we need to support editables?
assert not parent.editable, "parent is editable"
return install_req_from_line(
link.url,
comes_from=parent.comes_from,
@ -46,9 +55,28 @@ def make_install_req_from_link(link, parent):
)
def make_install_req_from_editable(link, parent):
# type: (Link, InstallRequirement) -> InstallRequirement
assert parent.editable, "parent not editable"
return install_req_from_editable(
link.url,
# HACK: install_req_from_editable accepts Optional[str] here, but
# parent.comes_from is Union[str, InstallRequirement, None]. How do
# we fix the type hint conflicts?
comes_from=parent.comes_from, # type: ignore
use_pep517=parent.use_pep517,
isolated=parent.isolated,
constraint=parent.constraint,
options=dict(
install_options=parent.install_options,
global_options=parent.global_options,
hashes=parent.hash_options
),
)
def make_install_req_from_dist(dist, parent):
# type: (Distribution, InstallRequirement) -> InstallRequirement
# TODO: Do we need to support editables?
ireq = install_req_from_line(
"{}=={}".format(
canonicalize_name(dist.project_name),
@ -192,6 +220,29 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
return self._factory.preparer.prepare_linked_requirement(self._ireq)
class EditableCandidate(_InstallRequirementBackedCandidate):
def __init__(
self,
link, # type: Link
parent, # type: InstallRequirement
factory, # type: Factory
name=None, # type: Optional[str]
version=None, # type: Optional[_BaseVersion]
):
# type: (...) -> None
super(EditableCandidate, self).__init__(
link=link,
ireq=make_install_req_from_editable(link, parent),
factory=factory,
name=name,
version=version,
)
def _get_abstract_distribution(self):
# type: () -> AbstractDistribution
return self._factory.preparer.prepare_editable_requirement(self._ireq)
class AlreadyInstalledCandidate(Candidate):
def __init__(
self,
@ -259,7 +310,7 @@ class ExtrasCandidate(Candidate):
"""
def __init__(
self,
base, # type: Union[AlreadyInstalledCandidate, LinkCandidate]
base, # type: BaseCandidate
extras, # type: Set[str]
):
# type: (...) -> None

View File

@ -8,6 +8,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .candidates import (
AlreadyInstalledCandidate,
EditableCandidate,
ExtrasCandidate,
LinkCandidate,
RequiresPythonCandidate,
@ -33,6 +34,7 @@ if MYPY_CHECK_RUNNING:
from pip._internal.resolution.base import InstallRequirementProvider
from .base import Candidate, Requirement
from .candidates import BaseCandidate
C = TypeVar("C")
Cache = Dict[Link, C]
@ -56,6 +58,7 @@ class Factory(object):
self._ignore_installed = ignore_installed
self._ignore_requires_python = ignore_requires_python
self._link_candidate_cache = {} # type: Cache[LinkCandidate]
self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
def _make_candidate_from_dist(
self,
@ -78,11 +81,18 @@ class Factory(object):
version=None, # type: Optional[_BaseVersion]
):
# type: (...) -> Candidate
if link not in self._link_candidate_cache:
self._link_candidate_cache[link] = LinkCandidate(
link, parent, factory=self, name=name, version=version,
)
base = self._link_candidate_cache[link]
if parent.editable:
if link not in self._editable_candidate_cache:
self._editable_candidate_cache[link] = EditableCandidate(
link, parent, factory=self, name=name, version=version,
)
base = self._editable_candidate_cache[link] # type: BaseCandidate
else:
if link not in self._link_candidate_cache:
self._link_candidate_cache[link] = LinkCandidate(
link, parent, factory=self, name=name, version=version,
)
base = self._link_candidate_cache[link]
if extras:
return ExtrasCandidate(base, extras)
return base

View File

@ -1,10 +1,12 @@
import json
import os
import pytest
from tests.lib import (
create_basic_sdist_for_package,
create_basic_wheel_for_package,
create_test_package_with_setup,
)
@ -27,6 +29,15 @@ def assert_not_installed(script, *args):
"{!r} contained in {!r}".format(args, installed)
def assert_editable(script, *args):
# This simply checks whether all of the listed packages have a
# corresponding .egg-link file installed.
# TODO: Implement a more rigorous way to test for editable installations.
egg_links = set("{}.egg-link".format(arg) for arg in args)
assert egg_links <= set(os.listdir(script.site_packages_path)), \
"{!r} not all found in {!r}".format(args, script.site_packages_path)
def test_new_resolver_can_install(script):
create_basic_wheel_for_package(
script,
@ -144,6 +155,29 @@ def test_new_resolver_installs_extras(script):
assert_installed(script, base="0.1.0", dep="0.1.0")
def test_new_resolver_installs_editable(script):
create_basic_wheel_for_package(
script,
"base",
"0.1.0",
depends=["dep"],
)
source_dir = create_test_package_with_setup(
script,
name="dep",
version="0.1.0",
)
script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"base",
"--editable", source_dir,
)
assert_installed(script, base="0.1.0", dep="0.1.0")
assert_editable(script, "dep")
@pytest.mark.parametrize(
"requires_python, ignore_requires_python, dep_version",
[