update
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,10 +11,10 @@ import logging
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from html.parser import HTMLParser
|
||||
from optparse import Values
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
@@ -22,6 +22,7 @@ from typing import (
|
||||
MutableMapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Protocol,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
@@ -42,11 +43,6 @@ from pip._internal.vcs import vcs
|
||||
|
||||
from .sources import CandidatesFromPage, LinkSource, build_source
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
else:
|
||||
Protocol = object
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ResponseHeaders = MutableMapping[str, str]
|
||||
@@ -201,8 +197,7 @@ class CacheablePageContent:
|
||||
|
||||
|
||||
class ParseLinks(Protocol):
|
||||
def __call__(self, page: "IndexContent") -> Iterable[Link]:
|
||||
...
|
||||
def __call__(self, page: "IndexContent") -> Iterable[Link]: ...
|
||||
|
||||
|
||||
def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
|
||||
@@ -254,29 +249,22 @@ def parse_links(page: "IndexContent") -> Iterable[Link]:
|
||||
yield link
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IndexContent:
|
||||
"""Represents one response (or page), along with its URL"""
|
||||
"""Represents one response (or page), along with its URL.
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
content: bytes,
|
||||
content_type: str,
|
||||
encoding: Optional[str],
|
||||
url: str,
|
||||
cache_link_parsing: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
:param encoding: the encoding to decode the given content.
|
||||
:param url: the URL from which the HTML was downloaded.
|
||||
:param cache_link_parsing: whether links parsed from this page's url
|
||||
should be cached. PyPI index urls should
|
||||
have this set to False, for example.
|
||||
"""
|
||||
self.content = content
|
||||
self.content_type = content_type
|
||||
self.encoding = encoding
|
||||
self.url = url
|
||||
self.cache_link_parsing = cache_link_parsing
|
||||
:param encoding: the encoding to decode the given content.
|
||||
:param url: the URL from which the HTML was downloaded.
|
||||
:param cache_link_parsing: whether links parsed from this page's url
|
||||
should be cached. PyPI index urls should
|
||||
have this set to False, for example.
|
||||
"""
|
||||
|
||||
content: bytes
|
||||
content_type: str
|
||||
encoding: Optional[str]
|
||||
url: str
|
||||
cache_link_parsing: bool = True
|
||||
|
||||
def __str__(self) -> str:
|
||||
return redact_auth_from_url(self.url)
|
||||
@@ -400,7 +388,6 @@ class CollectedSources(NamedTuple):
|
||||
|
||||
|
||||
class LinkCollector:
|
||||
|
||||
"""
|
||||
Responsible for collecting Link objects from all configured locations,
|
||||
making network requests as needed.
|
||||
@@ -473,6 +460,7 @@ class LinkCollector:
|
||||
page_validator=self.session.is_secure_origin,
|
||||
expand_dir=False,
|
||||
cache_link_parsing=False,
|
||||
project_name=project_name,
|
||||
)
|
||||
for loc in self.search_scope.get_index_urls_locations(project_name)
|
||||
).values()
|
||||
@@ -483,6 +471,7 @@ class LinkCollector:
|
||||
page_validator=self.session.is_secure_origin,
|
||||
expand_dir=True,
|
||||
cache_link_parsing=True,
|
||||
project_name=project_name,
|
||||
)
|
||||
for loc in self.find_links
|
||||
).values()
|
||||
|
||||
@@ -5,12 +5,13 @@ import functools
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union
|
||||
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import _BaseVersion
|
||||
from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
@@ -106,7 +107,6 @@ class LinkType(enum.Enum):
|
||||
|
||||
|
||||
class LinkEvaluator:
|
||||
|
||||
"""
|
||||
Responsible for evaluating links for a particular project.
|
||||
"""
|
||||
@@ -198,7 +198,7 @@ class LinkEvaluator:
|
||||
reason = f"wrong project name (not {self.project_name})"
|
||||
return (LinkType.different_project, reason)
|
||||
|
||||
supported_tags = self._target_python.get_tags()
|
||||
supported_tags = self._target_python.get_unsorted_tags()
|
||||
if not wheel.supported(supported_tags):
|
||||
# Include the wheel's tags in the reason string to
|
||||
# simplify troubleshooting compatibility issues.
|
||||
@@ -323,23 +323,15 @@ def filter_unallowed_hashes(
|
||||
return filtered
|
||||
|
||||
|
||||
@dataclass
|
||||
class CandidatePreferences:
|
||||
|
||||
"""
|
||||
Encapsulates some of the preferences for filtering and sorting
|
||||
InstallationCandidate objects.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prefer_binary: bool = False,
|
||||
allow_all_prereleases: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
:param allow_all_prereleases: Whether to allow all pre-releases.
|
||||
"""
|
||||
self.allow_all_prereleases = allow_all_prereleases
|
||||
self.prefer_binary = prefer_binary
|
||||
prefer_binary: bool = False
|
||||
allow_all_prereleases: bool = False
|
||||
|
||||
|
||||
class BestCandidateResult:
|
||||
@@ -383,7 +375,6 @@ class BestCandidateResult:
|
||||
|
||||
|
||||
class CandidateEvaluator:
|
||||
|
||||
"""
|
||||
Responsible for filtering and sorting candidates for installation based
|
||||
on what tags are valid.
|
||||
@@ -414,7 +405,7 @@ class CandidateEvaluator:
|
||||
if specifier is None:
|
||||
specifier = specifiers.SpecifierSet()
|
||||
|
||||
supported_tags = target_python.get_tags()
|
||||
supported_tags = target_python.get_sorted_tags()
|
||||
|
||||
return cls(
|
||||
project_name=project_name,
|
||||
@@ -461,24 +452,23 @@ class CandidateEvaluator:
|
||||
# Using None infers from the specifier instead.
|
||||
allow_prereleases = self._allow_all_prereleases or None
|
||||
specifier = self._specifier
|
||||
versions = {
|
||||
str(v)
|
||||
for v in specifier.filter(
|
||||
# We turn the version object into a str here because otherwise
|
||||
# when we're debundled but setuptools isn't, Python will see
|
||||
# packaging.version.Version and
|
||||
# pkg_resources._vendor.packaging.version.Version as different
|
||||
# types. This way we'll use a str as a common data interchange
|
||||
# format. If we stop using the pkg_resources provided specifier
|
||||
# and start using our own, we can drop the cast to str().
|
||||
(str(c.version) for c in candidates),
|
||||
|
||||
# We turn the version object into a str here because otherwise
|
||||
# when we're debundled but setuptools isn't, Python will see
|
||||
# packaging.version.Version and
|
||||
# pkg_resources._vendor.packaging.version.Version as different
|
||||
# types. This way we'll use a str as a common data interchange
|
||||
# format. If we stop using the pkg_resources provided specifier
|
||||
# and start using our own, we can drop the cast to str().
|
||||
candidates_and_versions = [(c, str(c.version)) for c in candidates]
|
||||
versions = set(
|
||||
specifier.filter(
|
||||
(v for _, v in candidates_and_versions),
|
||||
prereleases=allow_prereleases,
|
||||
)
|
||||
}
|
||||
|
||||
# Again, converting version to str to deal with debundling.
|
||||
applicable_candidates = [c for c in candidates if str(c.version) in versions]
|
||||
)
|
||||
|
||||
applicable_candidates = [c for c, v in candidates_and_versions if v in versions]
|
||||
filtered_applicable_candidates = filter_unallowed_hashes(
|
||||
candidates=applicable_candidates,
|
||||
hashes=self._hashes,
|
||||
@@ -533,8 +523,8 @@ class CandidateEvaluator:
|
||||
)
|
||||
except ValueError:
|
||||
raise UnsupportedWheel(
|
||||
"{} is not a supported wheel for this platform. It "
|
||||
"can't be sorted.".format(wheel.filename)
|
||||
f"{wheel.filename} is not a supported wheel for this platform. It "
|
||||
"can't be sorted."
|
||||
)
|
||||
if self._prefer_binary:
|
||||
binary_preference = 1
|
||||
@@ -761,11 +751,14 @@ class PackageFinder:
|
||||
self._log_skipped_link(link, result, detail)
|
||||
return None
|
||||
|
||||
return InstallationCandidate(
|
||||
name=link_evaluator.project_name,
|
||||
link=link,
|
||||
version=detail,
|
||||
)
|
||||
try:
|
||||
return InstallationCandidate(
|
||||
name=link_evaluator.project_name,
|
||||
link=link,
|
||||
version=detail,
|
||||
)
|
||||
except InvalidVersion:
|
||||
return None
|
||||
|
||||
def evaluate_links(
|
||||
self, link_evaluator: LinkEvaluator, links: Iterable[Link]
|
||||
@@ -939,9 +932,7 @@ class PackageFinder:
|
||||
_format_versions(best_candidate_result.iter_all()),
|
||||
)
|
||||
|
||||
raise DistributionNotFound(
|
||||
"No matching distribution found for {}".format(req)
|
||||
)
|
||||
raise DistributionNotFound(f"No matching distribution found for {req}")
|
||||
|
||||
def _should_install_candidate(
|
||||
candidate: Optional[InstallationCandidate],
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import pathlib
|
||||
from typing import Callable, Iterable, Optional, Tuple
|
||||
from collections import defaultdict
|
||||
from typing import Callable, Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.packaging.utils import (
|
||||
InvalidSdistFilename,
|
||||
InvalidVersion,
|
||||
InvalidWheelFilename,
|
||||
canonicalize_name,
|
||||
parse_sdist_filename,
|
||||
parse_wheel_filename,
|
||||
)
|
||||
|
||||
from pip._internal.models.candidate import InstallationCandidate
|
||||
from pip._internal.models.link import Link
|
||||
@@ -36,6 +45,53 @@ def _is_html_file(file_url: str) -> bool:
|
||||
return mimetypes.guess_type(file_url, strict=False)[0] == "text/html"
|
||||
|
||||
|
||||
class _FlatDirectoryToUrls:
|
||||
"""Scans directory and caches results"""
|
||||
|
||||
def __init__(self, path: str) -> None:
|
||||
self._path = path
|
||||
self._page_candidates: List[str] = []
|
||||
self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list)
|
||||
self._scanned_directory = False
|
||||
|
||||
def _scan_directory(self) -> None:
|
||||
"""Scans directory once and populates both page_candidates
|
||||
and project_name_to_urls at the same time
|
||||
"""
|
||||
for entry in os.scandir(self._path):
|
||||
url = path_to_url(entry.path)
|
||||
if _is_html_file(url):
|
||||
self._page_candidates.append(url)
|
||||
continue
|
||||
|
||||
# File must have a valid wheel or sdist name,
|
||||
# otherwise not worth considering as a package
|
||||
try:
|
||||
project_filename = parse_wheel_filename(entry.name)[0]
|
||||
except (InvalidWheelFilename, InvalidVersion):
|
||||
try:
|
||||
project_filename = parse_sdist_filename(entry.name)[0]
|
||||
except (InvalidSdistFilename, InvalidVersion):
|
||||
continue
|
||||
|
||||
self._project_name_to_urls[project_filename].append(url)
|
||||
self._scanned_directory = True
|
||||
|
||||
@property
|
||||
def page_candidates(self) -> List[str]:
|
||||
if not self._scanned_directory:
|
||||
self._scan_directory()
|
||||
|
||||
return self._page_candidates
|
||||
|
||||
@property
|
||||
def project_name_to_urls(self) -> Dict[str, List[str]]:
|
||||
if not self._scanned_directory:
|
||||
self._scan_directory()
|
||||
|
||||
return self._project_name_to_urls
|
||||
|
||||
|
||||
class _FlatDirectorySource(LinkSource):
|
||||
"""Link source specified by ``--find-links=<path-to-dir>``.
|
||||
|
||||
@@ -45,30 +101,34 @@ class _FlatDirectorySource(LinkSource):
|
||||
* ``file_candidates``: Archives in the directory.
|
||||
"""
|
||||
|
||||
_paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
candidates_from_page: CandidatesFromPage,
|
||||
path: str,
|
||||
project_name: str,
|
||||
) -> None:
|
||||
self._candidates_from_page = candidates_from_page
|
||||
self._path = pathlib.Path(os.path.realpath(path))
|
||||
self._project_name = canonicalize_name(project_name)
|
||||
|
||||
# Get existing instance of _FlatDirectoryToUrls if it exists
|
||||
if path in self._paths_to_urls:
|
||||
self._path_to_urls = self._paths_to_urls[path]
|
||||
else:
|
||||
self._path_to_urls = _FlatDirectoryToUrls(path=path)
|
||||
self._paths_to_urls[path] = self._path_to_urls
|
||||
|
||||
@property
|
||||
def link(self) -> Optional[Link]:
|
||||
return None
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
for path in self._path.iterdir():
|
||||
url = path_to_url(str(path))
|
||||
if not _is_html_file(url):
|
||||
continue
|
||||
for url in self._path_to_urls.page_candidates:
|
||||
yield from self._candidates_from_page(Link(url))
|
||||
|
||||
def file_links(self) -> FoundLinks:
|
||||
for path in self._path.iterdir():
|
||||
url = path_to_url(str(path))
|
||||
if _is_html_file(url):
|
||||
continue
|
||||
for url in self._path_to_urls.project_name_to_urls[self._project_name]:
|
||||
yield Link(url)
|
||||
|
||||
|
||||
@@ -170,6 +230,7 @@ def build_source(
|
||||
page_validator: PageValidator,
|
||||
expand_dir: bool,
|
||||
cache_link_parsing: bool,
|
||||
project_name: str,
|
||||
) -> Tuple[Optional[str], Optional[LinkSource]]:
|
||||
path: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
@@ -203,6 +264,7 @@ def build_source(
|
||||
source = _FlatDirectorySource(
|
||||
candidates_from_page=candidates_from_page,
|
||||
path=path,
|
||||
project_name=project_name,
|
||||
)
|
||||
else:
|
||||
source = _IndexDirectorySource(
|
||||
|
||||
Reference in New Issue
Block a user