update
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import collections
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Generator, List, Optional, Sequence, Tuple
|
||||
|
||||
from pip._internal.utils.logging import indent_log
|
||||
@@ -18,12 +19,9 @@ __all__ = [
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InstallationResult:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"InstallationResult(name={self.name!r})"
|
||||
name: str
|
||||
|
||||
|
||||
def _validate_requirements(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,10 +8,12 @@ These are meant to be used elsewhere within pip to create instances of
|
||||
InstallRequirement.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Optional, Set, Tuple, Union
|
||||
from dataclasses import dataclass
|
||||
from typing import Collection, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
@@ -57,6 +59,31 @@ def convert_extras(extras: Optional[str]) -> Set[str]:
|
||||
return get_requirement("placeholder" + extras.lower()).extras
|
||||
|
||||
|
||||
def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requirement:
|
||||
"""
|
||||
Returns a new requirement based on the given one, with the supplied extras. If the
|
||||
given requirement already has extras those are replaced (or dropped if no new extras
|
||||
are given).
|
||||
"""
|
||||
match: Optional[re.Match[str]] = re.fullmatch(
|
||||
# see https://peps.python.org/pep-0508/#complete-grammar
|
||||
r"([\w\t .-]+)(\[[^\]]*\])?(.*)",
|
||||
str(req),
|
||||
flags=re.ASCII,
|
||||
)
|
||||
# ireq.req is a valid requirement so the regex should always match
|
||||
assert (
|
||||
match is not None
|
||||
), f"regex match on requirement {req} failed, this should never happen"
|
||||
pre: Optional[str] = match.group(1)
|
||||
post: Optional[str] = match.group(3)
|
||||
assert (
|
||||
pre is not None and post is not None
|
||||
), f"regex group selection for requirement {req} failed, this should never happen"
|
||||
extras: str = "[%s]" % ",".join(sorted(new_extras)) if new_extras else ""
|
||||
return get_requirement(f"{pre}{extras}{post}")
|
||||
|
||||
|
||||
def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
|
||||
"""Parses an editable requirement into:
|
||||
- a requirement name
|
||||
@@ -106,8 +133,8 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
|
||||
package_name = link.egg_fragment
|
||||
if not package_name:
|
||||
raise InstallationError(
|
||||
"Could not detect requirement name for '{}', please specify one "
|
||||
"with #egg=your_package_name".format(editable_req)
|
||||
f"Could not detect requirement name for '{editable_req}', "
|
||||
"please specify one with #egg=your_package_name"
|
||||
)
|
||||
return package_name, url, set()
|
||||
|
||||
@@ -136,7 +163,7 @@ def check_first_requirement_in_file(filename: str) -> None:
|
||||
# If there is a line continuation, drop it, and append the next line.
|
||||
if line.endswith("\\"):
|
||||
line = line[:-2].strip() + next(lines, "")
|
||||
Requirement(line)
|
||||
get_requirement(line)
|
||||
return
|
||||
|
||||
|
||||
@@ -165,18 +192,12 @@ def deduce_helpful_msg(req: str) -> str:
|
||||
return msg
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RequirementParts:
|
||||
def __init__(
|
||||
self,
|
||||
requirement: Optional[Requirement],
|
||||
link: Optional[Link],
|
||||
markers: Optional[Marker],
|
||||
extras: Set[str],
|
||||
):
|
||||
self.requirement = requirement
|
||||
self.link = link
|
||||
self.markers = markers
|
||||
self.extras = extras
|
||||
requirement: Optional[Requirement]
|
||||
link: Optional[Link]
|
||||
markers: Optional[Marker]
|
||||
extras: Set[str]
|
||||
|
||||
|
||||
def parse_req_from_editable(editable_req: str) -> RequirementParts:
|
||||
@@ -184,9 +205,9 @@ def parse_req_from_editable(editable_req: str) -> RequirementParts:
|
||||
|
||||
if name is not None:
|
||||
try:
|
||||
req: Optional[Requirement] = Requirement(name)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError(f"Invalid requirement: '{name}'")
|
||||
req: Optional[Requirement] = get_requirement(name)
|
||||
except InvalidRequirement as exc:
|
||||
raise InstallationError(f"Invalid requirement: {name!r}: {exc}")
|
||||
else:
|
||||
req = None
|
||||
|
||||
@@ -338,8 +359,8 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
|
||||
|
||||
def _parse_req_string(req_as_string: str) -> Requirement:
|
||||
try:
|
||||
req = get_requirement(req_as_string)
|
||||
except InvalidRequirement:
|
||||
return get_requirement(req_as_string)
|
||||
except InvalidRequirement as exc:
|
||||
if os.path.sep in req_as_string:
|
||||
add_msg = "It looks like a path."
|
||||
add_msg += deduce_helpful_msg(req_as_string)
|
||||
@@ -349,21 +370,10 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
|
||||
add_msg = "= is not a valid operator. Did you mean == ?"
|
||||
else:
|
||||
add_msg = ""
|
||||
msg = with_source(f"Invalid requirement: {req_as_string!r}")
|
||||
msg = with_source(f"Invalid requirement: {req_as_string!r}: {exc}")
|
||||
if add_msg:
|
||||
msg += f"\nHint: {add_msg}"
|
||||
raise InstallationError(msg)
|
||||
else:
|
||||
# Deprecate extras after specifiers: "name>=1.0[extras]"
|
||||
# This currently works by accident because _strip_extras() parses
|
||||
# any extras in the end of the string and those are saved in
|
||||
# RequirementParts
|
||||
for spec in req.specifier:
|
||||
spec_str = str(spec)
|
||||
if spec_str.endswith("]"):
|
||||
msg = f"Extras after version '{spec_str}'."
|
||||
raise InstallationError(msg)
|
||||
return req
|
||||
|
||||
if req_as_string is not None:
|
||||
req: Optional[Requirement] = _parse_req_string(req_as_string)
|
||||
@@ -419,8 +429,8 @@ def install_req_from_req_string(
|
||||
) -> InstallRequirement:
|
||||
try:
|
||||
req = get_requirement(req_string)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError(f"Invalid requirement: '{req_string}'")
|
||||
except InvalidRequirement as exc:
|
||||
raise InstallationError(f"Invalid requirement: {req_string!r}: {exc}")
|
||||
|
||||
domains_not_allowed = [
|
||||
PyPI.file_storage_domain,
|
||||
@@ -436,7 +446,7 @@ def install_req_from_req_string(
|
||||
raise InstallationError(
|
||||
"Packages installed from PyPI cannot depend on packages "
|
||||
"which are not also hosted on PyPI.\n"
|
||||
"{} depends on {} ".format(comes_from.name, req)
|
||||
f"{comes_from.name} depends on {req} "
|
||||
)
|
||||
|
||||
return InstallRequirement(
|
||||
@@ -504,3 +514,47 @@ def install_req_from_link_and_ireq(
|
||||
config_settings=ireq.config_settings,
|
||||
user_supplied=ireq.user_supplied,
|
||||
)
|
||||
|
||||
|
||||
def install_req_drop_extras(ireq: InstallRequirement) -> InstallRequirement:
|
||||
"""
|
||||
Creates a new InstallationRequirement using the given template but without
|
||||
any extras. Sets the original requirement as the new one's parent
|
||||
(comes_from).
|
||||
"""
|
||||
return InstallRequirement(
|
||||
req=(
|
||||
_set_requirement_extras(ireq.req, set()) if ireq.req is not None else None
|
||||
),
|
||||
comes_from=ireq,
|
||||
editable=ireq.editable,
|
||||
link=ireq.link,
|
||||
markers=ireq.markers,
|
||||
use_pep517=ireq.use_pep517,
|
||||
isolated=ireq.isolated,
|
||||
global_options=ireq.global_options,
|
||||
hash_options=ireq.hash_options,
|
||||
constraint=ireq.constraint,
|
||||
extras=[],
|
||||
config_settings=ireq.config_settings,
|
||||
user_supplied=ireq.user_supplied,
|
||||
permit_editable_wheels=ireq.permit_editable_wheels,
|
||||
)
|
||||
|
||||
|
||||
def install_req_extend_extras(
|
||||
ireq: InstallRequirement,
|
||||
extras: Collection[str],
|
||||
) -> InstallRequirement:
|
||||
"""
|
||||
Returns a copy of an installation requirement with some additional extras.
|
||||
Makes a shallow copy of the ireq object.
|
||||
"""
|
||||
result = copy.copy(ireq)
|
||||
result.extras = {*ireq.extras, *extras}
|
||||
result.req = (
|
||||
_set_requirement_extras(ireq.req, result.extras)
|
||||
if ireq.req is not None
|
||||
else None
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -17,6 +17,7 @@ from typing import (
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Tuple,
|
||||
)
|
||||
@@ -24,17 +25,11 @@ from typing import (
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.exceptions import InstallationError, RequirementsFileParseError
|
||||
from pip._internal.models.search_scope import SearchScope
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.network.utils import raise_for_status
|
||||
from pip._internal.utils.encoding import auto_decode
|
||||
from pip._internal.utils.urls import get_url_scheme
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# NoReturn introduced in 3.6.2; imported only for type checking to maintain
|
||||
# pip compatibility with older patch versions of Python 3.6
|
||||
from typing import NoReturn
|
||||
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.network.session import PipSession
|
||||
|
||||
__all__ = ["parse_requirements"]
|
||||
|
||||
@@ -75,8 +70,16 @@ SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
|
||||
cmdoptions.config_settings,
|
||||
]
|
||||
|
||||
SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [
|
||||
cmdoptions.config_settings,
|
||||
]
|
||||
|
||||
|
||||
# the 'dest' string values
|
||||
SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ]
|
||||
SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
|
||||
str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -128,7 +131,7 @@ class ParsedLine:
|
||||
|
||||
def parse_requirements(
|
||||
filename: str,
|
||||
session: PipSession,
|
||||
session: "PipSession",
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
options: Optional[optparse.Values] = None,
|
||||
constraint: bool = False,
|
||||
@@ -178,31 +181,25 @@ def handle_requirement_line(
|
||||
|
||||
assert line.is_requirement
|
||||
|
||||
# get the options that apply to requirements
|
||||
if line.is_editable:
|
||||
# For editable requirements, we don't support per-requirement
|
||||
# options, so just return the parsed requirement.
|
||||
return ParsedRequirement(
|
||||
requirement=line.requirement,
|
||||
is_editable=line.is_editable,
|
||||
comes_from=line_comes_from,
|
||||
constraint=line.constraint,
|
||||
)
|
||||
supported_dest = SUPPORTED_OPTIONS_EDITABLE_REQ_DEST
|
||||
else:
|
||||
# get the options that apply to requirements
|
||||
req_options = {}
|
||||
for dest in SUPPORTED_OPTIONS_REQ_DEST:
|
||||
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
|
||||
req_options[dest] = line.opts.__dict__[dest]
|
||||
supported_dest = SUPPORTED_OPTIONS_REQ_DEST
|
||||
req_options = {}
|
||||
for dest in supported_dest:
|
||||
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
|
||||
req_options[dest] = line.opts.__dict__[dest]
|
||||
|
||||
line_source = f"line {line.lineno} of {line.filename}"
|
||||
return ParsedRequirement(
|
||||
requirement=line.requirement,
|
||||
is_editable=line.is_editable,
|
||||
comes_from=line_comes_from,
|
||||
constraint=line.constraint,
|
||||
options=req_options,
|
||||
line_source=line_source,
|
||||
)
|
||||
line_source = f"line {line.lineno} of {line.filename}"
|
||||
return ParsedRequirement(
|
||||
requirement=line.requirement,
|
||||
is_editable=line.is_editable,
|
||||
comes_from=line_comes_from,
|
||||
constraint=line.constraint,
|
||||
options=req_options,
|
||||
line_source=line_source,
|
||||
)
|
||||
|
||||
|
||||
def handle_option_line(
|
||||
@@ -211,7 +208,7 @@ def handle_option_line(
|
||||
lineno: int,
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
options: Optional[optparse.Values] = None,
|
||||
session: Optional[PipSession] = None,
|
||||
session: Optional["PipSession"] = None,
|
||||
) -> None:
|
||||
if opts.hashes:
|
||||
logger.warning(
|
||||
@@ -279,7 +276,7 @@ def handle_line(
|
||||
line: ParsedLine,
|
||||
options: Optional[optparse.Values] = None,
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
session: Optional[PipSession] = None,
|
||||
session: Optional["PipSession"] = None,
|
||||
) -> Optional[ParsedRequirement]:
|
||||
"""Handle a single parsed requirements line; This can result in
|
||||
creating/yielding requirements, or updating the finder.
|
||||
@@ -322,7 +319,7 @@ def handle_line(
|
||||
class RequirementsFileParser:
|
||||
def __init__(
|
||||
self,
|
||||
session: PipSession,
|
||||
session: "PipSession",
|
||||
line_parser: LineParser,
|
||||
) -> None:
|
||||
self._session = session
|
||||
@@ -527,7 +524,7 @@ def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines:
|
||||
yield line_number, line
|
||||
|
||||
|
||||
def get_file_content(url: str, session: PipSession) -> Tuple[str, str]:
|
||||
def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]:
|
||||
"""Gets the content of a file; it may be a filename, file: URL, or
|
||||
http: URL. Returns (location, content). Content is unicode.
|
||||
Respects # -*- coding: declarations on the retrieved files.
|
||||
@@ -535,10 +532,12 @@ def get_file_content(url: str, session: PipSession) -> Tuple[str, str]:
|
||||
:param url: File path or url.
|
||||
:param session: PipSession instance.
|
||||
"""
|
||||
scheme = get_url_scheme(url)
|
||||
|
||||
scheme = urllib.parse.urlsplit(url).scheme
|
||||
# Pip has special support for file:// URLs (LocalFSAdapter).
|
||||
if scheme in ["http", "https", "file"]:
|
||||
# Delay importing heavy network modules until absolutely necessary.
|
||||
from pip._internal.network.utils import raise_for_status
|
||||
|
||||
resp = session.get(url)
|
||||
raise_for_status(resp)
|
||||
return resp.url, resp.text
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
@@ -9,6 +6,7 @@ import sys
|
||||
import uuid
|
||||
import zipfile
|
||||
from optparse import Values
|
||||
from pathlib import Path
|
||||
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
|
||||
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
@@ -20,7 +18,7 @@ from pip._vendor.packaging.version import parse as parse_version
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import InstallationError, PreviousBuildDirError
|
||||
from pip._internal.locations import get_scheme
|
||||
from pip._internal.metadata import (
|
||||
BaseDistribution,
|
||||
@@ -50,11 +48,14 @@ from pip._internal.utils.misc import (
|
||||
backup_dir,
|
||||
display_path,
|
||||
hide_url,
|
||||
is_installable_dir,
|
||||
redact_auth_from_requirement,
|
||||
redact_auth_from_url,
|
||||
)
|
||||
from pip._internal.utils.packaging import safe_extra
|
||||
from pip._internal.utils.packaging import get_requirement
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
from pip._internal.utils.unpacking import unpack_file
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._internal.vcs import vcs
|
||||
|
||||
@@ -128,7 +129,7 @@ class InstallRequirement:
|
||||
if extras:
|
||||
self.extras = extras
|
||||
elif req:
|
||||
self.extras = {safe_extra(extra) for extra in req.extras}
|
||||
self.extras = req.extras
|
||||
else:
|
||||
self.extras = set()
|
||||
if markers is None and req:
|
||||
@@ -180,14 +181,27 @@ class InstallRequirement:
|
||||
# but after loading this flag should be treated as read only.
|
||||
self.use_pep517 = use_pep517
|
||||
|
||||
# If config settings are provided, enforce PEP 517.
|
||||
if self.config_settings:
|
||||
if self.use_pep517 is False:
|
||||
logger.warning(
|
||||
"--no-use-pep517 ignored for %s "
|
||||
"because --config-settings are specified.",
|
||||
self,
|
||||
)
|
||||
self.use_pep517 = True
|
||||
|
||||
# This requirement needs more preparation before it can be built
|
||||
self.needs_more_preparation = False
|
||||
|
||||
# This requirement needs to be unpacked before it can be installed.
|
||||
self._archive_source: Optional[Path] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.req:
|
||||
s = str(self.req)
|
||||
s = redact_auth_from_requirement(self.req)
|
||||
if self.link:
|
||||
s += " from {}".format(redact_auth_from_url(self.link.url))
|
||||
s += f" from {redact_auth_from_url(self.link.url)}"
|
||||
elif self.link:
|
||||
s = redact_auth_from_url(self.link.url)
|
||||
else:
|
||||
@@ -208,8 +222,9 @@ class InstallRequirement:
|
||||
return s
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{} object: {} editable={!r}>".format(
|
||||
self.__class__.__name__, str(self), self.editable
|
||||
return (
|
||||
f"<{self.__class__.__name__} object: "
|
||||
f"{str(self)} editable={self.editable!r}>"
|
||||
)
|
||||
|
||||
def format_debug(self) -> str:
|
||||
@@ -217,7 +232,7 @@ class InstallRequirement:
|
||||
attributes = vars(self)
|
||||
names = sorted(attributes)
|
||||
|
||||
state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names))
|
||||
state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names))
|
||||
return "<{name} object: {{{state}}}>".format(
|
||||
name=self.__class__.__name__,
|
||||
state=", ".join(state),
|
||||
@@ -230,7 +245,7 @@ class InstallRequirement:
|
||||
return None
|
||||
return self.req.name
|
||||
|
||||
@functools.lru_cache() # use cached_property in python 3.8+
|
||||
@functools.cached_property
|
||||
def supports_pyproject_editable(self) -> bool:
|
||||
if not self.use_pep517:
|
||||
return False
|
||||
@@ -244,6 +259,7 @@ class InstallRequirement:
|
||||
|
||||
@property
|
||||
def specifier(self) -> SpecifierSet:
|
||||
assert self.req is not None
|
||||
return self.req.specifier
|
||||
|
||||
@property
|
||||
@@ -257,7 +273,8 @@ class InstallRequirement:
|
||||
|
||||
For example, some-package==1.2 is pinned; some-package>1.2 is not.
|
||||
"""
|
||||
specifiers = self.specifier
|
||||
assert self.req is not None
|
||||
specifiers = self.req.specifier
|
||||
return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
|
||||
|
||||
def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
|
||||
@@ -305,6 +322,7 @@ class InstallRequirement:
|
||||
else:
|
||||
link = None
|
||||
if link and link.hash:
|
||||
assert link.hash_name is not None
|
||||
good_hashes.setdefault(link.hash_name, []).append(link.hash)
|
||||
return Hashes(good_hashes)
|
||||
|
||||
@@ -314,6 +332,7 @@ class InstallRequirement:
|
||||
return None
|
||||
s = str(self.req)
|
||||
if self.comes_from:
|
||||
comes_from: Optional[str]
|
||||
if isinstance(self.comes_from, str):
|
||||
comes_from = self.comes_from
|
||||
else:
|
||||
@@ -345,7 +364,7 @@ class InstallRequirement:
|
||||
|
||||
# When parallel builds are enabled, add a UUID to the build directory
|
||||
# name so multiple builds do not interfere with each other.
|
||||
dir_name: str = canonicalize_name(self.name)
|
||||
dir_name: str = canonicalize_name(self.req.name)
|
||||
if parallel_builds:
|
||||
dir_name = f"{dir_name}_{uuid.uuid4().hex}"
|
||||
|
||||
@@ -377,7 +396,7 @@ class InstallRequirement:
|
||||
else:
|
||||
op = "==="
|
||||
|
||||
self.req = Requirement(
|
||||
self.req = get_requirement(
|
||||
"".join(
|
||||
[
|
||||
self.metadata["Name"],
|
||||
@@ -388,6 +407,7 @@ class InstallRequirement:
|
||||
)
|
||||
|
||||
def warn_on_mismatching_name(self) -> None:
|
||||
assert self.req is not None
|
||||
metadata_name = canonicalize_name(self.metadata["Name"])
|
||||
if canonicalize_name(self.req.name) == metadata_name:
|
||||
# Everything is fine.
|
||||
@@ -402,7 +422,7 @@ class InstallRequirement:
|
||||
metadata_name,
|
||||
self.name,
|
||||
)
|
||||
self.req = Requirement(metadata_name)
|
||||
self.req = get_requirement(metadata_name)
|
||||
|
||||
def check_if_exists(self, use_user_site: bool) -> None:
|
||||
"""Find an installed distribution that satisfies or conflicts
|
||||
@@ -457,6 +477,7 @@ class InstallRequirement:
|
||||
# Things valid for sdists
|
||||
@property
|
||||
def unpacked_source_directory(self) -> str:
|
||||
assert self.source_dir, f"No source dir for {self}"
|
||||
return os.path.join(
|
||||
self.source_dir, self.link and self.link.subdirectory_fragment or ""
|
||||
)
|
||||
@@ -493,15 +514,7 @@ class InstallRequirement:
|
||||
)
|
||||
|
||||
if pyproject_toml_data is None:
|
||||
if self.config_settings:
|
||||
deprecated(
|
||||
reason=f"Config settings are ignored for project {self}.",
|
||||
replacement=(
|
||||
"to use --use-pep517 or add a "
|
||||
"pyproject.toml file to the project"
|
||||
),
|
||||
gone_in="23.3",
|
||||
)
|
||||
assert not self.config_settings
|
||||
self.use_pep517 = False
|
||||
return
|
||||
|
||||
@@ -525,7 +538,7 @@ class InstallRequirement:
|
||||
if (
|
||||
self.editable
|
||||
and self.use_pep517
|
||||
and not self.supports_pyproject_editable()
|
||||
and not self.supports_pyproject_editable
|
||||
and not os.path.isfile(self.setup_py_path)
|
||||
and not os.path.isfile(self.setup_cfg_path)
|
||||
):
|
||||
@@ -543,7 +556,7 @@ class InstallRequirement:
|
||||
Under PEP 517 and PEP 660, call the backend hook to prepare the metadata.
|
||||
Under legacy processing, call setup.py egg-info.
|
||||
"""
|
||||
assert self.source_dir
|
||||
assert self.source_dir, f"No source dir for {self}"
|
||||
details = self.name or f"from {self.link}"
|
||||
|
||||
if self.use_pep517:
|
||||
@@ -551,7 +564,7 @@ class InstallRequirement:
|
||||
if (
|
||||
self.editable
|
||||
and self.permit_editable_wheels
|
||||
and self.supports_pyproject_editable()
|
||||
and self.supports_pyproject_editable
|
||||
):
|
||||
self.metadata_directory = generate_editable_metadata(
|
||||
build_env=self.build_env,
|
||||
@@ -592,8 +605,10 @@ class InstallRequirement:
|
||||
if self.metadata_directory:
|
||||
return get_directory_distribution(self.metadata_directory)
|
||||
elif self.local_file_path and self.is_wheel:
|
||||
assert self.req is not None
|
||||
return get_wheel_distribution(
|
||||
FilesystemWheel(self.local_file_path), canonicalize_name(self.name)
|
||||
FilesystemWheel(self.local_file_path),
|
||||
canonicalize_name(self.req.name),
|
||||
)
|
||||
raise AssertionError(
|
||||
f"InstallRequirement {self} has no metadata directory and no wheel: "
|
||||
@@ -601,9 +616,9 @@ class InstallRequirement:
|
||||
)
|
||||
|
||||
def assert_source_matches_version(self) -> None:
|
||||
assert self.source_dir
|
||||
assert self.source_dir, f"No source dir for {self}"
|
||||
version = self.metadata["version"]
|
||||
if self.req.specifier and version not in self.req.specifier:
|
||||
if self.req and self.req.specifier and version not in self.req.specifier:
|
||||
logger.warning(
|
||||
"Requested %s, but installing version %s",
|
||||
self,
|
||||
@@ -640,6 +655,27 @@ class InstallRequirement:
|
||||
parallel_builds=parallel_builds,
|
||||
)
|
||||
|
||||
def needs_unpacked_archive(self, archive_source: Path) -> None:
|
||||
assert self._archive_source is None
|
||||
self._archive_source = archive_source
|
||||
|
||||
def ensure_pristine_source_checkout(self) -> None:
|
||||
"""Ensure the source directory has not yet been built in."""
|
||||
assert self.source_dir is not None
|
||||
if self._archive_source is not None:
|
||||
unpack_file(str(self._archive_source), self.source_dir)
|
||||
elif is_installable_dir(self.source_dir):
|
||||
# If a checkout exists, it's unwise to keep going.
|
||||
# version inconsistencies are logged later, but do not fail
|
||||
# the installation.
|
||||
raise PreviousBuildDirError(
|
||||
f"pip can't proceed with requirements '{self}' due to a "
|
||||
f"pre-existing build directory ({self.source_dir}). This is likely "
|
||||
"due to a previous installation that failed . pip is "
|
||||
"being responsible and not assuming it can delete this. "
|
||||
"Please delete it and try again."
|
||||
)
|
||||
|
||||
# For editable installations
|
||||
def update_editable(self) -> None:
|
||||
if not self.link:
|
||||
@@ -696,9 +732,10 @@ class InstallRequirement:
|
||||
name = name.replace(os.path.sep, "/")
|
||||
return name
|
||||
|
||||
assert self.req is not None
|
||||
path = os.path.join(parentdir, path)
|
||||
name = _clean_zip_name(path, rootdir)
|
||||
return self.name + "/" + name
|
||||
return self.req.name + "/" + name
|
||||
|
||||
def archive(self, build_dir: Optional[str]) -> None:
|
||||
"""Saves archive to provided build_dir.
|
||||
@@ -715,8 +752,8 @@ class InstallRequirement:
|
||||
|
||||
if os.path.exists(archive_path):
|
||||
response = ask_path_exists(
|
||||
"The file {} exists. (i)gnore, (w)ipe, "
|
||||
"(b)ackup, (a)bort ".format(display_path(archive_path)),
|
||||
f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, "
|
||||
"(b)ackup, (a)bort ",
|
||||
("i", "w", "b", "a"),
|
||||
)
|
||||
if response == "i":
|
||||
@@ -777,8 +814,9 @@ class InstallRequirement:
|
||||
use_user_site: bool = False,
|
||||
pycompile: bool = True,
|
||||
) -> None:
|
||||
assert self.req is not None
|
||||
scheme = get_scheme(
|
||||
self.name,
|
||||
self.req.name,
|
||||
user=use_user_site,
|
||||
home=home,
|
||||
root=root,
|
||||
@@ -787,12 +825,34 @@ class InstallRequirement:
|
||||
)
|
||||
|
||||
if self.editable and not self.is_wheel:
|
||||
deprecated(
|
||||
reason=(
|
||||
f"Legacy editable install of {self} (setup.py develop) "
|
||||
"is deprecated."
|
||||
),
|
||||
replacement=(
|
||||
"to add a pyproject.toml or enable --use-pep517, "
|
||||
"and use setuptools >= 64. "
|
||||
"If the resulting installation is not behaving as expected, "
|
||||
"try using --config-settings editable_mode=compat. "
|
||||
"Please consult the setuptools documentation for more information"
|
||||
),
|
||||
gone_in="25.0",
|
||||
issue=11457,
|
||||
)
|
||||
if self.config_settings:
|
||||
logger.warning(
|
||||
"--config-settings ignored for legacy editable install of %s. "
|
||||
"Consider upgrading to a version of setuptools "
|
||||
"that supports PEP 660 (>= 64).",
|
||||
self,
|
||||
)
|
||||
install_editable_legacy(
|
||||
global_options=global_options if global_options is not None else [],
|
||||
prefix=prefix,
|
||||
home=home,
|
||||
use_user_site=use_user_site,
|
||||
name=self.name,
|
||||
name=self.req.name,
|
||||
setup_py_path=self.setup_py_path,
|
||||
isolated=self.isolated,
|
||||
build_env=self.build_env,
|
||||
@@ -805,7 +865,7 @@ class InstallRequirement:
|
||||
assert self.local_file_path
|
||||
|
||||
install_wheel(
|
||||
self.name,
|
||||
self.req.name,
|
||||
self.local_file_path,
|
||||
scheme=scheme,
|
||||
req_description=str(self.req),
|
||||
@@ -865,7 +925,7 @@ def check_legacy_setup_py_options(
|
||||
reason="--build-option and --global-option are deprecated.",
|
||||
issue=11859,
|
||||
replacement="to use --config-settings",
|
||||
gone_in="23.3",
|
||||
gone_in="25.0",
|
||||
)
|
||||
logger.warning(
|
||||
"Implying --no-binary=:all: due to the presence of "
|
||||
|
||||
@@ -2,12 +2,9 @@ import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, List
|
||||
|
||||
from pip._vendor.packaging.specifiers import LegacySpecifier
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import LegacyVersion
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -83,37 +80,3 @@ class RequirementSet:
|
||||
for install_req in self.all_requirements
|
||||
if not install_req.constraint and not install_req.satisfied_by
|
||||
]
|
||||
|
||||
def warn_legacy_versions_and_specifiers(self) -> None:
|
||||
for req in self.requirements_to_install:
|
||||
version = req.get_dist().version
|
||||
if isinstance(version, LegacyVersion):
|
||||
deprecated(
|
||||
reason=(
|
||||
f"pip has selected the non standard version {version} "
|
||||
f"of {req}. In the future this version will be "
|
||||
f"ignored as it isn't standard compliant."
|
||||
),
|
||||
replacement=(
|
||||
"set or update constraints to select another version "
|
||||
"or contact the package author to fix the version number"
|
||||
),
|
||||
issue=12063,
|
||||
gone_in="23.3",
|
||||
)
|
||||
for dep in req.get_dist().iter_dependencies():
|
||||
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
|
||||
deprecated(
|
||||
reason=(
|
||||
f"pip has selected {req} {version} which has non "
|
||||
f"standard dependency specifier {dep}. "
|
||||
f"In the future this version of {req} will be "
|
||||
f"ignored as it isn't standard compliant."
|
||||
),
|
||||
replacement=(
|
||||
"set or update constraints to select another version "
|
||||
"or contact the package author to fix the version number"
|
||||
),
|
||||
issue=12063,
|
||||
gone_in="23.3",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import sysconfig
|
||||
from importlib.util import cache_from_source
|
||||
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple
|
||||
|
||||
from pip._internal.exceptions import UninstallationError
|
||||
from pip._internal.exceptions import LegacyDistutilsInstall, UninstallMissingRecord
|
||||
from pip._internal.locations import get_bin_prefix, get_bin_user
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
@@ -61,7 +61,7 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
|
||||
|
||||
UninstallPathSet.add() takes care of the __pycache__ .py[co].
|
||||
|
||||
If RECORD is not found, raises UninstallationError,
|
||||
If RECORD is not found, raises an error,
|
||||
with possible information from the INSTALLER file.
|
||||
|
||||
https://packaging.python.org/specifications/recording-installed-packages/
|
||||
@@ -71,17 +71,7 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
|
||||
|
||||
entries = dist.iter_declared_entries()
|
||||
if entries is None:
|
||||
msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist)
|
||||
installer = dist.installer
|
||||
if not installer or installer == "pip":
|
||||
dep = "{}=={}".format(dist.raw_name, dist.version)
|
||||
msg += (
|
||||
" You might be able to recover from this via: "
|
||||
"'pip install --force-reinstall --no-deps {}'.".format(dep)
|
||||
)
|
||||
else:
|
||||
msg += " Hint: The package was installed by {}.".format(installer)
|
||||
raise UninstallationError(msg)
|
||||
raise UninstallMissingRecord(distribution=dist)
|
||||
|
||||
for entry in entries:
|
||||
path = os.path.join(location, entry)
|
||||
@@ -172,8 +162,7 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
|
||||
folders.add(os.path.dirname(path))
|
||||
files.add(path)
|
||||
|
||||
# probably this one https://github.com/python/mypy/issues/390
|
||||
_normcased_files = set(map(os.path.normcase, files)) # type: ignore
|
||||
_normcased_files = set(map(os.path.normcase, files))
|
||||
|
||||
folders = compact(folders)
|
||||
|
||||
@@ -274,7 +263,7 @@ class StashedUninstallPathSet:
|
||||
|
||||
def commit(self) -> None:
|
||||
"""Commits the uninstall by removing stashed files."""
|
||||
for _, save_dir in self._save_dirs.items():
|
||||
for save_dir in self._save_dirs.values():
|
||||
save_dir.cleanup()
|
||||
self._moves = []
|
||||
self._save_dirs = {}
|
||||
@@ -316,7 +305,7 @@ class UninstallPathSet:
|
||||
# Create local cache of normalize_path results. Creating an UninstallPathSet
|
||||
# can result in hundreds/thousands of redundant calls to normalize_path with
|
||||
# the same args, which hurts performance.
|
||||
self._normalize_path_cached = functools.lru_cache()(normalize_path)
|
||||
self._normalize_path_cached = functools.lru_cache(normalize_path)
|
||||
|
||||
def _permitted(self, path: str) -> bool:
|
||||
"""
|
||||
@@ -368,7 +357,7 @@ class UninstallPathSet:
|
||||
)
|
||||
return
|
||||
|
||||
dist_name_version = f"{self._dist.raw_name}-{self._dist.version}"
|
||||
dist_name_version = f"{self._dist.raw_name}-{self._dist.raw_version}"
|
||||
logger.info("Uninstalling %s:", dist_name_version)
|
||||
|
||||
with indent_log():
|
||||
@@ -510,13 +499,7 @@ class UninstallPathSet:
|
||||
paths_to_remove.add(f"{path}.pyo")
|
||||
|
||||
elif dist.installed_by_distutils:
|
||||
raise UninstallationError(
|
||||
"Cannot uninstall {!r}. It is a distutils installed project "
|
||||
"and thus we cannot accurately determine which files belong "
|
||||
"to it which would lead to only a partial uninstall.".format(
|
||||
dist.raw_name,
|
||||
)
|
||||
)
|
||||
raise LegacyDistutilsInstall(distribution=dist)
|
||||
|
||||
elif dist.installed_as_egg:
|
||||
# package installed by easy_install
|
||||
|
||||
Reference in New Issue
Block a user