mirror of https://github.com/pypa/pip
Merge pull request #7979 from uranusjr/editable
Implement editable candidate
This commit is contained in:
commit
d9b3a7cc32
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
[
|
||||
|
|
Loading…
Reference in New Issue